primer.rst 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. ===================================
  2. ``modeladmin`` customisation primer
  3. ===================================
  4. The ``modeladmin`` app is designed to offer you as much flexibility as possible
  5. in how your model and its objects are represented in Wagtail's CMS. This page
  6. aims to provide you with some background information to help you gain a better
  7. understanding of what the app can do, and to point you in the right direction,
  8. depending on the kind of customisations you're looking to make.
  9. .. contents::
  10. :local:
  11. :depth: 1
  12. ---------------------------------------------------------
  13. Wagtail's ``ModelAdmin`` class isn't the same as Django's
  14. ---------------------------------------------------------
  15. Wagtail's ``ModelAdmin`` class is designed to be used in a similar way to
  16. Django's class of the same name, and often uses the same attribute and method
  17. names to achieve similar things. However, there are a few key differences:
  18. Add & edit forms are still defined by ``panels`` and ``edit_handlers``
  19. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  20. In Wagtail, controlling which fields appear in add/edit forms for your
  21. ``Model``, and defining how they are grouped and ordered, is achieved by
  22. adding a ``panels`` attribute, or `edit_handler` to your ``Model`` class.
  23. This remains the same whether your model is a ``Page`` type, a snippet, or
  24. just a standard Django ``Model``. Because of this, Wagtail's ``ModelAdmin``
  25. class is mostly concerned with 'listing' configuration. For example,
  26. ``list_display``, ``list_filter`` and ``search_fields`` attributes are
  27. present and support largely the same values as Django's ModelAdmin class,
  28. while `fields`, `fieldsets`, `exclude` and other attributes you may be used
  29. to using to configure Django's add/edit views, simply aren't supported by
  30. Wagtail's version.
  31. 'Page type' models need to be treated differently to other models
  32. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  33. While ``modeladmin``'s listing view and it's supported customisation
  34. options work in exactly the same way for all types of ``Model``, when it
  35. comes to the other management views, the treatment differs depending on
  36. whether your ModelAdmin class is representing a page type model (that
  37. extends ``wagtailcore.models.Page``) or not.
  38. Pages in Wagtail have some unique properties, and require additional views,
  39. interface elements and general treatment in order to be managed
  40. effectively. For example, they have a tree structure that must be preserved
  41. properly as pages are added, deleted and moved around. They also have a
  42. revisions system, their own permission considerations, and the facility to
  43. preview changes before saving changes. Because of this added complexity
  44. Wagtail provides its own specific views for managing any custom page types
  45. you might add to your project; whether you create a ``ModelAdmin`` class
  46. for them, or not.
  47. In order to deliver a consistent experience for users, ``modeladmin``
  48. simply redirects users to Wagtail's existing page management views wherever
  49. possible. You should bare this in mind if you ever find yourself wanting to
  50. change what happens when pages of a certain type are added, deleted,
  51. published, or have some other action applied to them. Customising the
  52. ``CreateView`` or ``EditView`` for your a page type ``Model`` (even it just
  53. to add an additional stylesheet or javascript), simply won't have any
  54. effect, as those views are not used.
  55. If you do find yourself needing to customise the add, edit or other
  56. behaviour for a page type model, you should take a look at the following
  57. part of the documentation: :ref:`admin_hooks`.
  58. Wagtail's ``ModelAdmin`` class is 'modular'
  59. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  60. Unlike Django's class of the same name, wagtailadmin's ``ModelAmin`` acts
  61. primarily as a 'controller' class. While it does have a set of attributes
  62. and methods to enable you to configure how various components should treat
  63. your model, it has been deliberately designed to do as little work as
  64. possible by itself; it designates all of the real work to a set of
  65. separate, swappable components.
  66. The theory is: If you want to do something differently, or add some
  67. functionality that ``modeladmin`` doesn't already have, you can create new
  68. classes (or extend the ones provided by ``modeladmin``) and easily
  69. configure your ``ModelAdmin`` class to use them instead of the defaults.
  70. - Learn more about :ref:`modeladmin_overriding_views`
  71. - Learn more about :ref:`modeladmin_overriding_helper_classes`
  72. ------------------------------------
  73. Changing what appears in the listing
  74. ------------------------------------
  75. You should familarise yourself with the attributes and methods supported by
  76. the ``ModelAdmin`` class, that allow you to change what is displayed in the
  77. ``IndexView``. The following page should give you everything you need to get
  78. going: :doc:`indexview`
  79. .. _modeladmin_adding_css_and_js:
  80. -----------------------------------------------
  81. Adding additional stylesheets and/or javascript
  82. -----------------------------------------------
  83. The ``ModelAdmin`` class provides several attributes to enable you to easily
  84. add additional stylesheets and javascript to the admin interface for your
  85. model. Each atttribute simply needs to be a list of paths to the files you
  86. want to include. If the path is for a file in your project's static directory,
  87. Wagtail will automatically prepended paths for each path with ``STATIC_URL``,
  88. so you don't need to repeat it each time in your list of paths.
  89. If you'd like to add styles or scripts to the ``IndexView``, you should set the
  90. following attributes:
  91. - ``index_view_extra_css`` - Where each item is the path name of a
  92. pre-compiled stylesheet that you'd like to include.
  93. - ``index_view_extra_js`` - Where each item is the path name of a javascript
  94. file that you'd like to include.
  95. If you'd like to do the same for ``CreateView`` and ``EditView``, you should
  96. set the following attributes:
  97. - ``form_view_extra_css`` - Where each item is the path name of a
  98. pre-compiled stylesheet that you'd like to include.
  99. - ``form_view_extra_js`` - Where each item is the path name of a javascript
  100. file that you'd like to include.
  101. And if you're using the ``InspectView`` for your model, and want to do the same
  102. for that view, your should set the following attributes:
  103. - ``inspect_view_extra_css`` - Where each item is the path name of a
  104. pre-compiled stylesheet that you'd like to include.
  105. - ``inspect_view_extra_js`` - Where each item is the path name of a javascript
  106. file that you'd like to include.
  107. .. _modeladmin_overriding_templates:
  108. --------------------
  109. Overriding templates
  110. --------------------
  111. For all modeladmin views, Wagtail looks for templates in the following folders
  112. within your project, before resorting to the defaults:
  113. 1. ``/modeladmin/app-name/model-name/``
  114. 2. ``/modeladmin/app-name/``
  115. 3. ``/modeladmin/``
  116. So, to override the template used by ``IndexView`` for example, you'd create a
  117. new ``index.html`` template and put it in one of those locations. For example,
  118. if you wanted to do this for an ``ArticlePage`` model in a ``news`` app, you'd
  119. add your custom template as ``modeladmin/news/article/index.html``.
  120. For reference, ``modeladmin`` looks for templates with the following names for
  121. each view:
  122. - ``'index.html'`` for ``IndexView``
  123. - ``'inspect.html'`` for ``InspectView``
  124. - ``'create.html'`` for ``CreateView``
  125. - ``'edit.html'`` for ``EditView``
  126. - ``'delete.html'`` for ``DeleteView``
  127. - ``'choose_parent.html'`` for ``ChooseParentView``
  128. If for any reason you'd rather bypass this behaviour and explicitly specify a
  129. template for a specific view, you can set either of the following attributes
  130. on your ``ModelAdmin`` class:
  131. - ``index_template_name`` to specify a template for ``IndexView``
  132. - ``inspect_template_name`` to specify a template for ``InspectView``
  133. - ``create_template_name`` to specify a template for ``CreateView``
  134. - ``edit_template_name`` to specify a template for ``EditView``
  135. - ``delete_template_name`` to specify a template for ``DeleteView``
  136. - ``choose_parent_template_name`` to specify a template for ``ChooseParentView``
  137. .. _modeladmin_overriding_views:
  138. ----------------
  139. Overriding views
  140. ----------------
  141. For all of the views offered by ``ModelAdmin``, the class provides an attribute
  142. that you can override, to tell it which class you'd like to use:
  143. - ``index_view_class``
  144. - ``inspect_view_class``
  145. - ``create_view_class`` (not used for 'page type' models)
  146. - ``edit_view_class`` (not used for 'page type' models)
  147. - ``delete_view_class`` (not used for 'page type' models)
  148. - ``choose_parent_view_class`` (only used for 'page type' models)
  149. For example, if you'd like to create your own view class and use it for the
  150. ``IndexView``, you would do the following:
  151. .. code-block:: python
  152. from wagtail.contrib.modeladmin.views import IndexView
  153. from wagtail.contrib.modeladmin.options import ModelAdmin
  154. from .models import MyModel
  155. class MyCustomIndexView(IndexView):
  156. # New functionality and exising method overrides added here
  157. ...
  158. class MyModelAdmin(ModelAdmin):
  159. model = MyModel
  160. index_view_class = MyModelIndexView
  161. Or, if you have no need for any of ``IndexView``'s exisiting functionality in
  162. your view, and would rather create your own view from scratch, ``modeladmin``
  163. will support that, too. However, it's highly recommended that you use
  164. ``modeladmin.views.WMABaseView`` as a base for your view. It'll make
  165. integrating with your ``ModelAdmin`` class much easier, and provides a bunch of
  166. useful attributes and methods to get you started.
  167. .. _modeladmin_overriding_helper_classes:
  168. -------------------------
  169. Overriding helper classes
  170. -------------------------
  171. While 'view classes' are responsible for a lot of the work, there are also
  172. a number of other tasks that ``modeladmin`` must do regularly, that need to be
  173. handled in a consistent way, and in a number of different places. These tasks
  174. are designated to set of simple classes (in ``modeladmin``, these are termed
  175. 'helper' classes) and can be found in ``wagtail.contrib.modeladmin.helpers``.
  176. If you ever intend to write and use your own custom views with ``modeladmin``,
  177. you should familiarise yourself with these helpers, as they are made available
  178. to views via the ``modeladmin.views.WMABaseView`` view.
  179. There are three types of 'helper class':
  180. - **URL helpers** - That help with the consistent generation, naming and
  181. referencing of urls.
  182. - **Permission helpers** - That help with ensuring only users with sufficient
  183. permissions can perform certain actions, or see options to perform those
  184. actions.
  185. - **Button helpers** - That, with the help of the other two, helps with the
  186. generation of buttons for use in a number of places.
  187. The ``ModelAdmin`` class allows you to define and use your own helper classes
  188. by setting values on the following attributes:
  189. .. _modeladmin_url_helper_class:
  190. ``ModelAdmin.url_helper_class``
  191. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  192. By default, the ``modeladmin.helpers.url.PageAdminURLHelper`` class is used
  193. when your model extends ``wagtailcore.models.Page``, otherwise
  194. ``modeladmin.helpers.url.AdminURLHelper`` is used.
  195. If you find that the above helper classes don't cater for your needs, you can
  196. easily create your own helper class, by sub-classing ``AdminURLHelper`` or
  197. ``PageAdminURLHelper`` (if your model extend's Wagtail's ``Page`` model), and
  198. making any neccessary additions/overrides.
  199. Once your class is defined, set the ``url_helper_class`` attribute on
  200. your ``ModelAdmin`` class to use your custom URLHelper, like so:
  201. .. code-block:: python
  202. from wagtail.contrib.modeladmin.helpers import AdminURLHelper
  203. from wagtail.contrib.modeladmin.options import ModelAdmin, modeladmin_register
  204. from .models import MyModel
  205. class MyURLHelper(AdminURLHelper):
  206. ...
  207. class MyModelAdmin(ModelAdmin):
  208. model = MyModel
  209. url_helper_class = MyURLHelper
  210. modeladmin_register(MyModelAdmin)
  211. Or, if you have a more complicated use case, where simply setting that
  212. attribute isn't possible (due to circular imports, for example) or doesn't
  213. meet your needs, you can override the ``get_url_helper_class`` method, like
  214. so:
  215. .. code-block:: python
  216. class MyModelAdmin(ModelAdmin):
  217. model = MyModel
  218. def get_url_helper_class(self):
  219. if self.some_attribute is True:
  220. return MyURLHelper
  221. return AdminURLHelper
  222. .. _modeladmin_permission_helper_class:
  223. ``ModelAdmin.permission_helper_class``
  224. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  225. By default, the ``modeladmin.helpers.permission.PagePermissionHelper``
  226. class is used when your model extends ``wagtailcore.models.Page``,
  227. otherwise ``modeladmin.helpers.permission.PermissionHelper`` is used.
  228. If you find that the above helper classes don't cater for your needs, you can
  229. easily create your own helper class, by sub-classing
  230. ``PermissionHelper`` or (if your model extend's Wagtail's ``Page`` model)
  231. ``PagePermissionHelper``, and making any neccessary additions/overrides. Once
  232. defined, you set the ``permission_helper_class`` attribute on your
  233. ``ModelAdmin`` class to use your custom class instead of the default, like so:
  234. .. code-block:: python
  235. from wagtail.contrib.modeladmin.helpers import PermissionHelper
  236. from wagtail.contrib.modeladmin.options import ModelAdmin, modeladmin_register
  237. from .models import MyModel
  238. class MyPermissionHelper(PermissionHelper):
  239. ...
  240. class MyModelAdmin(ModelAdmin):
  241. model = MyModel
  242. permission_helper_class = MyPermissionHelper
  243. modeladmin_register(MyModelAdmin)
  244. Or, if you have a more complicated use case, where simply setting an attribute
  245. isn't possible or doesn't meet your needs, you can override the
  246. ``get_permission_helper_class`` method, like so:
  247. .. code-block:: python
  248. class MyModelAdmin(ModelAdmin):
  249. model = MyModel
  250. def get_get_permission_helper_class(self):
  251. if self.some_attribute is True:
  252. return MyPermissionHelper
  253. return PermissionHelper
  254. .. _modeladmin_button_helper_class:
  255. ``ModelAdmin.button_helper_class``
  256. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  257. By default, the ``modeladmin.helpers.button.PageButtonHelper`` class is used
  258. when your model extends ``wagtailcore.models.Page``, otherwise
  259. ``modeladmin.helpers.button.ButtonHelper`` is used.
  260. If you wish to add or change buttons for your model's IndexView, you'll need to
  261. create your own button helper class, by sub-classing ``ButtonHelper`` or (if
  262. your model extend's Wagtail's ``Page`` model) ``PageButtonHelper``, and
  263. make any neccessary additions/overrides. Once defined, you set the
  264. ``button_helper_class`` attribute on your ``ModelAdmin`` class to use your
  265. custom class instead of the default, like so:
  266. .. code-block:: python
  267. from wagtail.contrib.modeladmin.helpers import ButtonHelper
  268. from wagtail.contrib.modeladmin.options import ModelAdmin, modeladmin_register
  269. from .models import MyModel
  270. class MyButtonHelper(ButtonHelper):
  271. ...
  272. class MyModelAdmin(ModelAdmin):
  273. model = MyModel
  274. button_helper_class = MyButtonHelper
  275. modeladmin_register(MyModelAdmin)
  276. Or, if you have a more complicated use case, where simply setting an attribute
  277. isn't possible or doesn't meet your needs, you can override the
  278. ``get_button_helper_class`` method, like so:
  279. .. code-block:: python
  280. class MyModelAdmin(ModelAdmin):
  281. model = MyModel
  282. def get_button_helper_class(self):
  283. if self.some_attribute is True:
  284. return MyButtonHelper
  285. return ButtonHelper
  286. .. _modeladmin_helpers_in_custom_views:
  287. Using helpers in your custom views
  288. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  289. As long as you sub-class ``modeladmin.views.WMABaseView`` (or one of the more
  290. 'specific' view classes) to create your custom view, instances of each helper
  291. should be available on instances of your class as:
  292. - ``self.url_helper``
  293. - ``self.permission_helper``
  294. - ``self.button_helper``
  295. Unlike the other two, `self.button_helper` isn't populated right away when
  296. the view is instantiated. In order to show the right buttons for the right
  297. users, ButtonHelper instances need to be 'request aware', so
  298. ``self.button_helper`` is only set once the view's ``dispatch()`` method has
  299. run, which takes a ``HttpRequest`` object as an argument, from which the
  300. current user can be identified.