Adding reports¶
Reports are views with listings of pages matching a specific query. They can also export these listings in spreadsheet format. They are found in the Reports submenu: by default, the Locked pages report is provided, allowing an overview of locked pages on the site.
It is possible to create your own custom reports in the Wagtail admin. Two base classes are provided:
wagtail.admin.views.reports.ReportView
, which provides basic listing and spreadsheet export functionality, and
wagtail.admin.views.reports.PageReportView
, which additionally provides a default set of fields suitable for page listings.
For this example, we’ll add a report which shows any pages with unpublished changes.
We will register this view using the unpublished_changes_report
name for the URL pattern.
# <project>/views.py
from wagtail.admin.views.reports import PageReportView
class UnpublishedChangesReportView(PageReportView):
index_url_name = "unpublished_changes_report"
index_results_url_name = "unpublished_changes_report_results"
Defining your report¶
The most important attributes and methods to customize to define your report are:
- get_queryset(self)¶
This retrieves the queryset of pages for your report. For our example:
# <project>/views.py
from wagtail.admin.views.reports import PageReportView
from wagtail.models import Page
class UnpublishedChangesReportView(PageReportView):
def get_queryset(self):
return Page.objects.filter(has_unpublished_changes=True)
- template_name¶
(string)
The template used to render your report view, defaults to "wagtailadmin/reports/base_report.html"
.
Note that this template only provides the skeleton of the view, not the listing table itself.
The listing table should be implemented in a separate template specified by results_template_name
(see below), to then be rendered via {% include %}
.
Unless you want to customize the overall view, you will rarely need to change this template.
To customize the listing, change the results_template_name
instead.
Changed in version 6.2: The default template_name
attribute for PageReportView
was changed from "wagtailadmin/reports/base_page_report.html"
to "wagtailadmin/reports/base_report.html"
.
Additionally, customization of the template_name
should generally be replaced with a results_template_name
customization, unless you intend to completely override the view template and not just the listing table.
- results_template_name¶
(string)
The template used to render the listing table.
For ReportView
, this defaults to "wagtailadmin/reports/base_report_results.html"
,
which provides support for using the wagtail.admin.ui.tables
framework.
For PageReportView
, this defaults to "wagtailadmin/reports/base_page_report_results.html"
,
which provides a default table layout based on the explorer views,
displaying action buttons, as well as the title, time of the last update, status, and specific type of any pages.
In this example, we’ll change this to a new template in a later section.
Added in version 6.2: The results_template_name
attribute was added to support updating the listing via AJAX upon filtering and to allow the use of the wagtail.admin.ui.tables
framework.
- page_title¶
(string)
The name of your report, which will be displayed in the header. For our example, we’ll set it to
"Pages with unpublished changes"
.
Changed in version 6.2: The title
attribute was renamed to page_title
.
- header_icon¶
(string)
The name of the icon, using the standard Wagtail icon names. For example, the locked pages view uses "locked"
,
and for our example report, we’ll set it to 'doc-empty-inverse'
.
- index_url_name¶
(string)
The name of the URL pattern registered for the report view.
- index_results_url_name¶
(string)
The name of the URL pattern registered for the results view (the report view with .as_view(results_only=True)
).
Added in version 6.2: The index_results_url_name
attribute was added to support updating the listing via AJAX upon filtering.
Spreadsheet exports¶
- list_export¶
(list)
A list of the fields/attributes for each model which are exported as columns in the spreadsheet view. For ReportView
, this
is empty by default, and for PageReportView
, it corresponds to the listing fields: the title, time of the last update, status,
and specific type of any pages. For our example, we might want to know when the page was last published, so we’ll set
list_export
as follows:
list_export = PageReportView.list_export + ['last_published_at']
- export_headings¶
(dictionary)
A dictionary of any fields/attributes in list_export
for which you wish to manually specify a heading for the spreadsheet
column and their headings. If unspecified, the heading will be taken from the field verbose_name
if applicable, and the
attribute string otherwise. For our example, last_published_at
will automatically get a heading of "Last Published At"
,
but a simple “Last Published” looks neater. We’ll add that by setting export_headings
:
export_headings = dict(last_published_at='Last Published', **PageReportView.export_headings)
- custom_value_preprocess¶
(dictionary)
A dictionary of (value_class_1, value_class_2, ...)
tuples mapping to {export_format: preprocessing_function}
dictionaries,
allowing custom preprocessing functions to be applied when exporting field values of specific classes (or their subclasses). If
unspecified (and ReportView.custom_field_preprocess
also does not specify a function), force_str
will be used. To prevent
preprocessing, set the preprocessing_function to None
.
- custom_field_preprocess¶
(dictionary)
A dictionary of field_name
strings mapping to {export_format: preprocessing_function}
dictionaries,
allowing custom preprocessing functions to be applied when exporting field values of specific classes (or their subclasses). This
will take priority over functions specified in ReportView.custom_value_preprocess
. If unspecified (and
ReportView.custom_value_preprocess
also does not specify a function), force_str
will be used. To prevent
preprocessing, set the preprocessing_function to None
.
Customizing templates¶
For this example “pages with unpublished changes” report, we’ll add an extra column to the listing template, showing the last publication date for each page. To do this, we’ll extend two templates: wagtailadmin/reports/base_page_report_results.html
, and wagtailadmin/reports/listing/_list_page_report.html
.
Changed in version 6.2: Extending wagtailadmin/reports/base_page_report.html
was changed in favor of extending wagtailadmin/reports/base_page_report_results.html
. The listing
and no_results
blocks were renamed to results
and no_results_message
, respectively.
{# <project>/templates/reports/unpublished_changes_report_results.html #}
{% extends 'wagtailadmin/reports/base_page_report_results.html' %}
{% block results %}
{% include 'reports/include/_list_unpublished_changes.html' %}
{% endblock %}
{% block no_results_message %}
<p>No pages with unpublished changes.</p>
{% endblock %}
{# <project>/templates/reports/include/_list_unpublished_changes.html #}
{% extends 'wagtailadmin/reports/listing/_list_page_report.html' %}
{% block extra_columns %}
<th>Last Published</th>
{% endblock %}
{% block extra_page_data %}
<td valign="top">
{{ page.last_published_at }}
</td>
{% endblock %}
Finally, we’ll set UnpublishedChangesReportView.results_template_name
to this new template: 'reports/unpublished_changes_report_results.html'
.
Setting up permission restriction¶
Even with the menu item hidden, it would still be possible for any user to visit the report’s URL directly, and so it is necessary to set up a permission restriction on the report view itself. This can be done by adding a dispatch
method to the existing UnpublishedChangesReportView
view:
# add the below dispatch method to the existing UnpublishedChangesReportView view
def dispatch(self, request, *args, **kwargs):
if not self.request.user.is_superuser:
return permission_denied(request)
return super().dispatch(request, *args, **kwargs)
The full code¶
# <project>/views.py
from wagtail.admin.auth import permission_denied
from wagtail.admin.views.reports import PageReportView
from wagtail.models import Page
class UnpublishedChangesReportView(PageReportView):
index_url_name = "unpublished_changes_report"
index_results_url_name = "unpublished_changes_report_results"
header_icon = 'doc-empty-inverse'
results_template_name = 'reports/unpublished_changes_report_results.html'
page_title = "Pages with unpublished changes"
list_export = PageReportView.list_export + ['last_published_at']
export_headings = dict(last_published_at='Last Published', **PageReportView.export_headings)
def get_queryset(self):
return Page.objects.filter(has_unpublished_changes=True)
def dispatch(self, request, *args, **kwargs):
if not self.request.user.is_superuser:
return permission_denied(request)
return super().dispatch(request, *args, **kwargs)
# <project>/wagtail_hooks.py
from django.urls import path, reverse
from wagtail.admin.menu import AdminOnlyMenuItem
from wagtail import hooks
from .views import UnpublishedChangesReportView
@hooks.register('register_reports_menu_item')
def register_unpublished_changes_report_menu_item():
return AdminOnlyMenuItem("Pages with unpublished changes", reverse('unpublished_changes_report'), icon_name=UnpublishedChangesReportView.header_icon, order=700)
@hooks.register('register_admin_urls')
def register_unpublished_changes_report_url():
return [
path('reports/unpublished-changes/', UnpublishedChangesReportView.as_view(), name='unpublished_changes_report'),
path('reports/unpublished-changes/results/', UnpublishedChangesReportView.as_view(results_only=True), name='unpublished_changes_report_results'),
]
{# <project>/templates/reports/unpublished_changes_report_results.html #}
{% extends 'wagtailadmin/reports/base_page_report_results.html' %}
{% block results %}
{% include 'reports/include/_list_unpublished_changes.html' %}
{% endblock %}
{% block no_results_message %}
<p>No pages with unpublished changes.</p>
{% endblock %}
{# <project>/templates/reports/include/_list_unpublished_changes.html #}
{% extends 'wagtailadmin/reports/listing/_list_page_report.html' %}
{% block extra_columns %}
<th>Last Published</th>
{% endblock %}
{% block extra_page_data %}
<td valign="top">
{{ page.last_published_at }}
</td>
{% endblock %}