Initial commit
This commit is contained in:
1
env/lib/python3.10/site-packages/wagtail/admin/views/reports/__init__.py
vendored
Normal file
1
env/lib/python3.10/site-packages/wagtail/admin/views/reports/__init__.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
from .base import PageReportView, ReportView # noqa: F401
|
||||
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/reports/__pycache__/__init__.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/reports/__pycache__/__init__.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/reports/__pycache__/aging_pages.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/reports/__pycache__/aging_pages.cpython-310.pyc
vendored
Normal file
Binary file not shown.
Binary file not shown.
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/reports/__pycache__/base.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/reports/__pycache__/base.cpython-310.pyc
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/reports/__pycache__/workflows.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/reports/__pycache__/workflows.cpython-310.pyc
vendored
Normal file
Binary file not shown.
115
env/lib/python3.10/site-packages/wagtail/admin/views/reports/aging_pages.py
vendored
Normal file
115
env/lib/python3.10/site-packages/wagtail/admin/views/reports/aging_pages.py
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
import django_filters
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db.models import OuterRef, Subquery
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from wagtail.admin.filters import ContentTypeFilter, WagtailFilterSet
|
||||
from wagtail.admin.widgets import AdminDateInput
|
||||
from wagtail.coreutils import get_content_type_label
|
||||
from wagtail.models import Page, PageLogEntry, get_page_content_types
|
||||
from wagtail.permissions import page_permission_policy
|
||||
from wagtail.users.utils import get_deleted_user_display_name
|
||||
|
||||
from .base import PageReportView
|
||||
|
||||
|
||||
class AgingPagesReportFilterSet(WagtailFilterSet):
|
||||
last_published_at = django_filters.DateTimeFilter(
|
||||
label=_("Last published before"), lookup_expr="lte", widget=AdminDateInput
|
||||
)
|
||||
content_type = ContentTypeFilter(
|
||||
label=_("Type"),
|
||||
queryset=lambda request: get_page_content_types(include_base_page_type=False),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Page
|
||||
fields = ["live", "last_published_at", "content_type"]
|
||||
|
||||
|
||||
class AgingPagesView(PageReportView):
|
||||
results_template_name = "wagtailadmin/reports/aging_pages_results.html"
|
||||
page_title = _("Aging pages")
|
||||
header_icon = "time"
|
||||
filterset_class = AgingPagesReportFilterSet
|
||||
index_url_name = "wagtailadmin_reports:aging_pages"
|
||||
index_results_url_name = "wagtailadmin_reports:aging_pages_results"
|
||||
export_headings = {
|
||||
"status_string": _("Status"),
|
||||
"last_published_at": _("Last published at"),
|
||||
"last_published_by_user": _("Last published by"),
|
||||
"content_type": _("Type"),
|
||||
}
|
||||
list_export = [
|
||||
"title",
|
||||
"status_string",
|
||||
"last_published_at",
|
||||
"last_published_by_user",
|
||||
"content_type",
|
||||
]
|
||||
any_permission_required = ["add", "change", "publish"]
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.user_model = get_user_model()
|
||||
self.custom_field_preprocess = self.custom_field_preprocess.copy()
|
||||
self.custom_field_preprocess["content_type"] = {
|
||||
self.FORMAT_CSV: get_content_type_label,
|
||||
self.FORMAT_XLSX: get_content_type_label,
|
||||
}
|
||||
|
||||
def user_id_to_python(self, user_id):
|
||||
return self.user_model._meta.pk.to_python(user_id)
|
||||
|
||||
def add_last_publisher_name_to_page(self, username_mapping, page):
|
||||
if page.last_published_by:
|
||||
# Giving the last_published_by annotation an explicit output_field type
|
||||
# causes an issue with prefetch_workflow_states when the field is a
|
||||
# ConvertedValueField. If the last user to publish the page has been
|
||||
# deleted, we will render their user id in the template, so we call
|
||||
# to_python on the value so that what's rendered matches the developer's
|
||||
# expectation in the case of complex primary key types (e.g. UUIDField).
|
||||
try:
|
||||
user_id_value = self.user_id_to_python(page.last_published_by)
|
||||
except ValidationError:
|
||||
user_id_value = page.last_published_by
|
||||
|
||||
last_published_by_user = username_mapping.get(
|
||||
user_id_value, get_deleted_user_display_name(user_id=user_id_value)
|
||||
)
|
||||
page.last_published_by_user = last_published_by_user
|
||||
else:
|
||||
page.last_published_by_user = ""
|
||||
|
||||
def decorate_paginated_queryset(self, queryset):
|
||||
user_ids = set(queryset.values_list("last_published_by", flat=True))
|
||||
|
||||
username_mapping = {
|
||||
user.pk: user.get_username()
|
||||
for user in self.user_model.objects.filter(pk__in=user_ids)
|
||||
}
|
||||
for page in queryset:
|
||||
self.add_last_publisher_name_to_page(username_mapping, page)
|
||||
return queryset
|
||||
|
||||
def get_queryset(self):
|
||||
latest_publishing_log = PageLogEntry.objects.filter(
|
||||
page=OuterRef("pk"), action__exact="wagtail.publish"
|
||||
)
|
||||
self.queryset = (
|
||||
page_permission_policy.instances_user_has_permission_for(
|
||||
self.request.user, "publish"
|
||||
)
|
||||
.exclude(last_published_at__isnull=True)
|
||||
.prefetch_workflow_states()
|
||||
.select_related("content_type")
|
||||
.annotate_approved_schedule()
|
||||
.order_by("last_published_at")
|
||||
.annotate(
|
||||
last_published_by=Subquery(latest_publishing_log.values("user")[:1])
|
||||
)
|
||||
)
|
||||
|
||||
return super().get_queryset()
|
||||
188
env/lib/python3.10/site-packages/wagtail/admin/views/reports/audit_logging.py
vendored
Normal file
188
env/lib/python3.10/site-packages/wagtail/admin/views/reports/audit_logging.py
vendored
Normal file
@@ -0,0 +1,188 @@
|
||||
import datetime
|
||||
from collections import defaultdict
|
||||
|
||||
import django_filters
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db.models import IntegerField, Value
|
||||
from django.utils.encoding import force_str
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from wagtail.admin.admin_url_finder import AdminURLFinder
|
||||
from wagtail.admin.filters import (
|
||||
ContentTypeFilter,
|
||||
)
|
||||
from wagtail.admin.views.pages.history import PageHistoryFilterSet
|
||||
from wagtail.coreutils import get_content_type_label
|
||||
from wagtail.log_actions import registry as log_action_registry
|
||||
from wagtail.models import PageLogEntry
|
||||
|
||||
from .base import ReportView
|
||||
|
||||
|
||||
def get_users_for_filter(user):
|
||||
user_ids = set()
|
||||
for log_model in log_action_registry.get_log_entry_models():
|
||||
user_ids.update(log_model.objects.viewable_by_user(user).get_user_ids())
|
||||
|
||||
User = get_user_model()
|
||||
return User.objects.filter(pk__in=user_ids).order_by(User.USERNAME_FIELD)
|
||||
|
||||
|
||||
def get_content_types_for_filter(user):
|
||||
content_type_ids = set()
|
||||
for log_model in log_action_registry.get_log_entry_models():
|
||||
content_type_ids.update(
|
||||
log_model.objects.viewable_by_user(user).get_content_type_ids()
|
||||
)
|
||||
|
||||
return ContentType.objects.filter(pk__in=content_type_ids).order_by("model")
|
||||
|
||||
|
||||
def get_actions_for_filter(user):
|
||||
# Only return those actions used by log entries visible to the user.
|
||||
actions = set()
|
||||
for log_model in log_action_registry.get_log_entry_models():
|
||||
actions.update(log_model.objects.viewable_by_user(user).get_actions())
|
||||
|
||||
return [
|
||||
action for action in log_action_registry.get_choices() if action[0] in actions
|
||||
]
|
||||
|
||||
|
||||
class SiteHistoryReportFilterSet(PageHistoryFilterSet):
|
||||
label = django_filters.CharFilter(label=_("Name"), lookup_expr="icontains")
|
||||
object_type = ContentTypeFilter(
|
||||
label=_("Type"),
|
||||
method="filter_object_type",
|
||||
queryset=lambda request: get_content_types_for_filter(request.user),
|
||||
)
|
||||
|
||||
def filter_object_type(self, queryset, name, value):
|
||||
return queryset.filter_on_content_type(value)
|
||||
|
||||
def get_action_choices(self):
|
||||
return get_actions_for_filter(self.request.user)
|
||||
|
||||
def get_user_choices(self):
|
||||
return get_users_for_filter(self.request.user)
|
||||
|
||||
class Meta:
|
||||
model = PageLogEntry
|
||||
fields = []
|
||||
|
||||
|
||||
class LogEntriesView(ReportView):
|
||||
results_template_name = "wagtailadmin/reports/site_history_results.html"
|
||||
page_title = _("Site history")
|
||||
header_icon = "history"
|
||||
filterset_class = SiteHistoryReportFilterSet
|
||||
index_url_name = "wagtailadmin_reports:site_history"
|
||||
index_results_url_name = "wagtailadmin_reports:site_history_results"
|
||||
|
||||
export_headings = {
|
||||
"object_id": _("ID"),
|
||||
"label": _("Name"),
|
||||
"content_type": _("Type"),
|
||||
"action": _("Action type"),
|
||||
"user_display_name": _("User"),
|
||||
"timestamp": _("Date/Time"),
|
||||
}
|
||||
list_export = [
|
||||
"object_id",
|
||||
"label",
|
||||
"content_type",
|
||||
"action",
|
||||
"user_display_name",
|
||||
"timestamp",
|
||||
]
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.custom_field_preprocess = self.custom_field_preprocess.copy()
|
||||
self.custom_field_preprocess["action"] = {
|
||||
self.FORMAT_CSV: self.get_action_label,
|
||||
self.FORMAT_XLSX: self.get_action_label,
|
||||
}
|
||||
self.custom_field_preprocess["content_type"] = {
|
||||
self.FORMAT_CSV: get_content_type_label,
|
||||
self.FORMAT_XLSX: get_content_type_label,
|
||||
}
|
||||
|
||||
def get_filename(self):
|
||||
return "audit-log-{}".format(datetime.datetime.today().strftime("%Y-%m-%d"))
|
||||
|
||||
def get_filtered_queryset(self):
|
||||
"""
|
||||
Since this report combines records from multiple log models, the standard pattern of
|
||||
returning a queryset from get_queryset() to be filtered by filter_queryset() is not
|
||||
possible - the subquery for each log model must be filtered separately before joining
|
||||
with union().
|
||||
|
||||
Additionally, a union() on standard model-based querysets will return a queryset based on
|
||||
the first model in the union, so instances of the other model(s) would be returned as the
|
||||
wrong type. To avoid this, we construct values() querysets as follows:
|
||||
|
||||
1. For each model, construct a values() queryset consisting of id, timestamp and an
|
||||
annotation to indicate which model it is, and filter this with filter_queryset
|
||||
2. Form a union() queryset from these queries, and order it by -timestamp
|
||||
(this is the result returned from get_filtered_queryset)
|
||||
3. Apply pagination (done in MultipleObjectMixin.get_context_data)
|
||||
4. (In decorate_paginated_queryset:) For each model included in the result set, look up
|
||||
the set of model instances by ID. Use these to form a final list of model instances
|
||||
in the same order as the query.
|
||||
"""
|
||||
queryset = None
|
||||
|
||||
# Retrieve the set of registered log models, and cast it to a list so that we assign
|
||||
# an index number to each one; this index number will be used to distinguish models
|
||||
# in the combined results
|
||||
self.log_models = list(log_action_registry.get_log_entry_models())
|
||||
|
||||
for log_model_index, log_model in enumerate(self.log_models):
|
||||
sub_queryset = (
|
||||
log_model.objects.viewable_by_user(self.request.user)
|
||||
.values("pk", "timestamp")
|
||||
.annotate(
|
||||
log_model_index=Value(log_model_index, output_field=IntegerField())
|
||||
)
|
||||
)
|
||||
sub_queryset = self.filter_queryset(sub_queryset)
|
||||
# disable any native ordering on the queryset; we will re-apply it on the combined result
|
||||
sub_queryset = sub_queryset.order_by()
|
||||
if queryset is None:
|
||||
queryset = sub_queryset
|
||||
else:
|
||||
queryset = queryset.union(sub_queryset)
|
||||
|
||||
return queryset.order_by("-timestamp")
|
||||
|
||||
def decorate_paginated_queryset(self, queryset):
|
||||
# build lists of ids from queryset, grouped by log model index
|
||||
pks_by_log_model_index = defaultdict(list)
|
||||
for row in queryset:
|
||||
pks_by_log_model_index[row["log_model_index"]].append(row["pk"])
|
||||
|
||||
url_finder = AdminURLFinder(self.request.user)
|
||||
|
||||
# for each log model found in the queryset, look up the set of log entries by id
|
||||
# and build a lookup table
|
||||
object_lookup = {}
|
||||
for log_model_index, pks in pks_by_log_model_index.items():
|
||||
log_entries = (
|
||||
self.log_models[log_model_index]
|
||||
.objects.prefetch_related("user__wagtail_userprofile", "content_type")
|
||||
.filter(pk__in=pks)
|
||||
.with_instances()
|
||||
)
|
||||
for log_entry, instance in log_entries:
|
||||
# annotate log entry with an 'edit_url' property
|
||||
log_entry.edit_url = url_finder.get_edit_url(instance)
|
||||
object_lookup[(log_model_index, log_entry.pk)] = log_entry
|
||||
|
||||
# return items from our lookup table in the order of the original queryset
|
||||
return [object_lookup[(row["log_model_index"], row["pk"])] for row in queryset]
|
||||
|
||||
def get_action_label(self, action):
|
||||
return force_str(log_action_registry.get_action_label(action))
|
||||
81
env/lib/python3.10/site-packages/wagtail/admin/views/reports/base.py
vendored
Normal file
81
env/lib/python3.10/site-packages/wagtail/admin/views/reports/base.py
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
from warnings import warn
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from wagtail.admin.views.generic import BaseListingView, PermissionCheckedMixin
|
||||
from wagtail.admin.views.mixins import SpreadsheetExportMixin
|
||||
from wagtail.permissions import page_permission_policy
|
||||
from wagtail.utils.deprecation import RemovedInWagtail70Warning
|
||||
|
||||
|
||||
class ReportView(SpreadsheetExportMixin, PermissionCheckedMixin, BaseListingView):
|
||||
template_name = "wagtailadmin/reports/base_report.html"
|
||||
results_template_name = "wagtailadmin/reports/base_report_results.html"
|
||||
title = ""
|
||||
paginate_by = 50
|
||||
_show_breadcrumbs = True
|
||||
|
||||
def get_breadcrumbs_items(self):
|
||||
return super().get_breadcrumbs_items() + [
|
||||
{"url": "", "label": self.get_page_title()}
|
||||
]
|
||||
|
||||
def get_page_title(self):
|
||||
if self.page_title:
|
||||
return self.page_title
|
||||
# WagtailAdminTemplateMixin uses `page_title`, but the documented approach
|
||||
# for ReportView used `title`, so we need to support both during the
|
||||
# deprecation period. When `title` is removed, this and the `get_context_data`
|
||||
# overrides can be removed.
|
||||
warn(
|
||||
f"The `title` attribute in `{self.__class__.__name__}` (a `ReportView` subclass) "
|
||||
"is deprecated. Use `page_title` instead.",
|
||||
RemovedInWagtail70Warning,
|
||||
)
|
||||
return self.title
|
||||
|
||||
def get_filtered_queryset(self):
|
||||
return self.filter_queryset(self.get_queryset())
|
||||
|
||||
def decorate_paginated_queryset(self, object_list):
|
||||
# A hook point to allow rewriting the object list after pagination has been applied
|
||||
return object_list
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.object_list = self.get_filtered_queryset()
|
||||
context = self.get_context_data()
|
||||
# Decorate the queryset *after* Django's BaseListView has returned a paginated/reduced
|
||||
# list of objects
|
||||
context["object_list"] = self.decorate_paginated_queryset(
|
||||
context["object_list"]
|
||||
)
|
||||
return self.render_to_response(context)
|
||||
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
context = super().get_context_data(*args, **kwargs)
|
||||
context["title"] = self.get_page_title()
|
||||
return context
|
||||
|
||||
def render_to_response(self, context, **response_kwargs):
|
||||
if self.is_export:
|
||||
return self.as_spreadsheet(
|
||||
context["object_list"], self.request.GET.get("export")
|
||||
)
|
||||
return super().render_to_response(context, **response_kwargs)
|
||||
|
||||
|
||||
class PageReportView(ReportView):
|
||||
results_template_name = "wagtailadmin/reports/base_page_report_results.html"
|
||||
export_headings = {
|
||||
"latest_revision_created_at": _("Updated"),
|
||||
"status_string": _("Status"),
|
||||
"content_type.model_class._meta.verbose_name.title": _("Type"),
|
||||
}
|
||||
list_export = [
|
||||
"title",
|
||||
"latest_revision_created_at",
|
||||
"status_string",
|
||||
"content_type.model_class._meta.verbose_name.title",
|
||||
]
|
||||
context_object_name = "pages"
|
||||
permission_policy = page_permission_policy
|
||||
69
env/lib/python3.10/site-packages/wagtail/admin/views/reports/locked_pages.py
vendored
Normal file
69
env/lib/python3.10/site-packages/wagtail/admin/views/reports/locked_pages.py
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
import datetime
|
||||
|
||||
import django_filters
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from wagtail.admin.filters import DateRangePickerWidget, WagtailFilterSet
|
||||
from wagtail.models import Page
|
||||
from wagtail.permissions import page_permission_policy
|
||||
|
||||
from .base import PageReportView
|
||||
|
||||
|
||||
def get_users_for_filter():
|
||||
User = get_user_model()
|
||||
return (
|
||||
User.objects.filter(locked_pages__isnull=False)
|
||||
.order_by(User.USERNAME_FIELD)
|
||||
.distinct()
|
||||
)
|
||||
|
||||
|
||||
class LockedPagesReportFilterSet(WagtailFilterSet):
|
||||
locked_at = django_filters.DateFromToRangeFilter(widget=DateRangePickerWidget)
|
||||
locked_by = django_filters.ModelChoiceFilter(
|
||||
field_name="locked_by", queryset=lambda request: get_users_for_filter()
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Page
|
||||
fields = ["locked_by", "locked_at", "live"]
|
||||
|
||||
|
||||
class LockedPagesView(PageReportView):
|
||||
results_template_name = "wagtailadmin/reports/locked_pages_results.html"
|
||||
page_title = _("Locked pages")
|
||||
header_icon = "lock"
|
||||
list_export = PageReportView.list_export + [
|
||||
"locked_at",
|
||||
"locked_by",
|
||||
]
|
||||
filterset_class = LockedPagesReportFilterSet
|
||||
index_url_name = "wagtailadmin_reports:locked_pages"
|
||||
index_results_url_name = "wagtailadmin_reports:locked_pages_results"
|
||||
permission_required = "unlock"
|
||||
|
||||
def get_filename(self):
|
||||
return "locked-pages-report-{}".format(
|
||||
datetime.datetime.today().strftime("%Y-%m-%d")
|
||||
)
|
||||
|
||||
def get_queryset(self):
|
||||
pages = (
|
||||
(
|
||||
page_permission_policy.instances_user_has_permission_for(
|
||||
self.request.user, "change"
|
||||
)
|
||||
| Page.objects.filter(locked_by=self.request.user)
|
||||
)
|
||||
.filter(locked=True)
|
||||
.specific(defer=True)
|
||||
)
|
||||
|
||||
if getattr(settings, "WAGTAIL_I18N_ENABLED", False):
|
||||
pages = pages.select_related("locale")
|
||||
|
||||
self.queryset = pages
|
||||
return super().get_queryset()
|
||||
148
env/lib/python3.10/site-packages/wagtail/admin/views/reports/page_types_usage.py
vendored
Normal file
148
env/lib/python3.10/site-packages/wagtail/admin/views/reports/page_types_usage.py
vendored
Normal file
@@ -0,0 +1,148 @@
|
||||
import django_filters
|
||||
from django.conf import settings
|
||||
from django.db.models import Count, F, OuterRef, Q, Subquery
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from wagtail.admin.filters import WagtailFilterSet
|
||||
from wagtail.admin.views.reports import ReportView
|
||||
from wagtail.coreutils import get_content_languages
|
||||
from wagtail.models import ContentType, Page, Site, get_page_models
|
||||
from wagtail.permissions import page_permission_policy
|
||||
|
||||
|
||||
def _get_locale_choices():
|
||||
return list(get_content_languages().items())
|
||||
|
||||
|
||||
def _get_site_choices():
|
||||
"""Tuples of (site root page path, site display name) for all sites in project."""
|
||||
choices = [
|
||||
(site.root_page.path, str(site))
|
||||
for site in Site.objects.all().select_related("root_page")
|
||||
]
|
||||
return choices
|
||||
|
||||
|
||||
def _annotate_last_edit_info(queryset, language_code, site_root_path):
|
||||
latest_edited_page_filter_kwargs = {}
|
||||
page_count_filter_kwargs = {}
|
||||
if language_code:
|
||||
latest_edited_page_filter_kwargs["locale__language_code"] = language_code
|
||||
page_count_filter_kwargs["pages__locale__language_code"] = language_code
|
||||
if site_root_path:
|
||||
latest_edited_page_filter_kwargs["path__startswith"] = site_root_path
|
||||
page_count_filter_kwargs["pages__path__startswith"] = site_root_path
|
||||
|
||||
latest_edited_page = Page.objects.filter(
|
||||
content_type=OuterRef("pk"), **latest_edited_page_filter_kwargs
|
||||
).order_by(F("latest_revision_created_at").desc(nulls_last=True), "title", "-pk")
|
||||
|
||||
queryset = queryset.annotate(
|
||||
count=Count("pages", filter=Q(**page_count_filter_kwargs)),
|
||||
last_edited_page_id=Subquery(latest_edited_page.values("pk")[:1]),
|
||||
)
|
||||
|
||||
return queryset
|
||||
|
||||
|
||||
class LocaleFilter(django_filters.ChoiceFilter):
|
||||
def filter(self, qs, language_code):
|
||||
if language_code:
|
||||
return qs.filter(pages__locale__language_code=language_code)
|
||||
return qs
|
||||
|
||||
|
||||
class SiteFilter(django_filters.ChoiceFilter):
|
||||
def filter(self, qs, site_root_path):
|
||||
# Value passed will be the site root page path
|
||||
# To check if a page is in a site, we check if the page path starts with the
|
||||
# site's root page path
|
||||
if site_root_path:
|
||||
return qs.filter(pages__path__startswith=site_root_path)
|
||||
return qs
|
||||
|
||||
|
||||
class PageTypesUsageReportFilterSet(WagtailFilterSet):
|
||||
page_locale = LocaleFilter(
|
||||
label=_("Locale"),
|
||||
choices=_get_locale_choices,
|
||||
empty_label=None,
|
||||
null_label=_("All"),
|
||||
null_value=None,
|
||||
)
|
||||
site = SiteFilter(
|
||||
label=_("Site"),
|
||||
choices=_get_site_choices,
|
||||
empty_label=None,
|
||||
null_label=_("All"),
|
||||
null_value=None,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = ContentType
|
||||
fields = ["page_locale", "site"]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.sites = {
|
||||
site.pk: site for site in Site.objects.all().prefetch_related("root_page")
|
||||
}
|
||||
self.sites_filter_enabled = True
|
||||
if len(self.sites) == 1:
|
||||
# If there is only one site, we don't need to show the site filter
|
||||
self.sites_filter_enabled = False
|
||||
del self.filters["site"]
|
||||
|
||||
|
||||
class PageTypesUsageReportView(ReportView):
|
||||
results_template_name = "wagtailadmin/reports/page_types_usage_results.html"
|
||||
page_title = _("Page types usage")
|
||||
header_icon = "doc-empty-inverse"
|
||||
filterset_class = PageTypesUsageReportFilterSet
|
||||
index_url_name = "wagtailadmin_reports:page_types_usage"
|
||||
index_results_url_name = "wagtailadmin_reports:page_types_usage_results"
|
||||
permission_policy = page_permission_policy
|
||||
any_permission_required = ["add", "change", "publish"]
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.page_models = [model.__name__.lower() for model in get_page_models()]
|
||||
self.i18n_enabled = getattr(settings, "WAGTAIL_I18N_ENABLED", False)
|
||||
|
||||
def decorate_paginated_queryset(self, page_types):
|
||||
pages_mapping = Page.objects.specific().in_bulk(
|
||||
obj.last_edited_page_id for obj in page_types if obj.last_edited_page_id
|
||||
)
|
||||
|
||||
for item in page_types:
|
||||
item.last_edited_page = (
|
||||
pages_mapping[item.last_edited_page_id]
|
||||
if item.last_edited_page_id
|
||||
else None
|
||||
)
|
||||
|
||||
return page_types
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = ContentType.objects.filter(model__in=self.page_models)
|
||||
|
||||
page_content_type = ContentType.objects.get_for_model(Page)
|
||||
has_pages = Page.objects.filter(
|
||||
depth__gt=1, content_type=page_content_type
|
||||
).exists()
|
||||
if not has_pages:
|
||||
# If there are no `wagtailcore.Page` pages, we don't need to
|
||||
# show it in the report
|
||||
queryset = queryset.exclude(id=page_content_type.id)
|
||||
|
||||
self.queryset = queryset
|
||||
|
||||
queryset = self.filter_queryset(queryset)
|
||||
|
||||
language_code = self.filters.form.cleaned_data.get("page_locale", None)
|
||||
site_root_path = self.filters.form.cleaned_data.get("site", None)
|
||||
queryset = _annotate_last_edit_info(queryset, language_code, site_root_path)
|
||||
|
||||
queryset = queryset.order_by("-count", "app_label", "model")
|
||||
|
||||
return queryset
|
||||
300
env/lib/python3.10/site-packages/wagtail/admin/views/reports/workflows.py
vendored
Normal file
300
env/lib/python3.10/site-packages/wagtail/admin/views/reports/workflows.py
vendored
Normal file
@@ -0,0 +1,300 @@
|
||||
import datetime
|
||||
|
||||
import django_filters
|
||||
from django import forms
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db.models import CharField, Q
|
||||
from django.db.models.functions import Cast
|
||||
from django.urls import reverse
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import gettext
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from wagtail.admin.filters import (
|
||||
DateRangePickerWidget,
|
||||
FilteredModelChoiceFilter,
|
||||
WagtailFilterSet,
|
||||
)
|
||||
from wagtail.admin.utils import get_latest_str
|
||||
from wagtail.admin.widgets.button import HeaderButton
|
||||
from wagtail.coreutils import get_content_type_label
|
||||
from wagtail.models import (
|
||||
Task,
|
||||
TaskState,
|
||||
Workflow,
|
||||
WorkflowState,
|
||||
get_default_page_content_type,
|
||||
)
|
||||
from wagtail.permissions import page_permission_policy
|
||||
from wagtail.snippets.models import get_editable_models
|
||||
|
||||
from .base import ReportView
|
||||
|
||||
|
||||
def get_requested_by_queryset(request):
|
||||
User = get_user_model()
|
||||
return User.objects.filter(
|
||||
pk__in=set(WorkflowState.objects.values_list("requested_by__pk", flat=True))
|
||||
).order_by(User.USERNAME_FIELD)
|
||||
|
||||
|
||||
def get_editable_page_ids_query(request):
|
||||
pages = page_permission_policy.instances_user_has_permission_for(
|
||||
request.user, "change"
|
||||
)
|
||||
# Need to cast the page ids to string because Postgres doesn't support
|
||||
# implicit type casts when querying on GenericRelations
|
||||
# https://code.djangoproject.com/ticket/16055
|
||||
# Once the issue is resolved, we can remove this function
|
||||
# and change the query to page__in=pages
|
||||
return pages.values_list(Cast("id", output_field=CharField()), flat=True)
|
||||
|
||||
|
||||
def get_editable_content_type_ids(request):
|
||||
editable_models = get_editable_models(request.user)
|
||||
return [
|
||||
ct.id for ct in ContentType.objects.get_for_models(*editable_models).values()
|
||||
]
|
||||
|
||||
|
||||
class WorkflowReportFilterSet(WagtailFilterSet):
|
||||
created_at = django_filters.DateFromToRangeFilter(
|
||||
label=_("Started at"), widget=DateRangePickerWidget
|
||||
)
|
||||
reviewable = django_filters.ChoiceFilter(
|
||||
label=_("Show"),
|
||||
method="filter_reviewable",
|
||||
choices=(("true", _("Awaiting my review")),),
|
||||
empty_label=_("All"),
|
||||
widget=forms.RadioSelect,
|
||||
)
|
||||
requested_by = django_filters.ModelChoiceFilter(
|
||||
field_name="requested_by", queryset=get_requested_by_queryset
|
||||
)
|
||||
|
||||
def filter_reviewable(self, queryset, name, value):
|
||||
if value and self.request and self.request.user:
|
||||
queryset = queryset.filter(
|
||||
current_task_state__in=TaskState.objects.reviewable_by(
|
||||
self.request.user
|
||||
)
|
||||
)
|
||||
return queryset
|
||||
|
||||
class Meta:
|
||||
model = WorkflowState
|
||||
fields = ["reviewable", "workflow", "status", "requested_by", "created_at"]
|
||||
|
||||
|
||||
class WorkflowTasksReportFilterSet(WagtailFilterSet):
|
||||
started_at = django_filters.DateFromToRangeFilter(
|
||||
label=_("Started at"), widget=DateRangePickerWidget
|
||||
)
|
||||
finished_at = django_filters.DateFromToRangeFilter(
|
||||
label=_("Completed at"), widget=DateRangePickerWidget
|
||||
)
|
||||
workflow = django_filters.ModelChoiceFilter(
|
||||
field_name="workflow_state__workflow",
|
||||
queryset=Workflow.objects.all(),
|
||||
label=_("Workflow"),
|
||||
)
|
||||
|
||||
# When a workflow is chosen in the 'id_workflow' selector, filter this list of tasks
|
||||
# to just the ones whose workflows attribute includes the selected workflow.
|
||||
task = FilteredModelChoiceFilter(
|
||||
queryset=Task.objects.all(),
|
||||
filter_field="id_workflow",
|
||||
filter_accessor="workflows",
|
||||
)
|
||||
|
||||
reviewable = django_filters.ChoiceFilter(
|
||||
label=_("Show"),
|
||||
method="filter_reviewable",
|
||||
choices=(("true", _("Awaiting my review")),),
|
||||
empty_label=_("All"),
|
||||
widget=forms.RadioSelect,
|
||||
)
|
||||
|
||||
def filter_reviewable(self, queryset, name, value):
|
||||
if value and self.request and self.request.user:
|
||||
queryset = queryset.filter(
|
||||
id__in=TaskState.objects.reviewable_by(self.request.user).values_list(
|
||||
"id", flat=True
|
||||
)
|
||||
)
|
||||
return queryset
|
||||
|
||||
class Meta:
|
||||
model = TaskState
|
||||
fields = [
|
||||
"reviewable",
|
||||
"workflow",
|
||||
"task",
|
||||
"status",
|
||||
"started_at",
|
||||
"finished_at",
|
||||
]
|
||||
|
||||
|
||||
class WorkflowView(ReportView):
|
||||
results_template_name = "wagtailadmin/reports/workflow_results.html"
|
||||
page_title = _("Workflows")
|
||||
header_icon = "tasks"
|
||||
filterset_class = WorkflowReportFilterSet
|
||||
index_url_name = "wagtailadmin_reports:workflow"
|
||||
index_results_url_name = "wagtailadmin_reports:workflow_results"
|
||||
permission_policy = page_permission_policy
|
||||
any_permission_required = ["add", "change", "publish"]
|
||||
|
||||
export_headings = {
|
||||
"content_object.pk": _("Page/Snippet ID"),
|
||||
"content_type": _("Page/Snippet Type"),
|
||||
"content_object": _("Page/Snippet Title"),
|
||||
"get_status_display": _("Status"),
|
||||
"created_at": _("Started at"),
|
||||
}
|
||||
list_export = [
|
||||
"workflow",
|
||||
"content_object.pk",
|
||||
"content_type",
|
||||
"content_object",
|
||||
"get_status_display",
|
||||
"requested_by",
|
||||
"created_at",
|
||||
]
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.custom_field_preprocess = self.custom_field_preprocess.copy()
|
||||
self.custom_field_preprocess["content_object"] = {
|
||||
self.FORMAT_CSV: self.get_title,
|
||||
self.FORMAT_XLSX: self.get_title,
|
||||
}
|
||||
self.custom_field_preprocess["content_type"] = {
|
||||
self.FORMAT_CSV: get_content_type_label,
|
||||
self.FORMAT_XLSX: get_content_type_label,
|
||||
}
|
||||
|
||||
@cached_property
|
||||
def header_buttons(self):
|
||||
return [
|
||||
HeaderButton(
|
||||
gettext("By task"),
|
||||
reverse("wagtailadmin_reports:workflow_tasks"),
|
||||
icon_name="thumbtack",
|
||||
)
|
||||
]
|
||||
|
||||
def get_title(self, content_object):
|
||||
return get_latest_str(content_object)
|
||||
|
||||
def get_filename(self):
|
||||
return "workflow-report-{}".format(
|
||||
datetime.datetime.today().strftime("%Y-%m-%d")
|
||||
)
|
||||
|
||||
def get_queryset(self):
|
||||
editable_pages = Q(
|
||||
base_content_type_id=get_default_page_content_type().id,
|
||||
object_id__in=get_editable_page_ids_query(self.request),
|
||||
)
|
||||
|
||||
editable_objects = Q(
|
||||
content_type_id__in=get_editable_content_type_ids(self.request)
|
||||
)
|
||||
|
||||
return (
|
||||
WorkflowState.objects.filter(editable_pages | editable_objects)
|
||||
.select_related("workflow", "requested_by")
|
||||
.prefetch_related("content_object", "content_object__latest_revision")
|
||||
.order_by("-created_at")
|
||||
)
|
||||
|
||||
def decorate_paginated_queryset(self, object_list):
|
||||
return [obj for obj in object_list if obj.content_object]
|
||||
|
||||
|
||||
class WorkflowTasksView(ReportView):
|
||||
results_template_name = "wagtailadmin/reports/workflow_tasks_results.html"
|
||||
page_title = _("Workflow tasks")
|
||||
header_icon = "thumbtack"
|
||||
filterset_class = WorkflowTasksReportFilterSet
|
||||
index_url_name = "wagtailadmin_reports:workflow_tasks"
|
||||
index_results_url_name = "wagtailadmin_reports:workflow_tasks_results"
|
||||
permission_policy = page_permission_policy
|
||||
any_permission_required = ["add", "change", "publish"]
|
||||
|
||||
export_headings = {
|
||||
"workflow_state.content_object.pk": _("Page/Snippet ID"),
|
||||
"workflow_state.content_type": _("Page/Snippet Type"),
|
||||
"workflow_state.content_object.__str__": _("Page/Snippet Title"),
|
||||
"get_status_display": _("Status"),
|
||||
"workflow_state.requested_by": _("Requested By"),
|
||||
}
|
||||
list_export = [
|
||||
"task",
|
||||
"workflow_state.content_object.pk",
|
||||
"workflow_state.content_type",
|
||||
"workflow_state.content_object.__str__",
|
||||
"get_status_display",
|
||||
"workflow_state.requested_by",
|
||||
"started_at",
|
||||
"finished_at",
|
||||
"finished_by",
|
||||
]
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.custom_field_preprocess = self.custom_field_preprocess.copy()
|
||||
self.custom_field_preprocess["workflow_state.content_object"] = {
|
||||
self.FORMAT_CSV: self.get_title,
|
||||
self.FORMAT_XLSX: self.get_title,
|
||||
}
|
||||
self.custom_field_preprocess["workflow_state.content_type"] = {
|
||||
self.FORMAT_CSV: get_content_type_label,
|
||||
self.FORMAT_XLSX: get_content_type_label,
|
||||
}
|
||||
|
||||
@cached_property
|
||||
def header_buttons(self):
|
||||
return [
|
||||
HeaderButton(
|
||||
gettext("By workflow"),
|
||||
reverse("wagtailadmin_reports:workflow"),
|
||||
icon_name="tasks",
|
||||
)
|
||||
]
|
||||
|
||||
def get_title(self, content_object):
|
||||
return get_latest_str(content_object)
|
||||
|
||||
def get_filename(self):
|
||||
return "workflow-tasks-{}".format(
|
||||
datetime.datetime.today().strftime("%Y-%m-%d")
|
||||
)
|
||||
|
||||
def get_queryset(self):
|
||||
editable_pages = Q(
|
||||
workflow_state__base_content_type_id=get_default_page_content_type().id,
|
||||
workflow_state__object_id__in=get_editable_page_ids_query(self.request),
|
||||
)
|
||||
editable_objects = Q(
|
||||
workflow_state__content_type_id__in=get_editable_content_type_ids(
|
||||
self.request
|
||||
)
|
||||
)
|
||||
return (
|
||||
TaskState.objects.filter(editable_pages | editable_objects)
|
||||
.select_related("workflow_state", "task")
|
||||
.prefetch_related(
|
||||
"workflow_state__content_object",
|
||||
"workflow_state__content_object__latest_revision",
|
||||
)
|
||||
.order_by("-started_at")
|
||||
)
|
||||
|
||||
def decorate_paginated_queryset(self, object_list):
|
||||
return [obj for obj in object_list if obj.workflow_state.content_object]
|
||||
Reference in New Issue
Block a user