Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add OR-based super-search and set review date bulk action #8726

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions cfgov/v1/wagtail_hooks.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
import re
from datetime import date

from django.conf import settings
from django.shortcuts import redirect
Expand All @@ -9,6 +10,9 @@
from wagtail import hooks
from wagtail.admin import messages, widgets
from wagtail.admin.menu import MenuItem
from wagtail.admin.views.pages.bulk_actions.page_bulk_action import (
PageBulkAction,
)
from wagtail.snippets.models import register_snippet

from v1.admin_views import (
Expand Down Expand Up @@ -47,6 +51,54 @@
languages = dict(settings.LANGUAGES)


@hooks.register("register_bulk_action")
class ReviewDateBulkAction(PageBulkAction):
display_name = "Set review date"
aria_label = "Set review date on selected pages"
action_type = "set-review-date"
action_priority = 30
template_name = "wagtailadmin_overrides/confirmation.html"

def __init__(self, request, model):
super().__init__(request, model)
r = request.GET
next_url = r.get("next")
q = f"?q={r.get('q')}"
ct = ""
if r.get("content_type"):
ct = f"&content_type={r.get('content_type')}"

self.next_url = next_url + q + ct

def check_perm(self, page):
return page.permissions_for_user(self.request.user).can_edit()

def get_success_message(self, updated, failed):
suffix = f". {failed} failed to update." if failed else ""
if updated == 1:
return "1 page updated" + suffix
return f"{updated} pages updated" + suffix

@classmethod
def execute_action(cls, objects, user=None, **kwargs):
today = date.today()
count = 0
failed = 0
for page in objects:
try:
sp = page.specific
logger.warning(
f"Updating last reviewed date on {sp.last_edited}"
)
sp.last_edited = today
sp.save_revision(user=user).publish()
count += 1
except AttributeError:
failed += 1
pass
return count, failed


@hooks.register("register_page_header_buttons")
def page_header_buttons(page, user, view_name, next_url=None):
return [
Expand Down
198 changes: 198 additions & 0 deletions cfgov/wagtailadmin_overrides/search.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
from typing import Any, Dict

from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.db.models.query import QuerySet
from django.http import Http404
from django.utils.translation import gettext_lazy as _

from wagtail.admin.forms.search import SearchForm
from wagtail.admin.ui.tables import Column, DateColumn
from wagtail.admin.ui.tables.pages import (
BulkActionsColumn,
NavigateToChildrenColumn,
PageStatusColumn,
PageTable,
PageTitleColumn,
ParentPageColumn,
)
from wagtail.admin.views.generic.base import BaseListingView
from wagtail.admin.views.generic.permissions import PermissionCheckedMixin
from wagtail.models import Page
from wagtail.permissions import page_permission_policy
from wagtail.search.query import MATCH_ALL
from wagtail.search.utils import parse_query_string


def page_filter_search(q, pages, all_pages=None, ordering=None):
# Parse query
filters, query = parse_query_string(q, operator="or", zero_terms=MATCH_ALL)

# Live filter
live_filter = filters.get("live") or filters.get("published")
live_filter = live_filter and live_filter.lower()

if live_filter in ["yes", "true"]:
if all_pages is not None:
all_pages = all_pages.filter(live=True)
pages = pages.filter(live=True)
elif live_filter in ["no", "false"]:
if all_pages is not None:
all_pages = all_pages.filter(live=False)
pages = pages.filter(live=False)

# Search
if all_pages is not None:
all_pages = all_pages.autocomplete(
query, order_by_relevance=not ordering
)
pages = pages.autocomplete(query, order_by_relevance=not ordering)

return pages, all_pages


class BaseSearchView(PermissionCheckedMixin, BaseListingView):
permission_policy = page_permission_policy
any_permission_required = {
"add",
"change",
"publish",
"bulk_delete",
"lock",
"unlock",
}
paginate_by = 20
page_kwarg = "p"
context_object_name = "pages"
table_class = PageTable
index_url_name = "super-search:search"

columns = [
BulkActionsColumn("bulk_actions"),
PageTitleColumn(
"title",
classname="title",
label=_("Title"),
sort_key="title",
),
ParentPageColumn("parent", label=_("Parent")),
DateColumn(
"latest_revision_created_at",
label=_("Updated"),
sort_key="latest_revision_created_at",
width="12%",
),
Column(
"type",
label=_("Type"),
accessor="page_type_display_name",
width="12%",
),
PageStatusColumn(
"status",
label=_("Status"),
sort_key="live",
width="12%",
),
NavigateToChildrenColumn("navigate", width="10%"),
]

def get(self, request):
self.show_locale_labels = getattr(
settings, "WAGTAIL_I18N_ENABLED", False
)
self.content_types = []
self.ordering = None

if "ordering" in request.GET and request.GET["ordering"] in [
"title",
"-title",
"latest_revision_created_at",
"-latest_revision_created_at",
"live",
"-live",
]:
self.ordering = request.GET["ordering"]

if "content_type" in request.GET:
try:
app_label, model_name = request.GET["content_type"].split(".")
except ValueError as err:
raise Http404 from err

try:
self.selected_content_type = (
ContentType.objects.get_by_natural_key(
app_label, model_name
)
)
except ContentType.DoesNotExist as err:
raise Http404 from err

else:
self.selected_content_type = None

self.q = self.request.GET.get("q", "")

return super().get(request)

def get_queryset(self) -> QuerySet[Any]:
pages = self.all_pages = (
Page.objects.all().prefetch_related("content_type").specific()
)
if self.show_locale_labels:
pages = pages.select_related("locale")

if self.ordering:
pages = pages.order_by(self.ordering)

if self.selected_content_type:
pages = pages.filter(content_type=self.selected_content_type)

# Parse query and filter
pages, self.all_pages = page_filter_search(
self.q, pages, self.all_pages, self.ordering
)

# Facets
if pages.supports_facet:
self.content_types = [
(ContentType.objects.get(id=content_type_id), count)
for content_type_id, count in self.all_pages.facet(
"content_type_id"
).items()
]

return pages

def get_table_kwargs(self):
kwargs = super().get_table_kwargs()
kwargs["show_locale_labels"] = self.show_locale_labels
kwargs["actions_next_url"] = self.get_index_url()
return kwargs

def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
context = super().get_context_data(**kwargs)
context.update(
{
"all_pages": self.all_pages,
"query_string": self.q,
"content_types": self.content_types,
"selected_content_type": self.selected_content_type,
"ordering": self.ordering,
}
)
return context


class SearchView(BaseSearchView):
template_name = "wagtailadmin_overrides/search.html"

def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
context = super().get_context_data(**kwargs)
context["search_form"] = SearchForm(self.request.GET)
return context


class SearchResultsView(BaseSearchView):
template_name = "wagtailadmin_overrides/search_results.html"
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{% extends 'wagtailadmin/bulk_actions/confirmation/base.html' %}
{% load i18n wagtailadmin_tags %}

{% block titletag %}Set review date{% endblock %}


{% block header %}
{% trans "Set review date" as rev_str %}
{% include "wagtailadmin/shared/header.html" with title=rev_str icon="doc-empty-inverse" %}
{% endblock header %}

{% block items_with_access %}
{% if items %}
<p>{% trans "Set review date to today for these items?" %}</p>
<ul>
{% for item in items %}
<li>
<a href="" target="_blank" rel="noreferrer">{{ item.item.title }}</a>
</li>
{% endfor %}
</ul>
{% endif %}
{% endblock items_with_access %}

{% block items_with_no_access %}

{% blocktranslate trimmed asvar no_access_msg count counter=items_with_no_access|length %}You don't have permission to edit this item{% plural %}You don't have permission to edit these items{% endblocktranslate %}
{% include './list_items_with_no_access.html' with items=items_with_no_access no_access_msg=no_access_msg %}

{% endblock items_with_no_access %}

{% block form_section %}
{% if items %}
{% trans 'Yes, set date' as action_button_text %}
{% trans "No, keep date" as no_action_button_text %}
{% include 'wagtailadmin/bulk_actions/confirmation/form.html' %}
{% else %}
{% include 'wagtailadmin/bulk_actions/confirmation/go_back.html' %}
{% endif %}
{% endblock form_section %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{% extends 'wagtailadmin/bulk_actions/confirmation/list_items_with_no_access.html' %}
{% load i18n %}

{% block per_item %}
{% if item.can_edit %}
<a href="{% url 'wagtailadmin_pages:edit' item.item.id %}" target="_blank" rel="noreferrer">{{ item.item.title }}</a>
{% else %}
{{ item.item.title }}
{% endif %}
{% endblock per_item %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{% extends "wagtailadmin/base.html" %}
{% load wagtailadmin_tags i18n %}
{% block titletag %}{% trans 'Search' %}{% endblock %}
{% block extra_js %}
{{ block.super }}
<script>
window.wagtailConfig.BULK_ACTION_ITEM_TYPE = 'PAGE';
</script>
<script defer src="{% versioned_static 'wagtailadmin/js/bulk-actions.js' %}"></script>
{% endblock %}

{% block content %}
{% url 'super-search:results' as search_results_url %}
{% include "wagtailadmin/shared/header.html" with title=_("Super Search") search_url="super-search:search" icon="search" search_results_url=search_results_url search_target="#page-results" %}
<div id="page-results">
{% include "./search_results.html" %}
</div>
{% trans "Select all pages in listing" as select_all_text %}
{% include 'wagtailadmin/bulk_actions/footer.html' with app_label='wagtailcore' model_name='page' objects=page_obj select_all_obj_text=select_all_text %}
{% endblock %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{% load i18n wagtailadmin_tags %}
<div class="nice-padding">
{% if pages %}
<h2 role="alert">
{% blocktrans trimmed count counter=all_pages.count %}
There is one matching page
{% plural %}
There are {{ counter }} matching pages
{% endblocktrans %}
</h2>

{% search_other %}

{% if object_list.supports_facet %}
<nav class="listing-filter" aria-labelledby="page-types-title">
<h3 id="page-types-title" class="filter-title">{% trans "Page types" %}</h3>
<ul class="filter-options">
{% if not selected_content_type %}
<li class="w-bg-border-furniture">{% trans "All" %} ({{ all_pages.count }})</li>
{% else %}
<li><a href="{% url 'super-search:search' %}?q={{ query_string|urlencode }}">{% trans "All" %} ({{ all_pages.count }})</a></li>
{% endif %}

{% for content_type, count in content_types %}
{% if content_type == selected_content_type %}
<li class="w-bg-border-furniture">{{ content_type.model_class.get_verbose_name }} ({{ count }})</li>
{% else %}
<li><a href="{% url 'super-search:search' %}?q={{ query_string|urlencode }}&amp;content_type={{ content_type.app_label }}.{{ content_type.model|lower }}">{{ content_type.model_class.get_verbose_name }} ({{ count }})</a></li>
{% endif %}
{% endfor %}
</ul>
</nav>
{% endif %}

{% component table %}

{% if is_paginated %}
{% include "wagtailadmin/shared/pagination_nav.html" with items=page_obj linkurl=index_url %}
{% endif %}
{% else %}
{% if query_string %}
<h2 role="alert">{% blocktrans trimmed %}Sorry, no pages match <em>{{ query_string }}</em>{% endblocktrans %}</h2>

{% search_other %}
{% else %}
<p>{% trans 'Enter a search term above' %}</p>
{% endif %}
{% endif %}
</div>
Loading
Loading