1632 lines
62 KiB
Python
1632 lines
62 KiB
Python
from datetime import datetime
|
|
from io import BytesIO
|
|
from unittest import mock
|
|
|
|
from django.conf import settings
|
|
from django.contrib.admin.utils import quote
|
|
from django.contrib.auth import get_permission_codename
|
|
from django.contrib.auth.models import Permission
|
|
from django.contrib.contenttypes.models import ContentType
|
|
from django.core.exceptions import ImproperlyConfigured
|
|
from django.template.defaultfilters import date
|
|
from django.test import SimpleTestCase, TestCase, TransactionTestCase, override_settings
|
|
from django.urls import NoReverseMatch, resolve, reverse
|
|
from django.utils.timezone import now
|
|
from openpyxl import load_workbook
|
|
|
|
from wagtail.admin.admin_url_finder import AdminURLFinder
|
|
from wagtail.admin.menu import admin_menu, settings_menu
|
|
from wagtail.admin.panels import get_edit_handler
|
|
from wagtail.admin.staticfiles import versioned_static
|
|
from wagtail.admin.views.mixins import ExcelDateFormatter
|
|
from wagtail.blocks.field_block import FieldBlockAdapter
|
|
from wagtail.coreutils import get_dummy_request
|
|
from wagtail.documents import get_document_model
|
|
from wagtail.documents.tests.utils import get_test_document_file
|
|
from wagtail.images import get_image_model
|
|
from wagtail.images.tests.utils import get_test_image_file
|
|
from wagtail.models import Locale, Workflow, WorkflowContentType
|
|
from wagtail.snippets.blocks import SnippetChooserBlock
|
|
from wagtail.snippets.models import register_snippet
|
|
from wagtail.snippets.views.snippets import SnippetViewSet
|
|
from wagtail.snippets.widgets import AdminSnippetChooser
|
|
from wagtail.test.testapp.models import (
|
|
Advert,
|
|
DraftStateModel,
|
|
FullFeaturedSnippet,
|
|
ModeratedModel,
|
|
RevisableChildModel,
|
|
RevisableModel,
|
|
SnippetChooserModel,
|
|
VariousOnDeleteModel,
|
|
)
|
|
from wagtail.test.utils import WagtailTestUtils
|
|
from wagtail.test.utils.template_tests import AdminTemplateTestUtils
|
|
from wagtail.utils.timestamps import render_timestamp
|
|
|
|
|
|
class TestIncorrectRegistration(SimpleTestCase):
|
|
def test_no_model_set_or_passed(self):
|
|
# The base SnippetViewSet class has no `model` attribute set,
|
|
# so using it directly should raise an error
|
|
with self.assertRaises(ImproperlyConfigured) as cm:
|
|
register_snippet(SnippetViewSet)
|
|
message = str(cm.exception)
|
|
self.assertIn("ModelViewSet", message)
|
|
self.assertIn(
|
|
"must define a `model` attribute or pass a `model` argument",
|
|
message,
|
|
)
|
|
|
|
|
|
class BaseSnippetViewSetTests(WagtailTestUtils, TestCase):
|
|
model = None
|
|
|
|
def setUp(self):
|
|
self.user = self.login()
|
|
|
|
def get_url(self, url_name, args=()):
|
|
return reverse(self.model.snippet_viewset.get_url_name(url_name), args=args)
|
|
|
|
|
|
class TestCustomIcon(BaseSnippetViewSetTests):
|
|
model = FullFeaturedSnippet
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.object = self.model.objects.create(text="test snippet with custom icon")
|
|
self.revision_1 = self.object.save_revision()
|
|
self.revision_1.publish()
|
|
self.object.text = "test snippet with custom icon (updated)"
|
|
self.revision_2 = self.object.save_revision()
|
|
|
|
def test_get_views(self):
|
|
pk = quote(self.object.pk)
|
|
views = [
|
|
("list", [], "headers/slim_header.html"),
|
|
("add", [], "headers/slim_header.html"),
|
|
("edit", [pk], "headers/slim_header.html"),
|
|
("delete", [pk], "header.html"),
|
|
("usage", [pk], "headers/slim_header.html"),
|
|
("unpublish", [pk], "header.html"),
|
|
("workflow_history", [pk], "header.html"),
|
|
("revisions_revert", [pk, self.revision_1.id], "headers/slim_header.html"),
|
|
(
|
|
"revisions_compare",
|
|
[pk, self.revision_1.id, self.revision_2.id],
|
|
"header.html",
|
|
),
|
|
("revisions_unschedule", [pk, self.revision_2.id], "header.html"),
|
|
]
|
|
for view_name, args, header in views:
|
|
with self.subTest(view_name=view_name):
|
|
response = self.client.get(self.get_url(view_name, args))
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertEqual(response.context["header_icon"], "cog")
|
|
self.assertContains(response, "icon icon-cog", count=1)
|
|
self.assertTemplateUsed(response, f"wagtailadmin/shared/{header}")
|
|
|
|
def test_get_history(self):
|
|
response = self.client.get(self.get_url("history", [quote(self.object.pk)]))
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertTemplateUsed(
|
|
response,
|
|
"wagtailadmin/shared/headers/slim_header.html",
|
|
)
|
|
# History view icon is not configurable for consistency with pages
|
|
self.assertEqual(response.context["header_icon"], "history")
|
|
self.assertContains(response, "icon icon-history")
|
|
self.assertNotContains(response, "icon icon-cog")
|
|
self.assertTemplateNotUsed(response, "wagtailadmin/shared/header.html")
|
|
|
|
def test_get_workflow_history_detail(self):
|
|
# Assign default workflow to the snippet model
|
|
self.content_type = ContentType.objects.get_for_model(type(self.object))
|
|
self.workflow = Workflow.objects.first()
|
|
WorkflowContentType.objects.create(
|
|
content_type=self.content_type,
|
|
workflow=self.workflow,
|
|
)
|
|
self.object.text = "Edited!"
|
|
self.object.save_revision()
|
|
workflow_state = self.workflow.start(self.object, self.user)
|
|
response = self.client.get(
|
|
self.get_url(
|
|
"workflow_history_detail", [quote(self.object.pk), workflow_state.id]
|
|
)
|
|
)
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertTemplateUsed(response, "wagtailadmin/shared/header.html")
|
|
# The icon is not displayed in the header,
|
|
# but it is displayed in the main content
|
|
self.assertEqual(response.context["header_icon"], "list-ul")
|
|
self.assertContains(response, "icon icon-list-ul")
|
|
self.assertContains(response, "icon icon-cog")
|
|
|
|
|
|
class TestSnippetChooserBlockWithIcon(TestCase):
|
|
def test_adapt(self):
|
|
block = SnippetChooserBlock(FullFeaturedSnippet)
|
|
|
|
block.set_name("test_snippetchooserblock")
|
|
js_args = FieldBlockAdapter().js_args(block)
|
|
|
|
self.assertEqual(js_args[0], "test_snippetchooserblock")
|
|
self.assertIsInstance(js_args[1], AdminSnippetChooser)
|
|
self.assertEqual(js_args[1].model, FullFeaturedSnippet)
|
|
# It should use the icon defined in the FullFeaturedSnippetViewSet
|
|
self.assertEqual(js_args[2]["icon"], "cog")
|
|
|
|
def test_deconstruct(self):
|
|
block = SnippetChooserBlock(FullFeaturedSnippet, required=False)
|
|
path, args, kwargs = block.deconstruct()
|
|
self.assertEqual(path, "wagtail.snippets.blocks.SnippetChooserBlock")
|
|
self.assertEqual(args, (FullFeaturedSnippet,))
|
|
# It should not add any extra kwargs for the icon
|
|
self.assertEqual(kwargs, {"required": False})
|
|
|
|
|
|
class TestSnippetChooserPanelWithIcon(BaseSnippetViewSetTests):
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.request = get_dummy_request()
|
|
self.request.user = self.user
|
|
self.text = "Test full-featured snippet with icon text"
|
|
self.full_featured_snippet = FullFeaturedSnippet.objects.create(text=self.text)
|
|
test_snippet = SnippetChooserModel.objects.create(
|
|
advert=Advert.objects.create(text="foo"),
|
|
full_featured=self.full_featured_snippet,
|
|
)
|
|
|
|
self.edit_handler = get_edit_handler(SnippetChooserModel)
|
|
self.form_class = self.edit_handler.get_form_class()
|
|
form = self.form_class(instance=test_snippet)
|
|
edit_handler = self.edit_handler.get_bound_panel(
|
|
instance=test_snippet, form=form, request=self.request
|
|
)
|
|
|
|
self.object_chooser_panel = [
|
|
panel
|
|
for panel in edit_handler.children
|
|
if getattr(panel, "field_name", None) == "full_featured"
|
|
][0]
|
|
|
|
def test_render_html(self):
|
|
field_html = self.object_chooser_panel.render_html()
|
|
self.assertIn(self.text, field_html)
|
|
self.assertIn("Choose full-featured snippet", field_html)
|
|
self.assertIn("Choose another full-featured snippet", field_html)
|
|
self.assertIn("icon icon-cog icon", field_html)
|
|
|
|
# make sure no snippet icons remain
|
|
self.assertNotIn("icon-snippet", field_html)
|
|
|
|
def test_render_as_empty_field(self):
|
|
test_snippet = SnippetChooserModel()
|
|
form = self.form_class(instance=test_snippet)
|
|
edit_handler = self.edit_handler.get_bound_panel(
|
|
instance=test_snippet, form=form, request=self.request
|
|
)
|
|
|
|
snippet_chooser_panel = [
|
|
panel
|
|
for panel in edit_handler.children
|
|
if getattr(panel, "field_name", None) == "full_featured"
|
|
][0]
|
|
|
|
field_html = snippet_chooser_panel.render_html()
|
|
self.assertIn("Choose full-featured snippet", field_html)
|
|
self.assertIn("Choose another full-featured snippet", field_html)
|
|
self.assertIn("icon icon-cog icon", field_html)
|
|
|
|
# make sure no snippet icons remain
|
|
self.assertNotIn("icon-snippet", field_html)
|
|
|
|
def test_chooser_popup(self):
|
|
chooser_viewset = FullFeaturedSnippet.snippet_viewset.chooser_viewset
|
|
response = self.client.get(reverse(chooser_viewset.get_url_name("choose")))
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertEqual(response.context["header_icon"], "cog")
|
|
self.assertContains(response, "icon icon-cog", count=1)
|
|
self.assertEqual(response.context["icon"], "cog")
|
|
|
|
# make sure no snippet icons remain
|
|
for key in response.context.keys():
|
|
if "icon" in key:
|
|
self.assertNotIn("snippet", response.context[key])
|
|
|
|
# chooser should include the creation form
|
|
response_json = response.json()
|
|
soup = self.get_soup(response_json["html"])
|
|
self.assertTrue(soup.select_one("form[data-chooser-modal-creation-form]"))
|
|
|
|
def test_chosen(self):
|
|
chooser_viewset = FullFeaturedSnippet.snippet_viewset.chooser_viewset
|
|
response = self.client.get(
|
|
reverse(
|
|
chooser_viewset.get_url_name("chosen"),
|
|
args=[self.full_featured_snippet.pk],
|
|
)
|
|
)
|
|
response_json = response.json()
|
|
self.assertEqual(response_json["step"], "chosen")
|
|
self.assertEqual(
|
|
response_json["result"]["id"], str(self.full_featured_snippet.pk)
|
|
)
|
|
self.assertEqual(response_json["result"]["string"], self.text)
|
|
|
|
def test_create_from_chooser(self):
|
|
chooser_viewset = FullFeaturedSnippet.snippet_viewset.chooser_viewset
|
|
response = self.client.post(
|
|
reverse(chooser_viewset.get_url_name("create")),
|
|
{
|
|
"text": "New snippet",
|
|
},
|
|
)
|
|
response_json = response.json()
|
|
self.assertEqual(response_json["step"], "chosen")
|
|
self.assertEqual(response_json["result"]["string"], "New snippet")
|
|
|
|
|
|
class TestAdminURLs(BaseSnippetViewSetTests):
|
|
def test_default_url_namespace(self):
|
|
snippet = Advert.objects.create(text="foo")
|
|
viewset = snippet.snippet_viewset
|
|
# Accessed via the viewset
|
|
self.assertEqual(
|
|
viewset.get_admin_url_namespace(),
|
|
"wagtailsnippets_tests_advert",
|
|
)
|
|
# Get specific URL name
|
|
self.assertEqual(
|
|
viewset.get_url_name("edit"),
|
|
"wagtailsnippets_tests_advert:edit",
|
|
)
|
|
# Chooser namespace
|
|
self.assertEqual(
|
|
viewset.get_chooser_admin_url_namespace(),
|
|
"wagtailsnippetchoosers_tests_advert",
|
|
)
|
|
# Get specific chooser URL name
|
|
self.assertEqual(
|
|
viewset.chooser_viewset.get_url_name("choose"),
|
|
"wagtailsnippetchoosers_tests_advert:choose",
|
|
)
|
|
|
|
def test_default_admin_base_path(self):
|
|
snippet = Advert.objects.create(text="foo")
|
|
viewset = snippet.snippet_viewset
|
|
pk = quote(snippet.pk)
|
|
expected_url = f"/admin/snippets/tests/advert/edit/{pk}/"
|
|
expected_choose_url = "/admin/snippets/choose/tests/advert/"
|
|
|
|
# Accessed via the viewset
|
|
self.assertEqual(viewset.get_admin_base_path(), "snippets/tests/advert")
|
|
# Get specific URL
|
|
self.assertEqual(reverse(viewset.get_url_name("edit"), args=[pk]), expected_url)
|
|
# Ensure AdminURLFinder returns the correct URL
|
|
url_finder = AdminURLFinder(self.user)
|
|
self.assertEqual(url_finder.get_edit_url(snippet), expected_url)
|
|
# Chooser base path
|
|
self.assertEqual(
|
|
viewset.get_chooser_admin_base_path(),
|
|
"snippets/choose/tests/advert",
|
|
)
|
|
# Get specific chooser URL
|
|
self.assertEqual(
|
|
reverse(viewset.chooser_viewset.get_url_name("choose")),
|
|
expected_choose_url,
|
|
)
|
|
|
|
def test_custom_url_namespace(self):
|
|
snippet = FullFeaturedSnippet.objects.create(text="customised")
|
|
viewset = snippet.snippet_viewset
|
|
# Accessed via the viewset
|
|
self.assertEqual(viewset.get_admin_url_namespace(), "some_namespace")
|
|
# Get specific URL name
|
|
self.assertEqual(viewset.get_url_name("edit"), "some_namespace:edit")
|
|
# Chooser namespace
|
|
self.assertEqual(
|
|
viewset.get_chooser_admin_url_namespace(),
|
|
"my_chooser_namespace",
|
|
)
|
|
# Get specific chooser URL name
|
|
self.assertEqual(
|
|
viewset.chooser_viewset.get_url_name("choose"),
|
|
"my_chooser_namespace:choose",
|
|
)
|
|
|
|
def test_custom_admin_base_path(self):
|
|
snippet = FullFeaturedSnippet.objects.create(text="customised")
|
|
viewset = snippet.snippet_viewset
|
|
pk = quote(snippet.pk)
|
|
expected_url = f"/admin/deep/within/the/admin/edit/{pk}/"
|
|
expected_choose_url = "/admin/choose/wisely/"
|
|
# Accessed via the viewset
|
|
self.assertEqual(viewset.get_admin_base_path(), "deep/within/the/admin")
|
|
# Get specific URL
|
|
self.assertEqual(reverse(viewset.get_url_name("edit"), args=[pk]), expected_url)
|
|
# Ensure AdminURLFinder returns the correct URL
|
|
url_finder = AdminURLFinder(self.user)
|
|
self.assertEqual(url_finder.get_edit_url(snippet), expected_url)
|
|
# Chooser base path
|
|
self.assertEqual(
|
|
viewset.get_chooser_admin_base_path(),
|
|
"choose/wisely",
|
|
)
|
|
# Get specific chooser URL
|
|
self.assertEqual(
|
|
reverse(viewset.chooser_viewset.get_url_name("choose")),
|
|
expected_choose_url,
|
|
)
|
|
|
|
|
|
class TestPagination(BaseSnippetViewSetTests):
|
|
@classmethod
|
|
def setUpTestData(cls):
|
|
default_locale = Locale.get_default()
|
|
objects = [
|
|
FullFeaturedSnippet(text=f"Snippet {i}", locale=default_locale)
|
|
for i in range(32)
|
|
]
|
|
FullFeaturedSnippet.objects.bulk_create(objects)
|
|
objects = [Advert(text=f"Snippet {i}") for i in range(32)]
|
|
Advert.objects.bulk_create(objects)
|
|
|
|
def test_default_list_pagination(self):
|
|
list_url = reverse(Advert.snippet_viewset.get_url_name("list"))
|
|
response = self.client.get(list_url)
|
|
|
|
# Default is 20 per page
|
|
self.assertEqual(Advert.objects.all().count(), 32)
|
|
self.assertContains(response, "Page 1 of 2")
|
|
self.assertContains(response, "Next")
|
|
self.assertContains(response, list_url + "?p=2")
|
|
|
|
def test_custom_list_pagination(self):
|
|
list_url = reverse(FullFeaturedSnippet.snippet_viewset.get_url_name("list"))
|
|
response = self.client.get(list_url)
|
|
|
|
# FullFeaturedSnippet is set to display 5 per page
|
|
self.assertEqual(FullFeaturedSnippet.objects.all().count(), 32)
|
|
self.assertContains(response, "Page 1 of 7")
|
|
self.assertContains(response, "Next")
|
|
self.assertContains(response, list_url + "?p=2")
|
|
|
|
def test_default_chooser_pagination(self):
|
|
chooser_viewset = Advert.snippet_viewset.chooser_viewset
|
|
choose_url = reverse(chooser_viewset.get_url_name("choose"))
|
|
choose_results_url = reverse(chooser_viewset.get_url_name("choose_results"))
|
|
response = self.client.get(choose_url)
|
|
|
|
# Default is 10 per page
|
|
self.assertEqual(Advert.objects.all().count(), 32)
|
|
self.assertContains(response, "Page 1 of 4")
|
|
self.assertContains(response, "Next")
|
|
self.assertContains(response, choose_results_url + "?p=2")
|
|
|
|
def test_custom_chooser_pagination(self):
|
|
chooser_viewset = FullFeaturedSnippet.snippet_viewset.chooser_viewset
|
|
choose_url = reverse(chooser_viewset.get_url_name("choose"))
|
|
choose_results_url = reverse(chooser_viewset.get_url_name("choose_results"))
|
|
response = self.client.get(choose_url)
|
|
|
|
# FullFeaturedSnippet is set to display 15 per page
|
|
self.assertEqual(FullFeaturedSnippet.objects.all().count(), 32)
|
|
self.assertContains(response, "Page 1 of 3")
|
|
self.assertContains(response, "Next")
|
|
self.assertContains(response, choose_results_url + "?p=2")
|
|
|
|
|
|
class TestFilterSetClass(BaseSnippetViewSetTests):
|
|
model = FullFeaturedSnippet
|
|
|
|
def get(self, params={}):
|
|
return self.client.get(self.get_url("list"), params)
|
|
|
|
def create_test_snippets(self):
|
|
FullFeaturedSnippet.objects.create(
|
|
text="Nasi goreng from Indonesia", country_code="ID"
|
|
)
|
|
FullFeaturedSnippet.objects.create(
|
|
text="Fish and chips from the UK", country_code="UK"
|
|
)
|
|
|
|
def test_get_include_filters_form_media(self):
|
|
response = self.get()
|
|
html = response.content.decode()
|
|
datetime_js = versioned_static("wagtailadmin/js/date-time-chooser.js")
|
|
|
|
# The script file for the date time chooser should be included
|
|
self.assertTagInHTML(f'<script src="{datetime_js}"></script>', html)
|
|
|
|
def test_unfiltered_no_results(self):
|
|
response = self.get()
|
|
self.assertContains(response, "There are no full-featured snippets to display.")
|
|
self.assertContains(
|
|
response,
|
|
'<label for="id_country_code_0"><input type="radio" name="country_code" value="" id="id_country_code_0" checked>All</label>',
|
|
html=True,
|
|
)
|
|
|
|
def test_unfiltered_with_results(self):
|
|
self.create_test_snippets()
|
|
response = self.get()
|
|
self.assertContains(response, "Nasi goreng from Indonesia")
|
|
self.assertContains(response, "Fish and chips from the UK")
|
|
self.assertNotContains(response, "There are 2 matches")
|
|
self.assertContains(
|
|
response,
|
|
'<label for="id_country_code_0"><input type="radio" name="country_code" value="" id="id_country_code_0" checked>All</label>',
|
|
html=True,
|
|
)
|
|
|
|
def test_empty_filter_with_results(self):
|
|
self.create_test_snippets()
|
|
response = self.get({"country_code": ""})
|
|
self.assertContains(response, "Nasi goreng from Indonesia")
|
|
self.assertContains(response, "Fish and chips from the UK")
|
|
self.assertNotContains(response, "There are 2 matches")
|
|
self.assertContains(
|
|
response,
|
|
'<label for="id_country_code_0"><input type="radio" name="country_code" value="" id="id_country_code_0" checked>All</label>',
|
|
html=True,
|
|
)
|
|
|
|
def test_filtered_no_results(self):
|
|
self.create_test_snippets()
|
|
response = self.get({"country_code": "PH"})
|
|
self.assertContains(response, "No full-featured snippets match your query")
|
|
self.assertContains(
|
|
response,
|
|
'<label for="id_country_code_2"><input type="radio" name="country_code" value="PH" id="id_country_code_2" checked>Philippines</label>',
|
|
html=True,
|
|
)
|
|
# Should render the active filters even when there are no results
|
|
soup = self.get_soup(response.content)
|
|
active_filters = soup.select_one(".w-active-filters")
|
|
self.assertIsNotNone(active_filters)
|
|
clear = active_filters.select_one(".w-pill__remove")
|
|
self.assertIsNotNone(clear)
|
|
url, params = clear.attrs.get("data-w-swap-src-value").split("?", 1)
|
|
self.assertEqual(url, self.get_url("list_results"))
|
|
self.assertNotIn("country_code=PH", params)
|
|
|
|
def test_filtered_with_results(self):
|
|
self.create_test_snippets()
|
|
response = self.get({"country_code": "ID"})
|
|
self.assertContains(response, "Nasi goreng from Indonesia")
|
|
self.assertContains(response, "There is 1 match")
|
|
self.assertContains(
|
|
response,
|
|
'<label for="id_country_code_1"><input type="radio" name="country_code" value="ID" id="id_country_code_1" checked>Indonesia</label>',
|
|
html=True,
|
|
)
|
|
# Should render the active filters
|
|
soup = self.get_soup(response.content)
|
|
active_filters = soup.select_one(".w-active-filters")
|
|
self.assertIsNotNone(active_filters)
|
|
clear = active_filters.select_one(".w-pill__remove")
|
|
self.assertIsNotNone(clear)
|
|
url, params = clear.attrs.get("data-w-swap-src-value").split("?", 1)
|
|
self.assertEqual(url, self.get_url("list_results"))
|
|
self.assertNotIn("country_code=ID", params)
|
|
|
|
|
|
class TestFilterSetClassSearch(WagtailTestUtils, TransactionTestCase):
|
|
fixtures = ["test_empty.json"]
|
|
|
|
def setUp(self):
|
|
self.login()
|
|
|
|
def get_url(self, url_name, args=()):
|
|
return reverse(
|
|
FullFeaturedSnippet.snippet_viewset.get_url_name(url_name), args=args
|
|
)
|
|
|
|
def get(self, params={}):
|
|
return self.client.get(self.get_url("list"), params)
|
|
|
|
def create_test_snippets(self):
|
|
FullFeaturedSnippet.objects.create(
|
|
text="Nasi goreng from Indonesia", country_code="ID"
|
|
)
|
|
FullFeaturedSnippet.objects.create(
|
|
text="Fish and chips from the UK", country_code="UK"
|
|
)
|
|
|
|
def test_filtered_searched_no_results(self):
|
|
self.create_test_snippets()
|
|
response = self.get({"country_code": "ID", "q": "chips"})
|
|
self.assertContains(response, "No full-featured snippets match your query")
|
|
self.assertContains(
|
|
response,
|
|
'<label for="id_country_code_1"><input type="radio" name="country_code" value="ID" id="id_country_code_1" checked>Indonesia</label>',
|
|
html=True,
|
|
)
|
|
|
|
def test_filtered_searched_with_results(self):
|
|
self.create_test_snippets()
|
|
response = self.get({"country_code": "UK", "q": "chips"})
|
|
self.assertContains(response, "Fish and chips from the UK")
|
|
self.assertContains(response, "There is 1 match")
|
|
self.assertContains(
|
|
response,
|
|
'<label for="id_country_code_3"><input type="radio" name="country_code" value="UK" id="id_country_code_3" checked>United Kingdom</label>',
|
|
html=True,
|
|
)
|
|
|
|
|
|
class TestListFilterWithList(BaseSnippetViewSetTests):
|
|
model = DraftStateModel
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.date = now()
|
|
self.date_str = self.date.isoformat()
|
|
|
|
def get(self, params={}):
|
|
return self.client.get(self.get_url("list"), params)
|
|
|
|
def create_test_snippets(self):
|
|
self.model.objects.create(text="The first created object")
|
|
self.model.objects.create(
|
|
text="A second one after that",
|
|
first_published_at=self.date,
|
|
)
|
|
|
|
def test_get_include_filters_form_media(self):
|
|
response = self.get()
|
|
html = response.content.decode()
|
|
datetime_js = versioned_static("wagtailadmin/js/date-time-chooser.js")
|
|
|
|
# The script file for the date time chooser should be included
|
|
self.assertTagInHTML(f'<script src="{datetime_js}"></script>', html)
|
|
|
|
def test_unfiltered_no_results(self):
|
|
response = self.get()
|
|
add_url = self.get_url("add")
|
|
self.assertContains(
|
|
response,
|
|
f"""<p>There are no {self.model._meta.verbose_name_plural} to display.
|
|
Why not <a href="{add_url}">add one</a>?</p>""",
|
|
html=True,
|
|
)
|
|
self.assertContains(
|
|
response,
|
|
'<label class="w-field__label" for="id_first_published_at" id="id_first_published_at-label">First published at</label>',
|
|
html=True,
|
|
)
|
|
self.assertContains(
|
|
response,
|
|
'<input type="text" name="first_published_at" autocomplete="off" id="id_first_published_at">',
|
|
html=True,
|
|
)
|
|
|
|
def test_unfiltered_with_results(self):
|
|
self.create_test_snippets()
|
|
response = self.get()
|
|
self.assertContains(response, "The first created object")
|
|
self.assertContains(response, "A second one after that")
|
|
self.assertNotContains(response, "There are 2 matches")
|
|
self.assertContains(
|
|
response,
|
|
'<label class="w-field__label" for="id_first_published_at" id="id_first_published_at-label">First published at</label>',
|
|
html=True,
|
|
)
|
|
self.assertContains(
|
|
response,
|
|
'<input type="text" name="first_published_at" autocomplete="off" id="id_first_published_at">',
|
|
html=True,
|
|
)
|
|
|
|
def test_empty_filter_with_results(self):
|
|
self.create_test_snippets()
|
|
response = self.get({"first_published_at": ""})
|
|
self.assertContains(response, "The first created object")
|
|
self.assertContains(response, "A second one after that")
|
|
self.assertNotContains(response, "There are 2 matches")
|
|
self.assertContains(
|
|
response,
|
|
'<label class="w-field__label" for="id_first_published_at" id="id_first_published_at-label">First published at</label>',
|
|
html=True,
|
|
)
|
|
self.assertContains(
|
|
response,
|
|
'<input type="text" name="first_published_at" value="" autocomplete="off" id="id_first_published_at">',
|
|
html=True,
|
|
)
|
|
|
|
def test_filtered_no_results(self):
|
|
self.create_test_snippets()
|
|
response = self.get({"first_published_at": "1970-01-01"})
|
|
self.assertContains(
|
|
response,
|
|
f"No {self.model._meta.verbose_name_plural} match your query",
|
|
)
|
|
self.assertContains(
|
|
response,
|
|
'<label class="w-field__label" for="id_first_published_at" id="id_first_published_at-label">First published at</label>',
|
|
html=True,
|
|
)
|
|
self.assertContains(
|
|
response,
|
|
'<input type="text" name="first_published_at" value="1970-01-01" autocomplete="off" id="id_first_published_at">',
|
|
html=True,
|
|
)
|
|
|
|
def test_filtered_with_results(self):
|
|
self.create_test_snippets()
|
|
response = self.get({"first_published_at": self.date_str})
|
|
self.assertContains(response, "A second one after that")
|
|
self.assertContains(response, "There is 1 match")
|
|
self.assertContains(
|
|
response,
|
|
'<label class="w-field__label" for="id_first_published_at" id="id_first_published_at-label">First published at</label>',
|
|
html=True,
|
|
)
|
|
self.assertContains(
|
|
response,
|
|
f'<input type="text" name="first_published_at" value="{self.date_str}" autocomplete="off" id="id_first_published_at">',
|
|
html=True,
|
|
)
|
|
|
|
|
|
class TestListFilterWithDict(TestListFilterWithList):
|
|
model = ModeratedModel
|
|
|
|
def test_filtered_contains_with_results(self):
|
|
self.create_test_snippets()
|
|
response = self.get({"text__contains": "second one"})
|
|
self.assertContains(response, "A second one after that")
|
|
self.assertContains(response, "There is 1 match")
|
|
self.assertContains(
|
|
response,
|
|
'<label class="w-field__label" for="id_text__contains" id="id_text__contains-label">Text contains</label>',
|
|
html=True,
|
|
)
|
|
self.assertContains(
|
|
response,
|
|
'<input type="text" name="text__contains" value="second one" id="id_text__contains">',
|
|
html=True,
|
|
)
|
|
|
|
|
|
class TestListViewWithCustomColumns(BaseSnippetViewSetTests):
|
|
model = FullFeaturedSnippet
|
|
|
|
@classmethod
|
|
def setUpTestData(cls):
|
|
cls.model.objects.create(text="From Indonesia", country_code="ID")
|
|
cls.model.objects.create(text="From the UK", country_code="UK")
|
|
|
|
def get(self, params={}):
|
|
return self.client.get(self.get_url("list"), params)
|
|
|
|
def test_custom_columns(self):
|
|
response = self.get()
|
|
self.assertContains(response, "Text")
|
|
self.assertContains(response, "Country code")
|
|
self.assertContains(response, "Custom FOO column")
|
|
self.assertContains(response, "Updated")
|
|
self.assertContains(response, "Modulo two")
|
|
self.assertContains(response, "Tristate")
|
|
|
|
self.assertContains(response, "Foo UK")
|
|
|
|
list_url = self.get_url("list")
|
|
sort_country_code_url = list_url + "?ordering=country_code"
|
|
|
|
# One from the country code column, another from the custom foo column
|
|
self.assertContains(response, sort_country_code_url, count=2)
|
|
|
|
soup = self.get_soup(response.content)
|
|
|
|
headings = soup.select("#listing-results table th")
|
|
|
|
# The bulk actions column plus 6 columns defined in FullFeaturedSnippetViewSet
|
|
self.assertEqual(len(headings), 7)
|
|
|
|
def test_falsy_value(self):
|
|
# https://github.com/wagtail/wagtail/issues/10765
|
|
response = self.get()
|
|
self.assertContains(response, "<td>0</td>", html=True, count=1)
|
|
|
|
def test_boolean_column(self):
|
|
self.model.objects.create(text="Another one")
|
|
response = self.get()
|
|
self.assertContains(
|
|
response,
|
|
"""
|
|
<td>
|
|
<svg class="icon icon-success default w-text-positive-100" aria-hidden="true">
|
|
<use href="#icon-success"></use>
|
|
</svg>
|
|
<span class="w-sr-only">True</span>
|
|
</td>
|
|
""",
|
|
html=True,
|
|
count=1,
|
|
)
|
|
self.assertContains(
|
|
response,
|
|
"""
|
|
<td>
|
|
<svg class="icon icon-error default w-text-critical-100" aria-hidden="true">
|
|
<use href="#icon-error"></use>
|
|
</svg>
|
|
<span class="w-sr-only">False</span>
|
|
</td>
|
|
""",
|
|
html=True,
|
|
count=1,
|
|
)
|
|
self.assertContains(
|
|
response,
|
|
"""
|
|
<td>
|
|
<svg class="icon icon-help default" aria-hidden="true">
|
|
<use href="#icon-help"></use>
|
|
</svg>
|
|
<span class="w-sr-only">None</span>
|
|
</td>
|
|
""",
|
|
html=True,
|
|
count=1,
|
|
)
|
|
|
|
|
|
class TestRelatedFieldListDisplay(BaseSnippetViewSetTests):
|
|
model = SnippetChooserModel
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
url = "https://example.com/free_examples"
|
|
self.advert = Advert.objects.create(url=url, text="Free Examples")
|
|
self.ffs = FullFeaturedSnippet.objects.create(text="royale with cheese")
|
|
|
|
def test_empty_foreignkey(self):
|
|
self.no_ffs_chooser = self.model.objects.create(advert=self.advert)
|
|
response = self.client.get(self.get_url("list"))
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertContains(response, "Chosen snippet text")
|
|
self.assertContains(response, "<td></td>", html=True)
|
|
|
|
def test_single_level_relation(self):
|
|
self.scm = self.model.objects.create(advert=self.advert, full_featured=self.ffs)
|
|
response = self.client.get(self.get_url("list"))
|
|
self.assertEqual(response.status_code, 200)
|
|
soup = self.get_soup(response.content)
|
|
headers = [
|
|
header.get_text(strip=True)
|
|
for header in soup.select("#listing-results table th")
|
|
]
|
|
self.assertIn("Chosen snippet text", headers)
|
|
self.assertContains(response, "<td>royale with cheese</td>", html=True)
|
|
|
|
def test_multi_level_relation(self):
|
|
self.scm = self.model.objects.create(advert=self.advert, full_featured=self.ffs)
|
|
dummy_revision = self.ffs.save_revision()
|
|
timestamp = render_timestamp(dummy_revision.created_at)
|
|
response = self.client.get(self.get_url("list"))
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertContains(response, "Latest revision created at")
|
|
self.assertContains(response, f"<td>{timestamp}</td>", html=True)
|
|
|
|
|
|
class TestListExport(BaseSnippetViewSetTests):
|
|
model = FullFeaturedSnippet
|
|
|
|
@classmethod
|
|
def setUpTestData(cls):
|
|
cls.model.objects.create(text="Pot Noodle", country_code="UK")
|
|
|
|
cls.first_published_at = "2023-07-01T13:12:11.100"
|
|
if settings.USE_TZ:
|
|
cls.first_published_at = "2023-07-01T13:12:11.100Z"
|
|
|
|
obj = cls.model.objects.create(
|
|
text="Indomie",
|
|
country_code="ID",
|
|
first_published_at=cls.first_published_at,
|
|
some_number=1,
|
|
)
|
|
# Refresh so the first_published_at becomes a datetime object
|
|
obj.refresh_from_db()
|
|
cls.first_published_at = obj.first_published_at
|
|
cls.some_date = obj.some_date
|
|
|
|
def test_get_not_export_shows_export_buttons(self):
|
|
response = self.client.get(self.get_url("list"))
|
|
self.assertContains(response, "Download CSV")
|
|
self.assertContains(response, self.get_url("list") + "?export=csv")
|
|
self.assertContains(response, "Download XLSX")
|
|
self.assertContains(response, self.get_url("list") + "?export=xlsx")
|
|
|
|
def test_csv_export(self):
|
|
response = self.client.get(self.get_url("list"), {"export": "csv"})
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertEqual(
|
|
response.get("Content-Disposition"),
|
|
'attachment; filename="all-fullfeatured-snippets.csv"',
|
|
)
|
|
|
|
data_lines = response.getvalue().decode().split("\n")
|
|
self.assertEqual(
|
|
data_lines[0],
|
|
"Text,Country code,Custom FOO column,Some date,Some number,First published at\r",
|
|
)
|
|
self.assertEqual(
|
|
data_lines[1],
|
|
f"Indomie,ID,Foo ID,{self.some_date.isoformat()},1,{self.first_published_at.isoformat(sep=' ')}\r",
|
|
)
|
|
self.assertEqual(
|
|
data_lines[2],
|
|
f"Pot Noodle,UK,Foo UK,{self.some_date.isoformat()},0,\r",
|
|
)
|
|
|
|
def test_xlsx_export(self):
|
|
response = self.client.get(self.get_url("list"), {"export": "xlsx"})
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertEqual(
|
|
response.get("Content-Disposition"),
|
|
'attachment; filename="all-fullfeatured-snippets.xlsx"',
|
|
)
|
|
|
|
workbook_data = response.getvalue()
|
|
worksheet = load_workbook(filename=BytesIO(workbook_data)).active
|
|
cell_array = [[cell.value for cell in row] for row in worksheet.rows]
|
|
self.assertEqual(
|
|
cell_array[0],
|
|
[
|
|
"Text",
|
|
"Country code",
|
|
"Custom FOO column",
|
|
"Some date",
|
|
"Some number",
|
|
"First published at",
|
|
],
|
|
)
|
|
self.assertEqual(
|
|
cell_array[1],
|
|
[
|
|
"Indomie",
|
|
"ID",
|
|
"Foo ID",
|
|
self.some_date,
|
|
1,
|
|
datetime(2023, 7, 1, 13, 12, 11, 100000),
|
|
],
|
|
)
|
|
self.assertEqual(
|
|
cell_array[2],
|
|
["Pot Noodle", "UK", "Foo UK", self.some_date, 0, None],
|
|
)
|
|
self.assertEqual(len(cell_array), 3)
|
|
|
|
self.assertEqual(worksheet["F2"].number_format, ExcelDateFormatter().get())
|
|
|
|
|
|
class TestCustomTemplates(BaseSnippetViewSetTests):
|
|
model = FullFeaturedSnippet
|
|
|
|
@classmethod
|
|
def setUpTestData(cls):
|
|
cls.object = cls.model.objects.create(text="Some snippet")
|
|
|
|
def test_template_lookups(self):
|
|
pk = quote(self.object.pk)
|
|
cases = {
|
|
"with app label and model name": (
|
|
"add",
|
|
[],
|
|
[
|
|
"wagtailsnippets/snippets/tests/fullfeaturedsnippet/create.html",
|
|
],
|
|
),
|
|
"with app label": (
|
|
"edit",
|
|
[pk],
|
|
[
|
|
"wagtailsnippets/snippets/tests/edit.html",
|
|
],
|
|
),
|
|
"without app label and model name": (
|
|
"delete",
|
|
[pk],
|
|
[
|
|
"wagtailsnippets/snippets/delete.html",
|
|
],
|
|
),
|
|
"override a view that uses a generic template": (
|
|
"unpublish",
|
|
[pk],
|
|
[
|
|
"wagtailsnippets/snippets/tests/fullfeaturedsnippet/unpublish.html",
|
|
],
|
|
),
|
|
"override with index_template_name and index results template with namespaced template": (
|
|
"list",
|
|
[],
|
|
[
|
|
"tests/fullfeaturedsnippet_index.html",
|
|
"wagtailsnippets/snippets/tests/fullfeaturedsnippet/index_results.html",
|
|
],
|
|
),
|
|
"override index results template with namespaced template": (
|
|
# This is technically the same as the first case, but this ensures that
|
|
# the index results view can be overridden separately from the index view
|
|
"list_results",
|
|
[],
|
|
[
|
|
"wagtailsnippets/snippets/tests/fullfeaturedsnippet/index_results.html"
|
|
],
|
|
),
|
|
"override with get_history_template": (
|
|
"history",
|
|
[pk],
|
|
[
|
|
"tests/snippet_history.html",
|
|
],
|
|
),
|
|
}
|
|
for case, (view_name, args, template_names) in cases.items():
|
|
with self.subTest(case=case):
|
|
response = self.client.get(self.get_url(view_name, args=args))
|
|
for template_name in template_names:
|
|
self.assertTemplateUsed(response, template_name)
|
|
self.assertContains(response, "<p>An added paragraph</p>", html=True)
|
|
|
|
|
|
class TestCustomQuerySet(BaseSnippetViewSetTests):
|
|
model = FullFeaturedSnippet
|
|
|
|
@classmethod
|
|
def setUpTestData(cls):
|
|
default_locale = Locale.get_default()
|
|
objects = [
|
|
cls.model(text="FooSnippet", country_code="ID", locale=default_locale),
|
|
cls.model(text="BarSnippet", country_code="UK", locale=default_locale),
|
|
cls.model(text="[HIDDEN]Snippet", country_code="ID", locale=default_locale),
|
|
]
|
|
cls.model.objects.bulk_create(objects)
|
|
|
|
def test_index_view(self):
|
|
response = self.client.get(self.get_url("list"), {"country_code": "ID"})
|
|
self.assertContains(response, "FooSnippet")
|
|
self.assertNotContains(response, "BarSnippet")
|
|
self.assertNotContains(response, "[HIDDEN]Snippet")
|
|
|
|
|
|
class TestCustomOrdering(BaseSnippetViewSetTests):
|
|
model = FullFeaturedSnippet
|
|
|
|
@classmethod
|
|
def setUpTestData(cls):
|
|
default_locale = Locale.get_default()
|
|
objects = [
|
|
cls.model(text="CCCCCCCCCC", locale=default_locale),
|
|
cls.model(text="AAAAAAAAAA", locale=default_locale),
|
|
cls.model(text="DDDDDDDDDD", locale=default_locale),
|
|
cls.model(text="BBBBBBBBBB", locale=default_locale),
|
|
]
|
|
cls.model.objects.bulk_create(objects)
|
|
|
|
def test_index_view_order(self):
|
|
response = self.client.get(self.get_url("list"))
|
|
# Should sort by text in descending order as specified in SnippetViewSet.ordering
|
|
# (not the default ordering of the model)
|
|
self.assertFalse(self.model._meta.ordering)
|
|
self.assertEqual(
|
|
[obj.text for obj in response.context["page_obj"]],
|
|
[
|
|
"AAAAAAAAAA",
|
|
"BBBBBBBBBB",
|
|
"CCCCCCCCCC",
|
|
"DDDDDDDDDD",
|
|
],
|
|
)
|
|
|
|
|
|
class TestDjangoORMSearchBackend(BaseSnippetViewSetTests):
|
|
model = DraftStateModel
|
|
|
|
@classmethod
|
|
def setUpTestData(cls):
|
|
cls.first = cls.model.objects.create(
|
|
text="Wagtail is a Django-based CMS",
|
|
)
|
|
cls.second = cls.model.objects.create(
|
|
text="Django is a Python-based web framework",
|
|
)
|
|
cls.third = cls.model.objects.create(
|
|
text="Python is a programming-bas, uh, language",
|
|
)
|
|
|
|
def get(self, params={}, url_name="list"):
|
|
return self.client.get(self.get_url(url_name), params)
|
|
|
|
def test_simple(self):
|
|
response = self.get()
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertTemplateUsed(response, "wagtailsnippets/snippets/index.html")
|
|
|
|
# All objects should be in items
|
|
self.assertCountEqual(
|
|
list(response.context["page_obj"].object_list),
|
|
[self.first, self.second, self.third],
|
|
)
|
|
|
|
# The search box should not raise an error
|
|
self.assertNotContains(response, "This field is required.")
|
|
|
|
def test_empty_q(self):
|
|
response = self.get({"q": ""})
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertTemplateUsed(response, "wagtailsnippets/snippets/index.html")
|
|
|
|
# All objects should be in items
|
|
self.assertCountEqual(
|
|
list(response.context["page_obj"].object_list),
|
|
[self.first, self.second, self.third],
|
|
)
|
|
|
|
# The search box should not raise an error
|
|
self.assertNotContains(response, "This field is required.")
|
|
|
|
def test_is_searchable(self):
|
|
self.assertTrue(self.get().context["is_searchable"])
|
|
|
|
def test_search_index_view(self):
|
|
response = self.get({"q": "Django"})
|
|
|
|
# Only objects with "Django" should be in items
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertCountEqual(
|
|
list(response.context["page_obj"].object_list),
|
|
[self.first, self.second],
|
|
)
|
|
|
|
def test_search_index_results_view(self):
|
|
response = self.get({"q": "Python"}, url_name="list_results")
|
|
|
|
# Only objects with "Python" should be in items
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertCountEqual(
|
|
list(response.context["object_list"]),
|
|
[self.second, self.third],
|
|
)
|
|
|
|
|
|
class TestMenuItemRegistration(BaseSnippetViewSetTests):
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.request = get_dummy_request()
|
|
self.request.user = self.user
|
|
|
|
def test_add_to_admin_menu(self):
|
|
self.model = FullFeaturedSnippet
|
|
menu_items = admin_menu.render_component(self.request)
|
|
item = menu_items[-1]
|
|
self.assertEqual(item.name, "fullfeatured")
|
|
self.assertEqual(item.label, "Full-Featured MenuItem")
|
|
self.assertEqual(item.icon_name, "cog")
|
|
self.assertEqual(item.url, self.get_url("list"))
|
|
|
|
def test_add_to_settings_menu(self):
|
|
self.model = DraftStateModel
|
|
menu_items = settings_menu.render_component(self.request)
|
|
item = menu_items[0]
|
|
self.assertEqual(item.name, "publishables")
|
|
self.assertEqual(item.label, "Publishables")
|
|
self.assertEqual(item.icon_name, "snippet")
|
|
self.assertEqual(item.url, self.get_url("list"))
|
|
|
|
def test_group_registration(self):
|
|
menu_items = admin_menu.render_component(self.request)
|
|
revisables = [item for item in menu_items if item.name == "revisables"]
|
|
self.assertEqual(len(revisables), 1)
|
|
|
|
group_item = revisables[0]
|
|
self.assertEqual(group_item.label, "Revisables")
|
|
self.assertEqual(group_item.icon_name, "tasks")
|
|
self.assertEqual(len(group_item.menu_items), 2)
|
|
|
|
self.model = RevisableModel
|
|
revisable_item = group_item.menu_items[0]
|
|
self.assertEqual(revisable_item.name, "revisable-models")
|
|
self.assertEqual(revisable_item.label, "Revisable Models")
|
|
self.assertEqual(revisable_item.icon_name, "snippet")
|
|
self.assertEqual(revisable_item.url, self.get_url("list"))
|
|
|
|
self.model = RevisableChildModel
|
|
revisable_child_item = group_item.menu_items[1]
|
|
self.assertEqual(revisable_child_item.name, "revisable-child-models")
|
|
self.assertEqual(revisable_child_item.label, "Revisable Child Models")
|
|
self.assertEqual(revisable_child_item.icon_name, "snippet")
|
|
self.assertEqual(revisable_child_item.url, self.get_url("list"))
|
|
|
|
def test_limited_permissions(self):
|
|
self.user.is_superuser = False
|
|
self.user.user_permissions.add(
|
|
Permission.objects.get(
|
|
content_type__app_label="wagtailadmin", codename="access_admin"
|
|
)
|
|
)
|
|
self.user.save()
|
|
|
|
menu_items = admin_menu.render_component(self.request)
|
|
|
|
# The menu items should not be present
|
|
item = [
|
|
item
|
|
for item in menu_items
|
|
if item.name in {"fullfeatured", "revisables", "publishables"}
|
|
]
|
|
self.assertEqual(len(item), 0)
|
|
|
|
def test_basic_permissions(self):
|
|
self.model = DraftStateModel
|
|
self.user.is_superuser = False
|
|
self.user.user_permissions.add(
|
|
Permission.objects.get(
|
|
content_type__app_label="wagtailadmin", codename="access_admin"
|
|
)
|
|
)
|
|
self.user.save()
|
|
|
|
for action in ("add", "change", "delete"):
|
|
with self.subTest(action=action):
|
|
permission = Permission.objects.get(
|
|
content_type__app_label=self.model._meta.app_label,
|
|
codename=get_permission_codename(action, self.model._meta),
|
|
)
|
|
self.user.user_permissions.add(permission)
|
|
|
|
menu_items = settings_menu.render_component(self.request)
|
|
item = menu_items[0]
|
|
self.assertEqual(item.name, "publishables")
|
|
self.assertEqual(item.label, "Publishables")
|
|
self.assertEqual(item.icon_name, "snippet")
|
|
self.assertEqual(item.url, self.get_url("list"))
|
|
|
|
self.user.user_permissions.remove(permission)
|
|
|
|
def test_snippets_menu_item_hidden_when_all_snippets_have_menu_item(self):
|
|
menu_items = admin_menu.menu_items_for_request(self.request)
|
|
snippets = [item for item in menu_items if item.name == "snippets"]
|
|
self.assertEqual(len(snippets), 1)
|
|
item = snippets[0]
|
|
self.assertEqual(item.name, "snippets")
|
|
self.assertEqual(item.label, "Snippets")
|
|
self.assertEqual(item.icon_name, "snippet")
|
|
self.assertEqual(item.url, reverse("wagtailsnippets:index"))
|
|
|
|
# Clear cached property
|
|
del item._all_have_menu_items
|
|
|
|
with mock.patch(
|
|
"wagtail.snippets.views.snippets.SnippetViewSet.get_menu_item_is_registered"
|
|
) as mock_registered:
|
|
mock_registered.return_value = True
|
|
menu_items = admin_menu.render_component(self.request)
|
|
snippets = [item for item in menu_items if item.name == "snippets"]
|
|
self.assertEqual(len(snippets), 0)
|
|
|
|
|
|
class TestCustomFormClass(BaseSnippetViewSetTests):
|
|
model = DraftStateModel
|
|
|
|
def test_get_form_class(self):
|
|
add_view = self.client.get(self.get_url("add"))
|
|
self.assertNotContains(add_view, '<input type="text" name="text"')
|
|
self.assertContains(add_view, '<textarea name="text"')
|
|
|
|
obj = self.model.objects.create(text="Hello World")
|
|
|
|
# The get_form_class has been overridden to replace the widget for the
|
|
# text field with a TextInput, but only for the edit view
|
|
edit_view = self.client.get(self.get_url("edit", args=(quote(obj.pk),)))
|
|
self.assertContains(edit_view, '<input type="text" name="text"')
|
|
self.assertNotContains(edit_view, '<textarea name="text"')
|
|
|
|
|
|
class TestInspectViewConfiguration(BaseSnippetViewSetTests):
|
|
model = FullFeaturedSnippet
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.viewset = self.model.snippet_viewset
|
|
self.object = self.model.objects.create(text="Perkedel", country_code="ID")
|
|
|
|
def test_enabled(self):
|
|
self.model = FullFeaturedSnippet
|
|
url = self.get_url("inspect", args=(quote(self.object.pk),))
|
|
response = self.client.get(url)
|
|
self.assertContains(
|
|
response,
|
|
"<dt>Text</dt> <dd>Perkedel</dd>",
|
|
html=True,
|
|
)
|
|
self.assertContains(
|
|
response,
|
|
"<dt>Country code</dt> <dd>Indonesia</dd>",
|
|
html=True,
|
|
)
|
|
self.assertContains(
|
|
response,
|
|
f"<dt>Some date</dt> <dd>{date(self.object.some_date)}</dd>",
|
|
html=True,
|
|
)
|
|
self.assertNotContains(
|
|
response,
|
|
"<dt>Some attribute</dt> <dd>some value</dd>",
|
|
html=True,
|
|
)
|
|
self.assertContains(
|
|
response,
|
|
self.get_url("edit", args=(quote(self.object.pk),)),
|
|
)
|
|
self.assertContains(
|
|
response,
|
|
self.get_url("delete", args=(quote(self.object.pk),)),
|
|
)
|
|
|
|
def test_disabled(self):
|
|
self.model = Advert
|
|
object = self.model.objects.create(text="ad")
|
|
with self.assertRaises(NoReverseMatch):
|
|
self.get_url("inspect", args=(quote(object.pk),))
|
|
|
|
def test_only_add_permission(self):
|
|
self.model = FullFeaturedSnippet
|
|
|
|
self.user.is_superuser = False
|
|
self.user.user_permissions.add(
|
|
Permission.objects.get(
|
|
content_type__app_label="wagtailadmin", codename="access_admin"
|
|
),
|
|
Permission.objects.get(
|
|
content_type__app_label=self.model._meta.app_label,
|
|
codename=get_permission_codename("add", self.model._meta),
|
|
),
|
|
)
|
|
self.user.save()
|
|
|
|
url = self.get_url("inspect", args=(quote(self.object.pk),))
|
|
response = self.client.get(url)
|
|
|
|
self.assertContains(
|
|
response,
|
|
"<dt>Text</dt> <dd>Perkedel</dd>",
|
|
html=True,
|
|
)
|
|
self.assertContains(
|
|
response,
|
|
"<dt>Country code</dt> <dd>Indonesia</dd>",
|
|
html=True,
|
|
)
|
|
self.assertContains(
|
|
response,
|
|
f"<dt>Some date</dt> <dd>{date(self.object.some_date)}</dd>",
|
|
html=True,
|
|
)
|
|
self.assertNotContains(
|
|
response,
|
|
self.get_url("edit", args=(quote(self.object.pk),)),
|
|
)
|
|
self.assertNotContains(
|
|
response,
|
|
self.get_url("delete", args=(quote(self.object.pk),)),
|
|
)
|
|
|
|
def test_custom_fields(self):
|
|
self.model = FullFeaturedSnippet
|
|
url = self.get_url("inspect", args=(quote(self.object.pk),))
|
|
view_func = resolve(url).func
|
|
|
|
adverts = [Advert.objects.create(text=f"advertisement {i}") for i in range(3)]
|
|
queryset = Advert.objects.filter(pk=adverts[0].pk)
|
|
|
|
mock_manager = mock.patch.object(
|
|
self.model, "adverts", Advert.objects, create=True
|
|
)
|
|
|
|
mock_queryset = mock.patch.object(
|
|
self.model, "some_queryset", queryset, create=True
|
|
)
|
|
|
|
mock_fields = mock.patch.dict(
|
|
view_func.view_initkwargs,
|
|
{
|
|
"fields": [
|
|
"country_code", # Field with choices (thus get_FOO_display method)
|
|
"some_date", # DateField
|
|
"some_attribute", # Model attribute
|
|
"adverts", # Manager
|
|
"some_queryset", # QuerySet
|
|
]
|
|
},
|
|
)
|
|
|
|
# We need to mock the view's init kwargs instead of the viewset's
|
|
# attributes, because the viewset's attributes are only used when the
|
|
# view is instantiated, and the view is instantiated once at startup.
|
|
with mock_manager, mock_queryset, mock_fields:
|
|
response = self.client.get(url)
|
|
|
|
self.assertNotContains(
|
|
response,
|
|
"<dt>Text</dt> <dd>Perkedel</dd>",
|
|
html=True,
|
|
)
|
|
self.assertContains(
|
|
response,
|
|
"<dt>Country code</dt> <dd>Indonesia</dd>",
|
|
html=True,
|
|
)
|
|
self.assertContains(
|
|
response,
|
|
f"<dt>Some date</dt> <dd>{date(self.object.some_date)}</dd>",
|
|
html=True,
|
|
)
|
|
self.assertContains(
|
|
response,
|
|
"<dt>Some attribute</dt> <dd>some value</dd>",
|
|
html=True,
|
|
)
|
|
self.assertContains(
|
|
response,
|
|
"""
|
|
<dt>Adverts</dt>
|
|
<dd>advertisement 0, advertisement 1, advertisement 2</dd>
|
|
""",
|
|
html=True,
|
|
)
|
|
self.assertContains(
|
|
response,
|
|
"<dt>Some queryset</dt> <dd>advertisement 0</dd>",
|
|
html=True,
|
|
)
|
|
|
|
def test_exclude_fields(self):
|
|
self.model = FullFeaturedSnippet
|
|
url = self.get_url("inspect", args=(quote(self.object.pk),))
|
|
view_func = resolve(url).func
|
|
|
|
# We need to mock the view's init kwargs instead of the viewset's
|
|
# attributes, because the viewset's attributes are only used when the
|
|
# view is instantiated, and the view is instantiated once at startup.
|
|
with mock.patch.dict(
|
|
view_func.view_initkwargs,
|
|
{"fields_exclude": ["some_date"]},
|
|
):
|
|
response = self.client.get(url)
|
|
|
|
self.assertContains(
|
|
response,
|
|
"<dt>Text</dt> <dd>Perkedel</dd>",
|
|
html=True,
|
|
)
|
|
self.assertContains(
|
|
response,
|
|
"<dt>Country code</dt> <dd>Indonesia</dd>",
|
|
html=True,
|
|
)
|
|
self.assertNotContains(
|
|
response,
|
|
f"<dt>Some date</dt> <dd>{date(self.object.some_date)}</dd>",
|
|
html=True,
|
|
)
|
|
self.assertNotContains(
|
|
response,
|
|
"<dt>Some attribute</dt> <dd>some value</dd>",
|
|
html=True,
|
|
)
|
|
|
|
def test_image_and_document_fields(self):
|
|
self.model = VariousOnDeleteModel
|
|
image = get_image_model().objects.create(
|
|
title="Test image",
|
|
file=get_test_image_file(),
|
|
)
|
|
document = get_document_model().objects.create(
|
|
title="Test document", file=get_test_document_file()
|
|
)
|
|
object = self.model.objects.create(
|
|
protected_image=image, protected_document=document
|
|
)
|
|
response = self.client.get(self.get_url("inspect", args=(quote(object.pk),)))
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertContains(
|
|
response,
|
|
f"<dt>Protected image</dt> <dd>{image.get_rendition('max-400x400').img_tag()}</dd>",
|
|
html=True,
|
|
)
|
|
self.assertContains(response, "<dt>Protected document</dt>", html=True)
|
|
self.assertContains(response, f'<a href="{document.url}">')
|
|
self.assertContains(response, "Test document")
|
|
self.assertContains(response, "TXT")
|
|
self.assertContains(response, f"{document.file.size}\xa0bytes")
|
|
|
|
def test_image_and_document_fields_none_values(self):
|
|
self.model = VariousOnDeleteModel
|
|
object = self.model.objects.create()
|
|
response = self.client.get(self.get_url("inspect", args=(quote(object.pk),)))
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertContains(
|
|
response,
|
|
"<dt>Protected image</dt> <dd>None</dd>",
|
|
html=True,
|
|
)
|
|
self.assertContains(
|
|
response,
|
|
"<dt>Protected document</dt> <dd>None</dd>",
|
|
html=True,
|
|
)
|
|
|
|
|
|
class TestBreadcrumbs(AdminTemplateTestUtils, BaseSnippetViewSetTests):
|
|
model = FullFeaturedSnippet
|
|
base_breadcrumb_items = AdminTemplateTestUtils.base_breadcrumb_items + [
|
|
{"label": "Snippets", "url": "/admin/snippets/"},
|
|
]
|
|
|
|
@classmethod
|
|
def setUpTestData(cls):
|
|
cls.object = cls.model.objects.create(text="Hello World")
|
|
|
|
def test_index_view(self):
|
|
response = self.client.get(self.get_url("list"))
|
|
items = [{"url": "", "label": "Full-featured snippets"}]
|
|
self.assertBreadcrumbsItemsRendered(items, response.content)
|
|
|
|
def test_add_view(self):
|
|
response = self.client.get(self.get_url("add"))
|
|
items = [
|
|
{
|
|
"url": self.get_url("list"),
|
|
"label": "Full-featured snippets",
|
|
},
|
|
{"url": "", "label": "New: Full-featured snippet"},
|
|
]
|
|
self.assertBreadcrumbsItemsRendered(items, response.content)
|
|
|
|
def test_edit_view(self):
|
|
response = self.client.get(self.get_url("edit", args=(self.object.pk,)))
|
|
items = [
|
|
{
|
|
"url": self.get_url("list"),
|
|
"label": "Full-featured snippets",
|
|
},
|
|
{"url": "", "label": str(self.object)},
|
|
]
|
|
self.assertBreadcrumbsItemsRendered(items, response.content)
|
|
|
|
def test_delete_view(self):
|
|
response = self.client.get(self.get_url("delete", args=(self.object.pk,)))
|
|
self.assertBreadcrumbsNotRendered(response.content)
|
|
|
|
def test_history_view(self):
|
|
response = self.client.get(self.get_url("history", args=(self.object.pk,)))
|
|
items = [
|
|
{
|
|
"url": self.get_url("list"),
|
|
"label": "Full-featured snippets",
|
|
},
|
|
{
|
|
"url": self.get_url("edit", args=(self.object.pk,)),
|
|
"label": str(self.object),
|
|
},
|
|
{"url": "", "label": "History", "sublabel": str(self.object)},
|
|
]
|
|
self.assertBreadcrumbsItemsRendered(items, response.content)
|
|
|
|
def test_usage_view(self):
|
|
response = self.client.get(self.get_url("usage", args=(self.object.pk,)))
|
|
items = [
|
|
{
|
|
"url": self.get_url("list"),
|
|
"label": "Full-featured snippets",
|
|
},
|
|
{
|
|
"url": self.get_url("edit", args=(self.object.pk,)),
|
|
"label": str(self.object),
|
|
},
|
|
{"url": "", "label": "Usage", "sublabel": str(self.object)},
|
|
]
|
|
self.assertBreadcrumbsItemsRendered(items, response.content)
|
|
|
|
def test_inspect_view(self):
|
|
response = self.client.get(self.get_url("inspect", args=(self.object.pk,)))
|
|
items = [
|
|
{
|
|
"url": self.get_url("list"),
|
|
"label": "Full-featured snippets",
|
|
},
|
|
{
|
|
"url": self.get_url("edit", args=(self.object.pk,)),
|
|
"label": str(self.object),
|
|
},
|
|
{"url": "", "label": "Inspect", "sublabel": str(self.object)},
|
|
]
|
|
self.assertBreadcrumbsItemsRendered(items, response.content)
|
|
|
|
|
|
class TestCustomMethods(BaseSnippetViewSetTests):
|
|
model = FullFeaturedSnippet
|
|
|
|
def test_index_view_get_add_url_is_respected(self):
|
|
response = self.client.get(self.get_url("list"))
|
|
add_url = self.get_url("add") + "?customised=param"
|
|
soup = self.get_soup(response.content)
|
|
links = soup.find_all("a", attrs={"href": add_url})
|
|
self.assertEqual(len(links), 2)
|
|
|
|
@override_settings(WAGTAIL_I18N_ENABLED=True)
|
|
def test_index_view_get_add_url_is_respected_with_i18n(self):
|
|
Locale.objects.create(language_code="fr")
|
|
response = self.client.get(self.get_url("list") + "?locale=fr")
|
|
add_url = self.get_url("add") + "?locale=fr&customised=param"
|
|
soup = self.get_soup(response.content)
|
|
links = soup.find_all("a", attrs={"href": add_url})
|
|
self.assertEqual(len(links), 1)
|
|
|
|
def test_index_results_view_get_add_url_teleports_to_header(self):
|
|
response = self.client.get(self.get_url("list_results"))
|
|
add_url = self.get_url("add") + "?customised=param"
|
|
soup = self.get_soup(response.content)
|
|
template = soup.find(
|
|
"template",
|
|
{
|
|
"data-controller": "w-teleport",
|
|
"data-w-teleport-target-value": "#w-slim-header-buttons",
|
|
},
|
|
)
|
|
self.assertIsNotNone(template)
|
|
links = template.find_all("a", attrs={"href": add_url})
|
|
self.assertEqual(len(links), 1)
|
|
|
|
@override_settings(WAGTAIL_I18N_ENABLED=True)
|
|
def test_index_results_view_get_add_url_teleports_to_header_with_i18n(self):
|
|
Locale.objects.create(language_code="fr")
|
|
response = self.client.get(self.get_url("list_results") + "?locale=fr")
|
|
add_url = self.get_url("add") + "?locale=fr&customised=param"
|
|
soup = self.get_soup(response.content)
|
|
template = soup.find(
|
|
"template",
|
|
{
|
|
"data-controller": "w-teleport",
|
|
"data-w-teleport-target-value": "#w-slim-header-buttons",
|
|
},
|
|
)
|
|
self.assertIsNotNone(template)
|
|
links = template.find_all("a", attrs={"href": add_url})
|
|
self.assertEqual(len(links), 1)
|
|
|
|
|
|
class TestCustomPermissionPolicy(BaseSnippetViewSetTests):
|
|
model = FullFeaturedSnippet
|
|
|
|
@classmethod
|
|
def setUpTestData(cls):
|
|
cls.object = cls.model.objects.create(text="Hello World")
|
|
|
|
def test_get_edit_view_not_allowed(self):
|
|
response = self.client.get(self.get_url("edit", args=(quote(self.object.pk),)))
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
# The custom permission policy disallows any user with [FORBIDDEN]
|
|
# in their name, even if they are a superuser
|
|
self.user.first_name = "[FORBIDDEN]"
|
|
self.user.last_name = "Joe"
|
|
self.user.save()
|
|
self.assertTrue(self.user.is_superuser)
|
|
self.assertEqual(self.user.get_full_name(), "[FORBIDDEN] Joe")
|
|
response = self.client.get(self.get_url("edit", args=(quote(self.object.pk),)))
|
|
self.assertRedirects(response, reverse("wagtailadmin_home"))
|