123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425 |
- import datetime
- from io import BytesIO
- 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.test import TestCase
- from django.urls import NoReverseMatch, reverse
- from django.utils.formats import date_format, localize
- from django.utils.html import escape
- from django.utils.timezone import make_aware
- from openpyxl import load_workbook
- from wagtail.admin.admin_url_finder import AdminURLFinder
- from wagtail.models import ModelLogEntry
- from wagtail.test.testapp.models import (
- FeatureCompleteToy,
- JSONStreamModel,
- SearchTestModel,
- VariousOnDeleteModel,
- )
- from wagtail.test.utils.template_tests import AdminTemplateTestUtils
- from wagtail.test.utils.wagtail_tests import WagtailTestUtils
- from wagtail.utils.deprecation import RemovedInWagtail70Warning
- class TestModelViewSetGroup(WagtailTestUtils, TestCase):
- def setUp(self):
- self.user = self.login()
- def test_menu_items(self):
- response = self.client.get(reverse("wagtailadmin_home"))
- self.assertEqual(response.status_code, 200)
- # Menu label falls back to the title-cased app label
- self.assertContains(
- response,
- '"name": "tests", "label": "Tests", "icon_name": "folder-open-inverse"',
- )
- # Title-cased from verbose_name_plural
- self.assertContains(response, "Json Stream Models")
- self.assertContains(response, reverse("streammodel:index"))
- self.assertEqual(reverse("streammodel:index"), "/admin/streammodel/")
- # Set on class
- self.assertContains(response, "JSON MinMaxCount StreamModel")
- self.assertContains(response, reverse("minmaxcount_streammodel:index"))
- self.assertEqual(
- reverse("minmaxcount_streammodel:index"),
- "/admin/minmaxcount-streammodel/",
- )
- # Set on instance
- self.assertContains(response, "JSON BlockCounts StreamModel")
- self.assertContains(response, reverse("blockcounts_streammodel:index"))
- self.assertEqual(
- reverse("blockcounts_streammodel:index"),
- "/admin/blockcounts/streammodel/",
- )
- class TestTemplateConfiguration(WagtailTestUtils, TestCase):
- def setUp(self):
- self.user = self.login()
- @classmethod
- def setUpTestData(cls):
- cls.default = JSONStreamModel.objects.create(
- body='[{"type": "text", "value": "foo"}]',
- )
- cls.custom = FeatureCompleteToy.objects.create(name="Test Toy")
- def get_default_url(self, view_name, args=()):
- return reverse(f"streammodel:{view_name}", args=args)
- def get_custom_url(self, view_name, args=()):
- return reverse(f"feature_complete_toy:{view_name}", args=args)
- def test_default_templates(self):
- pk = quote(self.default.pk)
- cases = {
- "index": (
- [],
- "wagtailadmin/generic/index.html",
- ),
- "index_results": (
- [],
- "wagtailadmin/generic/listing_results.html",
- ),
- "add": (
- [],
- "wagtailadmin/generic/create.html",
- ),
- "edit": (
- [pk],
- "wagtailadmin/generic/edit.html",
- ),
- "delete": (
- [pk],
- "wagtailadmin/generic/confirm_delete.html",
- ),
- }
- for view_name, (args, template_name) in cases.items():
- with self.subTest(view_name=view_name):
- response = self.client.get(self.get_default_url(view_name, args=args))
- self.assertTemplateUsed(response, template_name)
- def test_custom_template_lookups(self):
- pk = quote(self.custom.pk)
- cases = {
- "override with index_template_name": (
- "index",
- [],
- "tests/fctoy_index.html",
- ),
- "with app label and model name": (
- "add",
- [],
- "customprefix/tests/featurecompletetoy/create.html",
- ),
- "with app label": (
- "edit",
- [pk],
- "customprefix/tests/edit.html",
- ),
- "without app label and model name": (
- "delete",
- [pk],
- "customprefix/confirm_delete.html",
- ),
- }
- for case, (view_name, args, template_name) in cases.items():
- with self.subTest(case=case):
- response = self.client.get(self.get_custom_url(view_name, args=args))
- self.assertTemplateUsed(response, template_name)
- self.assertContains(
- response, "<p>Some extra custom content</p>", html=True
- )
- def test_wagtail_admin_template_mixin_variables_with_legacy_header(self):
- pk = quote(self.custom.pk)
- cases = {
- "delete": ([pk], "Delete", str(self.custom)),
- }
- for view_name, (args, title, subtitle) in cases.items():
- with self.subTest(view_name=view_name):
- response = self.client.get(self.get_custom_url(view_name, args=args))
- soup = self.get_soup(response.content)
- h1 = soup.select_one("h1")
- self.assertIsNotNone(h1)
- self.assertEqual(
- "".join(h1.find_all(string=True, recursive=False)).strip(), title
- )
- subtitle_el = h1.select_one("span")
- if subtitle:
- self.assertIsNotNone(subtitle_el)
- self.assertEqual(subtitle_el.string, subtitle)
- else:
- self.assertIsNone(subtitle_el)
- icon = h1.select_one("svg use[href='#icon-media']")
- self.assertIsNotNone(icon)
- def test_wagtail_admin_template_mixin_variables(self):
- pk = quote(self.custom.pk)
- cases = {
- "index": ([], "Feature complete toys", None),
- "add": ([], "New", "Feature complete toy"),
- "edit": ([pk], "Editing", str(self.custom)),
- }
- for view_name, (args, title, subtitle) in cases.items():
- with self.subTest(view_name=view_name):
- response = self.client.get(self.get_custom_url(view_name, args=args))
- soup = self.get_soup(response.content)
- h1 = soup.select_one("h1")
- expected_h1 = title
- if subtitle:
- expected_h1 = f"{title}: {subtitle}"
- self.assertIsNotNone(h1)
- self.assertEqual(h1.get_text(strip=True), expected_h1)
- icon = h1.select_one("svg use[href='#icon-media']")
- # Icon is no longer rendered in the h1 with the slim header in place
- self.assertIsNone(icon)
- class TestCustomColumns(WagtailTestUtils, TestCase):
- def setUp(self):
- self.user = self.login()
- @classmethod
- def setUpTestData(cls):
- FeatureCompleteToy.objects.create(name="Racecar")
- FeatureCompleteToy.objects.create(name="level")
- FeatureCompleteToy.objects.create(name="Lotso")
- def test_list_display(self):
- index_url = reverse("feature_complete_toy:index")
- response = self.client.get(index_url)
- # "name" column
- self.assertContains(response, "Racecar")
- self.assertContains(response, "level")
- self.assertContains(response, "Lotso")
- # BooleanColumn("is_cool")
- soup = self.get_soup(response.content)
- help = soup.select_one("td:has(svg.icon-help)")
- self.assertIsNotNone(help)
- self.assertEqual(help.text.strip(), "None")
- success = soup.select_one("td:has(svg.icon-success.w-text-positive-100)")
- self.assertIsNotNone(success)
- self.assertEqual(success.text.strip(), "True")
- error = soup.select_one("td:has(svg.icon-error.w-text-critical-100)")
- self.assertIsNotNone(error)
- self.assertEqual(error.text.strip(), "False")
- updated_at = soup.select("th a")[-1]
- self.assertEqual(updated_at.text.strip(), "Updated")
- self.assertEqual(updated_at["href"], f"{index_url}?ordering=_updated_at")
- class TestListFilter(WagtailTestUtils, TestCase):
- cases = {
- "list": ("feature_complete_toy", "release_date", "Release date"),
- "dict": ("fctoy_alt1", "name__icontains", "Name contains"),
- "filterset_class": (
- "fctoy-alt2",
- "release_date__year__lte",
- "Release date year is less than or equal to",
- ),
- }
- def setUp(self):
- self.user = self.login()
- def get(self, url_namespace, params=None):
- return self.client.get(reverse(f"{url_namespace}:index"), params)
- @classmethod
- def setUpTestData(cls):
- FeatureCompleteToy.objects.create(
- name="Buzz Lightyear",
- release_date=datetime.date(1995, 11, 19),
- )
- FeatureCompleteToy.objects.create(
- name="Forky",
- release_date=datetime.date(2019, 6, 11),
- )
- def test_unfiltered_no_results(self):
- FeatureCompleteToy.objects.all().delete()
- for case, (url_namespace, lookup, label_text) in self.cases.items():
- with self.subTest(case=case):
- response = self.get(url_namespace)
- self.assertContains(
- response,
- "There are no feature complete toys to display",
- )
- self.assertNotContains(
- response,
- "No feature complete toys match your query",
- )
- self.assertNotContains(response, "Buzz Lightyear")
- self.assertNotContains(response, "Forky")
- soup = self.get_soup(response.content)
- label = soup.select_one(f"label#id_{lookup}-label")
- self.assertIsNotNone(label)
- self.assertEqual(label.string.strip(), label_text)
- input = soup.select_one(f"input#id_{lookup}")
- self.assertIsNotNone(input)
- def test_unfiltered_with_results(self):
- for case, (url_namespace, lookup, label_text) in self.cases.items():
- with self.subTest(case=case):
- response = self.get(url_namespace)
- self.assertContains(response, "Buzz Lightyear")
- self.assertContains(response, "Forky")
- self.assertNotContains(response, "There are 2 matches")
- self.assertNotContains(
- response,
- "There are no feature complete toys to display",
- )
- self.assertNotContains(
- response,
- "No feature complete toys match your query",
- )
- soup = self.get_soup(response.content)
- label = soup.select_one(f"label#id_{lookup}-label")
- self.assertIsNotNone(label)
- self.assertEqual(label.string.strip(), label_text)
- input = soup.select_one(f"input#id_{lookup}")
- self.assertIsNotNone(input)
- def test_empty_filter_with_results(self):
- for case, (url_namespace, lookup, label_text) in self.cases.items():
- with self.subTest(case=case):
- response = self.get(url_namespace, {lookup: ""})
- self.assertContains(response, "Buzz Lightyear")
- self.assertContains(response, "Forky")
- self.assertNotContains(response, "There are 2 matches")
- self.assertNotContains(
- response,
- "No feature complete toys match your query",
- )
- soup = self.get_soup(response.content)
- label = soup.select_one(f"label#id_{lookup}-label")
- self.assertIsNotNone(label)
- self.assertEqual(label.string.strip(), label_text)
- input = soup.select_one(f"input#id_{lookup}")
- self.assertIsNotNone(input)
- self.assertFalse(input.attrs.get("value"))
- def test_filtered_no_results(self):
- lookup_values = {
- "release_date": "1999-09-09",
- "name__icontains": "Woody",
- "release_date__year__lte": "1990",
- }
- for case, (url_namespace, lookup, label_text) in self.cases.items():
- with self.subTest(case=case):
- value = lookup_values[lookup]
- response = self.get(url_namespace, {lookup: value})
- self.assertContains(
- response,
- "No feature complete toys match your query",
- )
- self.assertNotContains(response, "Buzz Lightyear")
- self.assertNotContains(response, "Forky")
- self.assertNotContains(response, "There are 2 matches")
- soup = self.get_soup(response.content)
- label = soup.select_one(f"label#id_{lookup}-label")
- self.assertIsNotNone(label)
- self.assertEqual(label.string.strip(), label_text)
- input = soup.select_one(f"input#id_{lookup}")
- self.assertIsNotNone(input)
- self.assertEqual(input.attrs.get("value"), value)
- # Should render the active filters even when there are no results
- 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, reverse(f"{url_namespace}:index_results"))
- self.assertNotIn(f"{lookup}={value}", params)
- def test_filtered_with_results(self):
- lookup_values = {
- "release_date": "1995-11-19",
- "name__icontains": "Ightyear",
- "release_date__year__lte": "2017",
- }
- for case, (url_namespace, lookup, label_text) in self.cases.items():
- with self.subTest(case=case):
- value = lookup_values[lookup]
- response = self.get(url_namespace, {lookup: value})
- self.assertContains(response, "Buzz Lightyear")
- self.assertContains(response, "There is 1 match")
- self.assertNotContains(response, "Forky")
- self.assertNotContains(
- response,
- "No feature complete toys match your query",
- )
- soup = self.get_soup(response.content)
- label = soup.select_one(f"label#id_{lookup}-label")
- self.assertIsNotNone(label)
- self.assertEqual(label.string.strip(), label_text)
- input = soup.select_one(f"input#id_{lookup}")
- self.assertIsNotNone(input)
- self.assertEqual(input.attrs.get("value"), value)
- # Should render the active filters
- 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, reverse(f"{url_namespace}:index_results"))
- self.assertNotIn(f"{lookup}={value}", params)
- class TestSearchIndexView(WagtailTestUtils, TestCase):
- url_name = "index"
- cases = {
- # With the default search backend
- "default": ("feature_complete_toy", "release_date"),
- # With Django ORM
- None: ("fctoy-alt2", "release_date__year__lte"),
- }
- def setUp(self):
- self.user = self.login()
- @classmethod
- def setUpTestData(cls):
- FeatureCompleteToy.objects.create(
- name="Buzz Lightyear",
- release_date=datetime.date(1995, 11, 19),
- )
- FeatureCompleteToy.objects.create(
- name="Forky",
- release_date=datetime.date(2019, 6, 11),
- )
- def assertInputRendered(self, response, search_q):
- soup = self.get_soup(response.content)
- input = soup.select_one("input#id_q")
- self.assertIsNotNone(input)
- self.assertEqual(input.attrs.get("value"), search_q)
- def get(self, url_namespace, params=None):
- return self.client.get(reverse(f"{url_namespace}:{self.url_name}"), params)
- def test_search_disabled(self):
- response = self.get("fctoy_alt1", {"q": "ork"})
- self.assertContains(response, "Forky")
- self.assertContains(response, "Buzz Lightyear")
- self.assertNotContains(response, "There are 2 matches")
- soup = self.get_soup(response.content)
- input = soup.select_one("input#id_q")
- self.assertIsNone(input)
- def test_search_no_results(self):
- for backend, (url_namespace, _) in self.cases.items():
- with self.subTest(backend=backend):
- response = self.get(url_namespace, {"q": "Woody"})
- self.assertContains(
- response,
- "No feature complete toys match your query",
- )
- self.assertNotContains(response, "Buzz Lightyear")
- self.assertNotContains(response, "Forky")
- self.assertInputRendered(response, "Woody")
- def test_search_with_results(self):
- for backend, (url_namespace, _) in self.cases.items():
- with self.subTest(backend=backend):
- response = self.get(url_namespace, {"q": "ork"})
- self.assertContains(response, "Forky")
- self.assertNotContains(response, "Buzz Lightyear")
- self.assertContains(response, "There is 1 match")
- self.assertInputRendered(response, "ork")
- def test_filtered_searched_no_results(self):
- lookup_values = {
- "release_date": "2019-06-11",
- "release_date__year__lte": "2023",
- }
- for backend, (url_namespace, lookup) in self.cases.items():
- with self.subTest(backend=backend):
- value = lookup_values[lookup]
- response = self.get(url_namespace, {"q": "Woody", lookup: value})
- self.assertContains(
- response,
- "No feature complete toys match your query",
- )
- self.assertNotContains(response, "Buzz Lightyear")
- self.assertNotContains(response, "Forky")
- self.assertInputRendered(response, "Woody")
- def test_filtered_searched_with_results(self):
- lookup_values = {
- "release_date": "2019-06-11",
- "release_date__year__lte": "2023",
- }
- for backend, (url_namespace, lookup) in self.cases.items():
- with self.subTest(backend=backend):
- value = lookup_values[lookup]
- response = self.get(url_namespace, {"q": "ork", lookup: value})
- self.assertContains(response, "Forky")
- self.assertNotContains(response, "Buzz Lightyear")
- self.assertContains(response, "There is 1 match")
- self.assertInputRendered(response, "ork")
- class TestSearchIndexResultsView(TestSearchIndexView):
- url_name = "index_results"
- def assertInputRendered(self, response, search_q):
- # index_results view doesn't render the search input
- pass
- class TestSearchFields(WagtailTestUtils, TestCase):
- @classmethod
- def setUpTestData(cls):
- objects = [
- SearchTestModel(title="Hello World", body="This one is classic"),
- SearchTestModel(title="Hello Anime", body="We love anime (opinions vary)"),
- SearchTestModel(title="Food", body="I like food, do you?"),
- ]
- SearchTestModel.objects.bulk_create(objects)
- def setUp(self):
- self.login()
- def get(self, q):
- return self.client.get(reverse("searchtest:index"), {"q": q})
- def test_single_result_with_body(self):
- response = self.get("IkE")
- self.assertEqual(response.status_code, 200)
- self.assertNotContains(response, "Hello World")
- self.assertNotContains(response, "Hello Anime")
- self.assertContains(response, "Food")
- def test_multiple_results_with_title(self):
- response = self.get("ELlo")
- self.assertEqual(response.status_code, 200)
- self.assertContains(response, "Hello World")
- self.assertContains(response, "Hello Anime")
- self.assertNotContains(response, "Food")
- def test_no_results(self):
- response = self.get("Abra Kadabra")
- self.assertEqual(response.status_code, 200)
- self.assertNotContains(response, "Hello World")
- self.assertNotContains(response, "Hello Anime")
- self.assertNotContains(response, "Food")
- class TestListExport(WagtailTestUtils, TestCase):
- def setUp(self):
- self.user = self.login()
- @classmethod
- def setUpTestData(cls):
- FeatureCompleteToy.objects.create(
- name="Racecar",
- release_date=datetime.date(1995, 11, 19),
- )
- FeatureCompleteToy.objects.create(
- name="LEVEL",
- release_date=datetime.date(2010, 6, 18),
- )
- FeatureCompleteToy.objects.create(
- name="Catso",
- release_date=datetime.date(2010, 6, 18),
- )
- def test_export_disabled(self):
- index_url = reverse("fctoy_alt1:index")
- response = self.client.get(index_url)
- soup = self.get_soup(response.content)
- csv_link = soup.select_one(f"a[href='{index_url}?export=csv']")
- self.assertIsNone(csv_link)
- xlsx_link = soup.select_one(f"a[href='{index_url}?export=xlsx']")
- self.assertIsNone(xlsx_link)
- def test_get_not_export_shows_export_buttons(self):
- index_url = reverse("feature_complete_toy:index")
- response = self.client.get(index_url)
- soup = self.get_soup(response.content)
- csv_link = soup.select_one(f"a[href='{index_url}?export=csv']")
- self.assertIsNotNone(csv_link)
- self.assertEqual(csv_link.text.strip(), "Download CSV")
- xlsx_link = soup.select_one(f"a[href='{index_url}?export=xlsx']")
- self.assertIsNotNone(xlsx_link)
- self.assertEqual(xlsx_link.text.strip(), "Download XLSX")
- def test_get_filtered_shows_export_buttons_with_filters(self):
- index_url = reverse("feature_complete_toy:index")
- response = self.client.get(index_url, {"release_date": "2010-06-18"})
- soup = self.get_soup(response.content)
- csv_link = soup.select_one(
- f"a[href='{index_url}?release_date=2010-06-18&export=csv']"
- )
- self.assertIsNotNone(csv_link)
- self.assertEqual(csv_link.text.strip(), "Download CSV")
- xlsx_link = soup.select_one(
- f"a[href='{index_url}?release_date=2010-06-18&export=xlsx']"
- )
- self.assertIsNotNone(xlsx_link)
- self.assertEqual(xlsx_link.text.strip(), "Download XLSX")
- def test_csv_export(self):
- index_url = reverse("feature_complete_toy:index")
- response = self.client.get(index_url, {"export": "csv"})
- self.assertEqual(response.status_code, 200)
- self.assertEqual(
- response.get("Content-Disposition"),
- 'attachment; filename="feature-complete-toys.csv"',
- )
- data_lines = response.getvalue().decode().strip().split("\r\n")
- self.assertEqual(data_lines[0], "Name,Launch date,Is cool")
- self.assertEqual(data_lines[1], "Catso,2010-06-18,False")
- self.assertEqual(data_lines[2], "LEVEL,2010-06-18,True")
- self.assertEqual(data_lines[3], "Racecar,1995-11-19,")
- self.assertEqual(len(data_lines), 4)
- def test_csv_export_filtered(self):
- index_url = reverse("feature_complete_toy:index")
- response = self.client.get(
- index_url,
- {"release_date": "2010-06-18", "export": "csv"},
- )
- self.assertEqual(response.status_code, 200)
- self.assertEqual(
- response.get("Content-Disposition"),
- 'attachment; filename="feature-complete-toys.csv"',
- )
- data_lines = response.getvalue().decode().strip().split("\r\n")
- self.assertEqual(data_lines[0], "Name,Launch date,Is cool")
- self.assertEqual(data_lines[1], "Catso,2010-06-18,False")
- self.assertEqual(data_lines[2], "LEVEL,2010-06-18,True")
- self.assertEqual(len(data_lines), 3)
- def test_xlsx_export(self):
- index_url = reverse("feature_complete_toy:index")
- response = self.client.get(index_url, {"export": "xlsx"})
- self.assertEqual(response.status_code, 200)
- self.assertEqual(
- response.get("Content-Disposition"),
- 'attachment; filename="feature-complete-toys.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], ["Name", "Launch date", "Is cool"])
- self.assertEqual(cell_array[1], ["Catso", datetime.date(2010, 6, 18), False])
- self.assertEqual(cell_array[2], ["LEVEL", datetime.date(2010, 6, 18), True])
- self.assertEqual(cell_array[3], ["Racecar", datetime.date(1995, 11, 19), None])
- self.assertEqual(len(cell_array), 4)
- def test_xlsx_export_filtered(self):
- index_url = reverse("feature_complete_toy:index")
- response = self.client.get(
- index_url,
- {"release_date": "2010-06-18", "export": "xlsx"},
- )
- self.assertEqual(response.status_code, 200)
- self.assertEqual(
- response.get("Content-Disposition"),
- 'attachment; filename="feature-complete-toys.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], ["Name", "Launch date", "Is cool"])
- self.assertEqual(cell_array[1], ["Catso", datetime.date(2010, 6, 18), False])
- self.assertEqual(cell_array[2], ["LEVEL", datetime.date(2010, 6, 18), True])
- self.assertEqual(len(cell_array), 3)
- class TestPagination(WagtailTestUtils, TestCase):
- def setUp(self):
- self.user = self.login()
- @classmethod
- def setUpTestData(cls):
- objects = [FeatureCompleteToy(name=f"Frisbee {i}") for i in range(32)]
- FeatureCompleteToy.objects.bulk_create(objects)
- def test_default_list_pagination(self):
- list_url = reverse("fctoy_alt1:index")
- response = self.client.get(list_url)
- # Default is 20 per page
- self.assertEqual(FeatureCompleteToy.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("feature_complete_toy:index")
- response = self.client.get(list_url)
- # Custom is set to display 5 per page
- self.assertEqual(FeatureCompleteToy.objects.all().count(), 32)
- self.assertContains(response, "Page 1 of 7")
- self.assertContains(response, "Next")
- self.assertContains(response, list_url + "?p=2")
- class TestOrdering(WagtailTestUtils, TestCase):
- def setUp(self):
- self.user = self.login()
- @classmethod
- def setUpTestData(cls):
- objects = [
- FeatureCompleteToy(name="CCCCCCCCCC", strid="1"),
- FeatureCompleteToy(name="AAAAAAAAAA", strid="2"),
- FeatureCompleteToy(name="DDDDDDDDDD", strid="3"),
- FeatureCompleteToy(name="BBBBBBBBBB", strid="4"),
- ]
- FeatureCompleteToy.objects.bulk_create(objects)
- def test_default_order(self):
- response = self.client.get(reverse("fctoy_alt1:index"))
- # Without ordering on the model, should be ordered descending by pk
- self.assertFalse(FeatureCompleteToy._meta.ordering)
- self.assertEqual(
- [obj.name for obj in response.context["object_list"]],
- [
- "BBBBBBBBBB",
- "DDDDDDDDDD",
- "AAAAAAAAAA",
- "CCCCCCCCCC",
- ],
- )
- def test_custom_order_from_query_args(self):
- response = self.client.get(reverse("fctoy-alt3:index") + "?ordering=-name")
- self.assertFalse(FeatureCompleteToy._meta.ordering)
- self.assertEqual(
- [obj.name for obj in response.context["object_list"]],
- [
- "DDDDDDDDDD",
- "CCCCCCCCCC",
- "BBBBBBBBBB",
- "AAAAAAAAAA",
- ],
- )
- def test_custom_order_from_view(self):
- response = self.client.get(reverse("feature_complete_toy:index"))
- # Should respect the view's ordering
- self.assertFalse(FeatureCompleteToy._meta.ordering)
- self.assertEqual(
- [obj.name for obj in response.context["object_list"]],
- [
- "AAAAAAAAAA",
- "BBBBBBBBBB",
- "CCCCCCCCCC",
- "DDDDDDDDDD",
- ],
- )
- def test_custom_order_from_from_viewset(self):
- response = self.client.get(reverse("fctoy-alt3:index"))
- # The view has an ordering but it is overwritten by the viewset
- self.assertFalse(FeatureCompleteToy._meta.ordering)
- self.assertEqual(
- [obj.name for obj in response.context["object_list"]],
- [
- "CCCCCCCCCC",
- "AAAAAAAAAA",
- "DDDDDDDDDD",
- "BBBBBBBBBB",
- ],
- )
- class TestBreadcrumbs(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
- def setUp(self):
- self.user = self.login()
- @classmethod
- def setUpTestData(cls):
- cls.object = FeatureCompleteToy.objects.create(name="Test Toy")
- def test_index_view(self):
- response = self.client.get(reverse("feature_complete_toy:index"))
- items = [
- {
- "url": "",
- "label": "Feature complete toys",
- }
- ]
- self.assertBreadcrumbsItemsRendered(items, response.content)
- def test_add_view(self):
- response = self.client.get(reverse("feature_complete_toy:add"))
- items = [
- {
- "url": reverse("feature_complete_toy:index"),
- "label": "Feature complete toys",
- },
- {
- "url": "",
- "label": "New: Feature complete toy",
- },
- ]
- self.assertBreadcrumbsItemsRendered(items, response.content)
- def test_edit_view(self):
- edit_url = reverse("feature_complete_toy:edit", args=(quote(self.object.pk),))
- response = self.client.get(edit_url)
- items = [
- {
- "url": reverse("feature_complete_toy:index"),
- "label": "Feature complete toys",
- },
- {
- "url": "",
- "label": str(self.object),
- },
- ]
- self.assertBreadcrumbsItemsRendered(items, response.content)
- def test_delete_view(self):
- delete_url = reverse(
- "feature_complete_toy:delete",
- args=(quote(self.object.pk),),
- )
- response = self.client.get(delete_url)
- self.assertBreadcrumbsNotRendered(response.content)
- def test_history_view(self):
- history_url = reverse(
- "feature_complete_toy:history",
- args=(quote(self.object.pk),),
- )
- response = self.client.get(history_url)
- items = [
- {
- "url": reverse("feature_complete_toy:index"),
- "label": "Feature complete toys",
- },
- {
- "url": reverse(
- "feature_complete_toy:edit", args=(quote(self.object.pk),)
- ),
- "label": str(self.object),
- },
- {
- "url": "",
- "label": "History",
- "sublabel": str(self.object),
- },
- ]
- self.assertBreadcrumbsItemsRendered(items, response.content)
- def test_usage_view(self):
- usage_url = reverse(
- "feature_complete_toy:usage",
- args=(quote(self.object.pk),),
- )
- response = self.client.get(usage_url)
- items = [
- {
- "url": reverse("feature_complete_toy:index"),
- "label": "Feature complete toys",
- },
- {
- "url": reverse(
- "feature_complete_toy:edit", args=(quote(self.object.pk),)
- ),
- "label": str(self.object),
- },
- {
- "url": "",
- "label": "Usage",
- "sublabel": str(self.object),
- },
- ]
- self.assertBreadcrumbsItemsRendered(items, response.content)
- def test_inspect_view(self):
- inspect_url = reverse(
- "feature_complete_toy:inspect",
- args=(quote(self.object.pk),),
- )
- response = self.client.get(inspect_url)
- items = [
- {
- "url": reverse("feature_complete_toy:index"),
- "label": "Feature complete toys",
- },
- {
- "url": reverse(
- "feature_complete_toy:edit", args=(quote(self.object.pk),)
- ),
- "label": str(self.object),
- },
- {
- "url": "",
- "label": "Inspect",
- "sublabel": str(self.object),
- },
- ]
- self.assertBreadcrumbsItemsRendered(items, response.content)
- class TestLegacyPatterns(WagtailTestUtils, TestCase):
- # RemovedInWagtail70Warning: legacy integer pk-based URLs will be removed
- def setUp(self):
- self.user = self.login()
- @classmethod
- def setUpTestData(cls):
- cls.object = JSONStreamModel.objects.create(
- body='[{"type": "text", "value": "foo"}]',
- )
- def test_legacy_edit(self):
- edit_url = reverse("streammodel:edit", args=(quote(self.object.pk),))
- legacy_edit_url = "/admin/streammodel/1/"
- with self.assertWarnsRegex(
- RemovedInWagtail70Warning,
- "`/<pk>/` edit view URL pattern has been deprecated in favour of /edit/<pk>/.",
- ):
- response = self.client.get(legacy_edit_url)
- self.assertEqual(edit_url, "/admin/streammodel/edit/1/")
- self.assertRedirects(response, edit_url, 301)
- def test_legacy_delete(self):
- delete_url = reverse("streammodel:delete", args=(quote(self.object.pk),))
- legacy_delete_url = "/admin/streammodel/1/delete/"
- with self.assertWarnsRegex(
- RemovedInWagtail70Warning,
- "`/<pk>/delete/` delete view URL pattern has been deprecated in favour of /delete/<pk>/.",
- ):
- response = self.client.get(legacy_delete_url)
- self.assertEqual(delete_url, "/admin/streammodel/delete/1/")
- self.assertRedirects(response, delete_url, 301)
- class TestHistoryView(WagtailTestUtils, TestCase):
- @classmethod
- def setUpTestData(cls):
- cls.user = cls.create_test_user()
- cls.object = FeatureCompleteToy.objects.create(name="Buzz")
- cls.url = reverse(
- "feature_complete_toy:history",
- args=(quote(cls.object.pk),),
- )
- content_type = ContentType.objects.get_for_model(FeatureCompleteToy)
- cls.timestamp_1 = datetime.datetime(2021, 9, 30, 10, 1, 0)
- cls.timestamp_2 = datetime.datetime(2022, 5, 10, 12, 34, 0)
- if settings.USE_TZ:
- cls.timestamp_1 = make_aware(cls.timestamp_1)
- cls.timestamp_2 = make_aware(cls.timestamp_2)
- ModelLogEntry.objects.create(
- content_type=content_type,
- label="Test Buzz",
- action="wagtail.create",
- user=cls.user,
- timestamp=cls.timestamp_1,
- object_id=cls.object.pk,
- )
- ModelLogEntry.objects.create(
- content_type=content_type,
- label="Test Buzz Updated",
- action="wagtail.edit",
- user=cls.user,
- timestamp=cls.timestamp_2,
- object_id=cls.object.pk,
- )
- def setUp(self):
- self.login(self.user)
- def test_simple(self):
- expected = (
- ("Edited", str(self.user), date_format(self.timestamp_2, "c")),
- ("Created", str(self.user), date_format(self.timestamp_1, "c")),
- )
- response = self.client.get(self.url)
- soup = self.get_soup(response.content)
- rows = soup.select("tbody tr")
- self.assertEqual(response.status_code, 200)
- self.assertEqual(len(rows), 2)
- rendered_rows = []
- for row in rows:
- cells = []
- tds = row.select("td")
- self.assertEqual(len(tds), 3)
- cells.append(tds[0].text.strip())
- cells.append(tds[1].text.strip())
- cells.append(tds[2].select_one("time").attrs.get("datetime"))
- rendered_rows.append(cells)
- for rendered_row, expected_row in zip(rendered_rows, expected):
- self.assertSequenceEqual(rendered_row, expected_row)
- def test_filters(self):
- response = self.client.get(self.url, {"action": "wagtail.edit"})
- soup = self.get_soup(response.content)
- rows = soup.select("tbody tr")
- self.assertEqual(response.status_code, 200)
- self.assertEqual(len(rows), 1)
- self.assertEqual(rows[0].select_one("td").text.strip(), "Edited")
- response = self.client.get(self.url, {"action": "wagtail.create"})
- soup = self.get_soup(response.content)
- rows = soup.select("tbody tr")
- heading = soup.select_one('h2[role="alert"]')
- self.assertEqual(response.status_code, 200)
- self.assertEqual(heading.string.strip(), "There is 1 match")
- self.assertEqual(len(rows), 1)
- self.assertEqual(rows[0].select_one("td").text.strip(), "Created")
- def test_filtered_no_results(self):
- response = self.client.get(self.url, {"timestamp_before": "2020-01-01"})
- soup = self.get_soup(response.content)
- results = soup.select_one("#listing-results")
- table = soup.select_one("table")
- p = results.select_one("p")
- self.assertEqual(response.status_code, 200)
- self.assertIsNotNone(results)
- self.assertIsNone(table)
- self.assertIsNotNone(p)
- self.assertEqual(p.text.strip(), "No log entries match your query.")
- def test_empty(self):
- ModelLogEntry.objects.all().delete()
- response = self.client.get(self.url)
- soup = self.get_soup(response.content)
- results = soup.select_one("#listing-results")
- table = soup.select_one("table")
- self.assertEqual(response.status_code, 200)
- self.assertIsNotNone(results)
- self.assertEqual(results.text.strip(), "There are no log entries to display.")
- self.assertIsNone(table)
- def test_edit_view_links_to_history_view(self):
- edit_url = reverse("feature_complete_toy:edit", args=(quote(self.object.pk),))
- response = self.client.get(edit_url)
- soup = self.get_soup(response.content)
- header = soup.select_one(".w-slim-header")
- history_link = header.find("a", attrs={"href": self.url})
- self.assertIsNotNone(history_link)
- class TestUsageView(WagtailTestUtils, TestCase):
- @classmethod
- def setUpTestData(cls):
- cls.user = cls.create_test_user()
- cls.object = FeatureCompleteToy.objects.create(name="Buzz")
- cls.url = reverse(
- "feature_complete_toy:usage",
- args=(quote(cls.object.pk),),
- )
- cls.tbx = VariousOnDeleteModel.objects.create(
- text="Toybox", cascading_toy=cls.object
- )
- def setUp(self):
- self.user = self.login(self.user)
- def test_simple(self):
- response = self.client.get(self.url)
- self.assertEqual(response.status_code, 200)
- soup = self.get_soup(response.content)
- h1 = soup.select_one("h1")
- self.assertEqual(h1.text.strip(), f"Usage: {self.object}")
- tds = soup.select("tbody tr td")
- self.assertEqual(len(tds), 3)
- self.assertEqual(tds[0].text.strip(), str(self.tbx))
- self.assertEqual(tds[1].text.strip(), "Various on delete model")
- self.assertEqual(tds[2].text.strip(), "Cascading toy")
- tbx_edit_url = AdminURLFinder(self.user).get_edit_url(self.tbx)
- # Link to referrer's edit view
- link = tds[0].select_one("a")
- self.assertIsNotNone(link)
- self.assertEqual(link.attrs.get("href"), tbx_edit_url)
- # Link to referrer's edit view with parameters for the specific field
- link = tds[2].select_one("a")
- self.assertIsNotNone(link)
- self.assertIn(tbx_edit_url, link.attrs.get("href"))
- def test_usage_without_permission(self):
- self.user.is_superuser = False
- self.user.save()
- admin_permission = Permission.objects.get(
- content_type__app_label="wagtailadmin", codename="access_admin"
- )
- self.user.user_permissions.add(admin_permission)
- response = self.client.get(self.url)
- self.assertEqual(response.status_code, 302)
- self.assertRedirects(response, reverse("wagtailadmin_home"))
- def test_usage_without_permission_on_referrer(self):
- self.user.is_superuser = False
- self.user.save()
- admin_permission = Permission.objects.get(
- content_type__app_label="wagtailadmin", codename="access_admin"
- )
- toy_edit_permission = Permission.objects.get(
- content_type__app_label="tests", codename="change_featurecompletetoy"
- )
- self.user.user_permissions.add(admin_permission, toy_edit_permission)
- response = self.client.get(self.url)
- self.assertEqual(response.status_code, 200)
- soup = self.get_soup(response.content)
- h1 = soup.select_one("h1")
- self.assertEqual(h1.text.strip(), f"Usage: {self.object}")
- tds = soup.select("tbody tr td")
- self.assertEqual(len(tds), 3)
- self.assertEqual(tds[0].text.strip(), "(Private various on delete model)")
- self.assertEqual(tds[1].text.strip(), "Various on delete model")
- self.assertEqual(tds[2].text.strip(), "Cascading toy")
- # Not link to referrer's edit view
- link = tds[0].select_one("a")
- self.assertIsNone(link)
- # Not link to referrer's edit view
- link = tds[2].select_one("a")
- self.assertIsNone(link)
- def test_usage_with_describe_on_delete(self):
- response = self.client.get(self.url + "?describe_on_delete=1")
- self.assertEqual(response.status_code, 200)
- soup = self.get_soup(response.content)
- h1 = soup.select_one("h1")
- self.assertEqual(h1.text.strip(), f"Usage: {self.object}")
- tds = soup.select("tbody tr td")
- self.assertEqual(len(tds), 3)
- self.assertEqual(tds[0].text.strip(), str(self.tbx))
- self.assertEqual(tds[1].text.strip(), "Various on delete model")
- self.assertEqual(
- tds[2].text.strip(),
- "Cascading toy: the various on delete model will also be deleted",
- )
- tbx_edit_url = AdminURLFinder(self.user).get_edit_url(self.tbx)
- # Link to referrer's edit view
- link = tds[0].select_one("a")
- self.assertIsNotNone(link)
- self.assertEqual(link.attrs.get("href"), tbx_edit_url)
- # Link to referrer's edit view with parameters for the specific field
- link = tds[2].select_one("a")
- self.assertIsNotNone(link)
- self.assertIn(tbx_edit_url, link.attrs.get("href"))
- def test_empty(self):
- self.tbx.delete()
- response = self.client.get(self.url)
- soup = self.get_soup(response.content)
- results = soup.select_one("#listing-results")
- table = soup.select_one("table")
- self.assertEqual(response.status_code, 200)
- self.assertIsNotNone(results)
- self.assertEqual(results.text.strip(), "There are no results.")
- self.assertIsNone(table)
- def test_edit_view_links_to_usage_view(self):
- edit_url = reverse("feature_complete_toy:edit", args=(quote(self.object.pk),))
- response = self.client.get(edit_url)
- soup = self.get_soup(response.content)
- side_panel = soup.select_one("[data-side-panel='status']")
- usage_link = side_panel.find("a", attrs={"href": self.url})
- self.assertIsNotNone(usage_link)
- def test_delete_view_links_to_usage_view(self):
- edit_url = reverse("feature_complete_toy:delete", args=(quote(self.object.pk),))
- response = self.client.get(edit_url)
- soup = self.get_soup(response.content)
- usage_link = soup.find("a", attrs={"href": self.url + "?describe_on_delete=1"})
- self.assertIsNotNone(usage_link)
- class TestInspectView(WagtailTestUtils, TestCase):
- def setUp(self):
- self.user = self.login()
- @classmethod
- def setUpTestData(cls):
- cls.object = FeatureCompleteToy.objects.create(name="Test Toy")
- cls.url = reverse("feature_complete_toy:inspect", args=(quote(cls.object.pk),))
- cls.edit_url = reverse(
- "feature_complete_toy:edit", args=(quote(cls.object.pk),)
- )
- cls.delete_url = reverse(
- "feature_complete_toy:delete", args=(quote(cls.object.pk),)
- )
- def test_simple(self):
- response = self.client.get(self.url)
- expected_fields = ["Strid", "Release date"]
- expected_values = [
- # The pk may contain whitespace at the start/end, it's hard to
- # distinguish from the whitespace in the HTML so just strip it
- self.object.pk.strip(),
- localize(self.object.release_date),
- ]
- self.assertEqual(response.status_code, 200)
- self.assertTemplateUsed(response, "wagtailadmin/generic/inspect.html")
- soup = self.get_soup(response.content)
- fields = [dt.text.strip() for dt in soup.select("dt")]
- values = [dd.text.strip() for dd in soup.select("dd")]
- self.assertEqual(fields, expected_fields)
- self.assertEqual(values, expected_values)
- # One in the breadcrumb, one at the bottom
- self.assertEqual(len(soup.find_all("a", attrs={"href": self.edit_url})), 2)
- self.assertEqual(len(soup.find_all("a", attrs={"href": self.delete_url})), 1)
- def test_inspect_view_fields(self):
- # The alt1 viewset has a custom inspect_view_fields and inspect_view_fields_exclude
- response = self.client.get(
- reverse("fctoy_alt1:inspect", args=(quote(self.object.pk),))
- )
- expected_fields = ["Name"]
- expected_values = ["Test Toy"]
- self.assertEqual(response.status_code, 200)
- self.assertTemplateUsed(response, "wagtailadmin/generic/inspect.html")
- soup = self.get_soup(response.content)
- fields = [dt.text.strip() for dt in soup.select("dt")]
- values = [dd.text.strip() for dd in soup.select("dd")]
- self.assertEqual(fields, expected_fields)
- self.assertEqual(values, expected_values)
- def test_disabled(self):
- # An alternate viewset for the same model without inspect_view_enabled = True
- with self.assertRaises(NoReverseMatch):
- reverse("fctoy-alt2:inspect", args=(quote(self.object.pk),))
- def test_without_permission(self):
- self.user.is_superuser = False
- self.user.save()
- admin_permission = Permission.objects.get(
- content_type__app_label="wagtailadmin", codename="access_admin"
- )
- self.user.user_permissions.add(admin_permission)
- response = self.client.get(self.url)
- self.assertEqual(response.status_code, 302)
- self.assertRedirects(response, reverse("wagtailadmin_home"))
- def test_only_add_permission(self):
- 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.object._meta.app_label,
- codename=get_permission_codename("add", self.object._meta),
- ),
- )
- self.user.save()
- response = self.client.get(self.url)
- expected_fields = ["Strid", "Release date"]
- expected_values = [
- # The pk may contain whitespace at the start/end, it's hard to
- # distinguish from the whitespace in the HTML so just strip it
- self.object.pk.strip(),
- localize(self.object.release_date),
- ]
- self.assertEqual(response.status_code, 200)
- self.assertTemplateUsed(response, "wagtailadmin/generic/inspect.html")
- soup = self.get_soup(response.content)
- fields = [dt.text.strip() for dt in soup.select("dt")]
- values = [dd.text.strip() for dd in soup.select("dd")]
- self.assertEqual(fields, expected_fields)
- self.assertEqual(values, expected_values)
- self.assertEqual(len(soup.find_all("a", attrs={"href": self.edit_url})), 0)
- self.assertEqual(len(soup.find_all("a", attrs={"href": self.delete_url})), 0)
- class TestListingButtons(WagtailTestUtils, TestCase):
- def setUp(self):
- self.user = self.login()
- @classmethod
- def setUpTestData(cls):
- cls.object = FeatureCompleteToy.objects.create(name="Test Toy")
- def test_simple(self):
- response = self.client.get(reverse("feature_complete_toy:index"))
- self.assertEqual(response.status_code, 200)
- self.assertTemplateUsed(response, "wagtailadmin/shared/buttons.html")
- soup = self.get_soup(response.content)
- actions = soup.select_one("tbody tr td ul.actions")
- more_dropdown = actions.select_one("li [data-controller='w-dropdown']")
- self.assertIsNotNone(more_dropdown)
- more_button = more_dropdown.select_one("button")
- self.assertEqual(
- more_button.attrs.get("aria-label").strip(),
- f"More options for '{self.object}'",
- )
- expected_buttons = [
- (
- "Edit",
- f"Edit '{self.object}'",
- reverse("feature_complete_toy:edit", args=[quote(self.object.pk)]),
- ),
- (
- "Inspect",
- f"Inspect '{self.object}'",
- reverse("feature_complete_toy:inspect", args=[quote(self.object.pk)]),
- ),
- (
- "Delete",
- f"Delete '{self.object}'",
- reverse("feature_complete_toy:delete", args=[quote(self.object.pk)]),
- ),
- ]
- rendered_buttons = more_dropdown.select("a")
- self.assertEqual(len(rendered_buttons), len(expected_buttons))
- for rendered_button, (label, aria_label, url) in zip(
- rendered_buttons, expected_buttons
- ):
- self.assertEqual(rendered_button.text.strip(), label)
- self.assertEqual(rendered_button.attrs.get("aria-label"), aria_label)
- self.assertEqual(rendered_button.attrs.get("href"), url)
- class TestEditHandler(WagtailTestUtils, TestCase):
- def setUp(self):
- self.user = self.login()
- @classmethod
- def setUpTestData(cls):
- cls.object = FeatureCompleteToy.objects.create(name="Test Toy")
- cls.url = reverse("feature_complete_toy:edit", args=(quote(cls.object.pk),))
- def test_edit_form_rendered_with_panels(self):
- response = self.client.get(self.url)
- self.assertEqual(response.status_code, 200)
- self.assertTemplateUsed(response, "wagtailadmin/shared/panel.html")
- soup = self.get_soup(response.content)
- # Minimap should be rendered
- minimap_container = soup.select_one("[data-minimap-container]")
- self.assertIsNotNone(minimap_container)
- # Form should be rendered using panels
- panels = soup.select("[data-panel]")
- self.assertEqual(len(panels), 2)
- headings = ["Name", "Release date"]
- for expected_heading, panel in zip(headings, panels):
- rendered_heading = panel.select_one("[data-panel-heading-text]")
- self.assertIsNotNone(rendered_heading)
- self.assertEqual(rendered_heading.text.strip(), expected_heading)
- class TestDefaultMessages(WagtailTestUtils, TestCase):
- def setUp(self):
- self.user = self.login()
- @classmethod
- def setUpTestData(cls):
- cls.object = FeatureCompleteToy.objects.create(name="Test Toy")
- cls.create_url = reverse("feature_complete_toy:add")
- cls.edit_url = reverse(
- "feature_complete_toy:edit", args=(quote(cls.object.pk),)
- )
- cls.delete_url = reverse(
- "feature_complete_toy:delete", args=(quote(cls.object.pk),)
- )
- def test_create_error(self):
- response = self.client.post(
- self.create_url,
- data={"name": "", "release_date": "2024-01-11"},
- )
- self.assertEqual(response.status_code, 200)
- self.assertContains(
- response,
- escape("The feature complete toy could not be created due to errors."),
- )
- def test_create_success(self):
- response = self.client.post(
- self.create_url,
- data={"name": "Pink Flamingo", "release_date": "2024-01-11"},
- follow=True,
- )
- self.assertEqual(response.status_code, 200)
- self.assertContains(
- response,
- escape("Feature complete toy 'Pink Flamingo (2024-01-11)' created."),
- )
- def test_edit_error(self):
- response = self.client.post(
- self.edit_url, data={"name": "", "release_date": "2024-01-11"}
- )
- self.assertEqual(response.status_code, 200)
- self.assertContains(
- response,
- escape("The feature complete toy could not be saved due to errors."),
- )
- def test_edit_success(self):
- response = self.client.post(
- self.edit_url,
- data={"name": "rubberduck", "release_date": "2024-02-01"},
- follow=True,
- )
- self.assertEqual(response.status_code, 200)
- self.assertContains(
- response,
- escape("Feature complete toy 'rubberduck (2024-02-01)' updated."),
- )
- def test_delete_success(self):
- response = self.client.post(self.delete_url, follow=True)
- self.assertEqual(response.status_code, 200)
- self.assertContains(
- response,
- escape(f"Feature complete toy '{self.object}' deleted."),
- )
|