kopia lustrzana https://github.com/wagtail/wagtail
Add ability to export redirects using reports (#6305)
rodzic
7423d72efe
commit
c0a84975e7
docs/releases
wagtail/contrib/redirects
static/wagtailredirects/css
templates/wagtailredirects
tests
|
@ -21,6 +21,7 @@ Changelog
|
|||
* Reinstate submitter's name on moderation notification email (Matt Westcott)
|
||||
* Add a new switch input widget as an alternative to checkboxes (Karl Hobley)
|
||||
* Allow `{% pageurl %}` fallback to be a direct URL or an object with a `get_absolute_url` method (Andy Babic)
|
||||
* Add support for exporting redirects (Martin Sandström)
|
||||
* Fix: StreamField required status is now consistently handled by the `blank` keyword argument (Matt Westcott)
|
||||
* Fix: Show 'required' asterisks for blocks inside required StreamFields (Matt Westcott)
|
||||
* Fix: Make image chooser "Select format" fields translatable (Helen Chapman, Thibaud Colas)
|
||||
|
|
|
@ -44,6 +44,7 @@ Other features
|
|||
* Reinstate submitter's name on moderation notification email (Matt Westcott)
|
||||
* Add a new switch input widget as an alternative to checkboxes (Karl Hobley)
|
||||
* Allow ``{% pageurl %}`` fallback to be a direct URL or an object with a ``get_absolute_url`` method (Andy Babic)
|
||||
* Add support for exporting redirects (Martin Sandström)
|
||||
|
||||
Bug fixes
|
||||
~~~~~~~~~
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
import django_filters
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from wagtail.admin.filters import WagtailFilterSet
|
||||
from wagtail.admin.widgets import ButtonSelect
|
||||
from wagtail.core.models import Site
|
||||
|
||||
|
||||
class RedirectsReportFilterSet(WagtailFilterSet):
|
||||
is_permanent = django_filters.ChoiceFilter(
|
||||
label=_("Type"),
|
||||
method="filter_type",
|
||||
choices=((True, _("Permanent")), (False, _("Temporary")),),
|
||||
empty_label=_("All"),
|
||||
widget=ButtonSelect,
|
||||
)
|
||||
|
||||
site = django_filters.ModelChoiceFilter(
|
||||
field_name="site", queryset=Site.objects.all()
|
||||
)
|
||||
|
||||
def filter_type(self, queryset, name, value):
|
||||
if value and self.request and self.request.user:
|
||||
queryset = queryset.filter(is_permanent=value)
|
||||
return queryset
|
|
@ -15,3 +15,7 @@ header .has-multiple-actions {
|
|||
header .has-multiple-actions .actionbutton {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
header .has-multiple-actions .dropdown {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
|
|
@ -23,7 +23,9 @@
|
|||
{% url "wagtailredirects:add" as add_link %}
|
||||
{% trans "Add redirect" as add_str %}
|
||||
{% url "wagtailredirects:start_import" as import_link %}
|
||||
{% url "wagtailredirects:report" as report_link %}
|
||||
{% trans "Import redirects" as import_str %}
|
||||
{% trans "Export redirects" as export_str %}
|
||||
|
||||
<header class="hasform">
|
||||
{% block breadcrumb %}{% endblock %}
|
||||
|
@ -45,8 +47,13 @@
|
|||
<div class="actionbutton">
|
||||
<a href="{{ add_link }}" class="button bicolor button--icon">{% icon name="plus" wrapped=1 %}{{ add_str }}</a>
|
||||
</div>
|
||||
<div class="actionbutton">
|
||||
|
||||
<div class="dropdown dropdown-button match-width">
|
||||
<a href="{{ import_link }}" class="button bicolor button--icon">{% icon name="doc-full-inverse" wrapped=1 %}{{ import_str }}</a>
|
||||
<div class="dropdown-toggle">{% icon name="arrow-down" %}</div>
|
||||
<ul>
|
||||
<li><a class="button bicolor button--icon" href="{{ report_link }}">{% icon name="download" wrapped=1 %}{{ export_str }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
{% extends 'wagtailadmin/reports/base_report.html' %}
|
||||
{% load i18n wagtailadmin_tags %}
|
||||
|
||||
{% block results %}
|
||||
{% if object_list %}
|
||||
{% include "wagtailredirects/list.html" with redirects=object_list %}
|
||||
{% else %}
|
||||
<p>{% trans "No redirects found." %}</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block no_results %}
|
||||
<p>{% trans "No redirects found." %}</p>
|
||||
{% endblock %}
|
|
@ -0,0 +1,82 @@
|
|||
from io import BytesIO
|
||||
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from openpyxl import load_workbook
|
||||
|
||||
from wagtail.contrib.redirects.models import Redirect
|
||||
from wagtail.core.models import Site
|
||||
from wagtail.tests.utils import WagtailTestUtils
|
||||
|
||||
|
||||
class TestRedirectReport(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
self.user = self.login()
|
||||
|
||||
def get(self, params={}):
|
||||
return self.client.get(reverse("wagtailredirects:report"), params)
|
||||
|
||||
def test_empty(self):
|
||||
response = self.get()
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(
|
||||
response, "wagtailredirects/reports/redirects_report.html"
|
||||
)
|
||||
self.assertContains(response, "No redirects found.")
|
||||
|
||||
def test_listing_contains_redirect(self):
|
||||
redirect = Redirect.add_redirect("/from", "/to", False)
|
||||
response = self.get()
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, redirect.old_path)
|
||||
|
||||
def test_filtering_by_type(self):
|
||||
temp_redirect = Redirect.add_redirect("/from", "/to", False)
|
||||
perm_redirect = Redirect.add_redirect("/cat", "/dog", True)
|
||||
|
||||
response = self.get(params={"is_permanent": "True"})
|
||||
|
||||
self.assertContains(response, perm_redirect.old_path)
|
||||
self.assertNotContains(response, temp_redirect.old_path)
|
||||
|
||||
def test_filtering_by_site(self):
|
||||
site = Site.objects.first()
|
||||
site_redirect = Redirect.add_redirect("/cat", "/dog")
|
||||
site_redirect.site = site
|
||||
site_redirect.save()
|
||||
nosite_redirect = Redirect.add_redirect("/from", "/to")
|
||||
|
||||
response = self.get(params={"site": site.pk})
|
||||
|
||||
self.assertContains(response, site_redirect.old_path)
|
||||
self.assertNotContains(response, nosite_redirect.old_path)
|
||||
|
||||
def test_csv_export(self):
|
||||
Redirect.add_redirect("/from", "/to", False)
|
||||
|
||||
response = self.get(params={"export": "csv"})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
csv_data = response.getvalue().decode().split("\n")
|
||||
csv_header = csv_data[0]
|
||||
csv_entries = csv_data[1:]
|
||||
csv_entries = csv_entries[:-1] # Drop empty last line
|
||||
|
||||
self.assertEqual(csv_header, "From,Site,To,Type\r")
|
||||
self.assertEqual(len(csv_entries), 1)
|
||||
self.assertEqual(csv_entries[0], "/from,None,/to,temporary\r")
|
||||
|
||||
def test_xlsx_export(self):
|
||||
Redirect.add_redirect("/from", "/to", True)
|
||||
|
||||
response = self.get(params={"export": "xlsx"})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
workbook_data = response.getvalue()
|
||||
worksheet = load_workbook(filename=BytesIO(workbook_data))["Sheet1"]
|
||||
cell_array = [[cell.value for cell in row] for row in worksheet.rows]
|
||||
|
||||
self.assertEqual(cell_array[0], ["From", "Site", "To", "Type"])
|
||||
self.assertEqual(len(cell_array), 2)
|
||||
self.assertEqual(cell_array[1], ["/from", "None", "/to", "permanent"])
|
|
@ -11,4 +11,5 @@ urlpatterns = [
|
|||
path('<int:redirect_id>/delete/', views.delete, name='delete'),
|
||||
path('import/', views.start_import, name="start_import"),
|
||||
path('import/process/', views.process_import, name="process_import"),
|
||||
path('report', views.RedirectsReportView.as_view(), name="report"),
|
||||
]
|
||||
|
|
|
@ -15,8 +15,10 @@ from django.views.decorators.vary import vary_on_headers
|
|||
from wagtail.admin import messages
|
||||
from wagtail.admin.auth import PermissionPolicyChecker
|
||||
from wagtail.admin.forms.search import SearchForm
|
||||
from wagtail.admin.views.reports import ReportView
|
||||
from wagtail.contrib.redirects import models
|
||||
from wagtail.contrib.redirects.base_formats import DEFAULT_FORMATS
|
||||
from wagtail.contrib.redirects.filters import RedirectsReportFilterSet
|
||||
from wagtail.contrib.redirects.forms import ConfirmImportForm, ImportForm, RedirectForm
|
||||
from wagtail.contrib.redirects.permissions import permission_policy
|
||||
from wagtail.contrib.redirects.utils import (
|
||||
|
@ -348,3 +350,27 @@ def to_readable_errors(error):
|
|||
errors = [x.lstrip('* ') for x in errors]
|
||||
errors = ", ".join(errors)
|
||||
return errors
|
||||
|
||||
|
||||
class RedirectsReportView(ReportView):
|
||||
header_icon = "redirect"
|
||||
title = _("Export Redirects")
|
||||
template_name = "wagtailredirects/reports/redirects_report.html"
|
||||
filterset_class = RedirectsReportFilterSet
|
||||
|
||||
list_export = [
|
||||
"old_path",
|
||||
"site",
|
||||
"link",
|
||||
"get_is_permanent_display",
|
||||
]
|
||||
|
||||
export_headings = {
|
||||
"old_path": _("From"),
|
||||
"site": _("Site"),
|
||||
"link": _("To"),
|
||||
"get_is_permanent_display": _("Type"),
|
||||
}
|
||||
|
||||
def get_queryset(self):
|
||||
return models.Redirect.objects.all().order_by("old_path")
|
||||
|
|
Ładowanie…
Reference in New Issue