admin_views.rst 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. Creating admin views
  2. ====================
  3. The most common use for adding custom views to the Wagtail admin is to provide an interface for managing a Django model. The :doc:`/reference/contrib/modeladmin/index` app makes this simple, providing ready-made views for listing, creating and editing objects with minimal configuration.
  4. For other kinds of admin view that don't fit this pattern, you can write your own Django views and register them as part of the Wagtail admin through :ref:`hooks <admin_hooks>`. In this example, we'll implement a view that displays a calendar for the current year, using `the calendar module <https://docs.python.org/3/library/calendar.html>`_ from Python's standard library.
  5. Defining a view
  6. ---------------
  7. Within a Wagtail project, create a new ``wagtailcalendar`` app with ``./manage.py startapp wagtailcalendar`` and add it to your project's ``INSTALLED_APPS``. (In this case we're using the name 'wagtailcalendar' to avoid clashing with the standard library's ``calendar`` module - in general there is no need to use a 'wagtail' prefix.)
  8. Edit ``views.py`` as follows - note that this is a plain Django view with no Wagtail-specific code.
  9. .. code-block:: python
  10. import calendar
  11. from django.http import HttpResponse
  12. from django.utils import timezone
  13. def index(request):
  14. current_year = timezone.now().year
  15. calendar_html = calendar.HTMLCalendar().formatyear(current_year)
  16. return HttpResponse(calendar_html)
  17. Registering a URL route
  18. -----------------------
  19. At this point, the standard practice for a Django project would be to add a URL route for this view to your project's top-level URL config module. However, in this case we want the view to only be available to logged-in users, and to appear within the ``/admin/`` URL namespace which is managed by Wagtail. This is done through the :ref:`register_admin_urls` hook.
  20. On startup, Wagtail looks for a ``wagtail_hooks`` submodule within each installed app. In this submodule, you can define functions to be run at various points in Wagtail's operation, such as building the URL config for the admin, and constructing the main menu.
  21. Create a ``wagtail_hooks.py`` file within the ``wagtailcalendar`` app containing the following:
  22. .. code-block:: python
  23. from django.urls import path
  24. from wagtail.core import hooks
  25. from .views import index
  26. @hooks.register('register_admin_urls')
  27. def register_calendar_url():
  28. return [
  29. path('calendar/', index, name='calendar'),
  30. ]
  31. The calendar will now be visible at the URL ``/admin/calendar/``.
  32. .. figure:: ../_static/images/adminviews_calendar.png
  33. :alt: A calendar, presented in unstyled HTML
  34. Adding a template
  35. -----------------
  36. Currently this view is outputting a plain HTML fragment. Let's insert this into the usual Wagtail admin page furniture, by creating a template that extends Wagtail's base template ``"wagtailadmin/base.html"``.
  37. .. note::
  38. The base template and HTML structure are not considered a stable part of Wagtail's API, and may change in future releases.
  39. Update ``views.py`` as follows:
  40. .. code-block:: python
  41. import calendar
  42. from django.shortcuts import render
  43. from django.utils import timezone
  44. def index(request):
  45. current_year = timezone.now().year
  46. calendar_html = calendar.HTMLCalendar().formatyear(current_year)
  47. return render(request, 'wagtailcalendar/index.html', {
  48. 'current_year': current_year,
  49. 'calendar_html': calendar_html,
  50. })
  51. Now create a ``templates/wagtailcalendar/`` folder within the ``wagtailcalendar`` app, containing ``index.html`` as follows:
  52. .. code-block:: html+django
  53. {% extends "wagtailadmin/base.html" %}
  54. {% block titletag %}{{ current_year }} calendar{% endblock %}
  55. {% block extra_css %}
  56. {{ block.super }}
  57. <style>
  58. table.month {
  59. margin: 20px;
  60. }
  61. table.month td, table.month th {
  62. padding: 5px;
  63. }
  64. </style>
  65. {% endblock %}
  66. {% block content %}
  67. {% include "wagtailadmin/shared/header.html" with title="Calendar" icon="date" %}
  68. <div class="nice-padding">
  69. {{ calendar_html|safe }}
  70. </div>
  71. {% endblock %}
  72. Here we are overriding three of the blocks defined in the base template: ``titletag`` (which sets the content of the HTML ``<title>`` tag), ``extra_css`` (which allows us to provide additional CSS styles specific to this page), and ``content`` (for the main content area of the page). We're also including the standard header bar component, and setting a title and icon. For a list of the recognised icon identifiers, see the :ref:`styleguide`.
  73. Revisiting ``/admin/calendar/`` will now show the calendar within the Wagtail admin page furniture.
  74. .. figure:: ../_static/images/adminviews_calendar_template.png
  75. :alt: A calendar, shown within the Wagtail admin interface
  76. Adding a menu item
  77. ------------------
  78. Our calendar view is now complete, but there's no way to reach it from the rest of the admin backend. To add an item to the sidebar menu, we'll use another hook, :ref:`register_admin_menu_item`. Update ``wagtail_hooks.py`` as follows:
  79. .. code-block:: python
  80. from django.urls import path, reverse
  81. from wagtail.admin.menu import MenuItem
  82. from wagtail.core import hooks
  83. from .views import index
  84. @hooks.register('register_admin_urls')
  85. def register_calendar_url():
  86. return [
  87. path('calendar/', index, name='calendar'),
  88. ]
  89. @hooks.register('register_admin_menu_item')
  90. def register_calendar_menu_item():
  91. return MenuItem('Calendar', reverse('calendar'), icon_name='date')
  92. A 'Calendar' item will now appear in the menu.
  93. .. figure:: ../_static/images/adminviews_menu.png
  94. :alt: Wagtail admin sidebar menu, showing a "Calendar" menu item with a date icon
  95. Adding a group of menu items
  96. ----------------------------
  97. Sometimes you want to group custom views together in a single menu item in the sidebar. Let's create another view to display only the current calendar month:
  98. .. code-block:: python
  99. :emphasize-lines: 13-18
  100. import calendar
  101. from django.http import HttpResponse
  102. from django.utils import timezone
  103. def index(request):
  104. current_year = timezone.now().year
  105. calendar_html = calendar.HTMLCalendar().formatyear(current_year)
  106. return HttpResponse(calendar_html)
  107. def month(request):
  108. current_year = timezone.now().year
  109. current_month = timezone.now().month
  110. calendar_html = calendar.HTMLCalendar().format_month(current_year, current_month)
  111. return HttpResponse(calendar_html)
  112. We also need to update ``wagtail_hooks.py`` to register our URL in the admin interface:
  113. .. code-block:: python
  114. :emphasize-lines: 11
  115. from django.urls import path
  116. from wagtail.core import hooks
  117. from .views import index, month
  118. @hooks.register('register_admin_urls')
  119. def register_calendar_url():
  120. return [
  121. path('calendar/', index, name='calendar'),
  122. path('calendar/month/', month, name='calendar-month'),
  123. ]
  124. The calendar will now be visible at the URL ``/admin/calendar/month/``.
  125. .. figure:: ../_static/images/adminviews_calendarmonth.png
  126. :alt: A single calendar month
  127. Finally we can alter our ``wagtail_hooks.py`` to include a group of custom menu items. This is similar to adding a single item but involves importing two more classes, ``SubMenu`` and ``SubmenuMenuItem``.
  128. .. code-block:: python
  129. :emphasize-lines: 3-4,21-26
  130. from django.urls import path, reverse
  131. from wagtail.admin.menu import MenuItem, SubmenuMenuItem
  132. from wagtail.contrib.modeladmin.menus import SubMenu
  133. from wagtail.core import hooks
  134. from .views import index, month
  135. @hooks.register('register_admin_urls')
  136. def register_calendar_url():
  137. return [
  138. path('calendar/', index, name='calendar'),
  139. path('calendar/month/', month, name='calendar-month'),
  140. ]
  141. @hooks.register('register_admin_menu_item')
  142. def register_calendar_menu_item():
  143. menu_items = [
  144. MenuItem('Calendar', reverse('calendar'), icon_name='date'),
  145. MenuItem('Current month', reverse('calendar-month'), icon_name='date'),
  146. ]
  147. return SubmenuMenuItem('Calendar', SubMenu(menu_items), classnames='icon icon-date')
  148. The 'Calendar' item will now appear as a group of menu items.
  149. .. figure:: ../_static/images/adminviews_menu_group.png
  150. :alt: Wagtail admin sidebar menu, showing a "Calendar" group menu item with a date icon
  151. When expanded, the 'Calendar' item will now show our two custom menu items.
  152. .. figure:: ../_static/images/adminviews_menu_group_expanded.png
  153. :alt: Wagtail admin sidebar 'Calendar' menu expaned, showing two child menu items, 'Calendar' and 'Month'.