Initial commit

This commit is contained in:
2024-08-27 20:33:44 +02:00
commit 1f1832267d
14794 changed files with 1599592 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
from wagtail.coreutils import get_dummy_request
from wagtail.models import Page, Site
from wagtail.test.testapp.models import TestGenericSetting
class GenericSettingsTestMixin:
def setUp(self):
root = Page.objects.first()
other_root = Page(title="Other Root")
root.add_child(instance=other_root)
self.default_site = Site.objects.get(is_default_site=True)
self.other_site = Site.objects.create(hostname="other", root_page=other_root)
self.default_settings = TestGenericSetting.objects.create(
title="Default GenericSettings title", email="email@example.com"
)
def get_request(self, site=None):
if site is None:
site = self.default_site
return get_dummy_request(site=site)

View File

@@ -0,0 +1,421 @@
from django.contrib.auth.models import Permission
from django.test import TestCase
from django.urls import reverse
from django.utils.text import capfirst
from wagtail import hooks
from wagtail.admin.admin_url_finder import AdminURLFinder
from wagtail.admin.panels import FieldPanel, ObjectList, TabbedInterface
from wagtail.contrib.settings.registry import SettingMenuItem
from wagtail.contrib.settings.views import get_setting_edit_handler
from wagtail.test.testapp.models import (
FileGenericSetting,
IconGenericSetting,
PanelGenericSettings,
TabbedGenericSettings,
TestGenericSetting,
TestPermissionedGenericSetting,
)
from wagtail.test.utils import WagtailTestUtils
class TestGenericSettingMenu(WagtailTestUtils, TestCase):
def login_only_admin(self):
"""Log in with a user that only has permission to access the admin"""
user = self.create_user(username="test", password="password")
user.user_permissions.add(
Permission.objects.get_by_natural_key(
codename="access_admin", app_label="wagtailadmin", model="admin"
)
)
self.login(username="test", password="password")
return user
def test_menu_item_in_admin(self):
self.login()
response = self.client.get(reverse("wagtailadmin_home"))
self.assertContains(response, capfirst(TestGenericSetting._meta.verbose_name))
self.assertContains(
response,
reverse("wagtailsettings:edit", args=("tests", "testgenericsetting")),
)
def test_menu_item_no_permissions(self):
self.login_only_admin()
response = self.client.get(reverse("wagtailadmin_home"))
self.assertNotContains(response, TestGenericSetting._meta.verbose_name)
self.assertNotContains(
response,
reverse("wagtailsettings:edit", args=("tests", "testgenericsetting")),
)
def test_menu_item_icon(self):
menu_item = SettingMenuItem(
IconGenericSetting, icon="tag", classname="test-class"
)
self.assertEqual(menu_item.icon_name, "tag")
self.assertEqual(menu_item.classname, "test-class")
class BaseTestGenericSettingView(WagtailTestUtils, TestCase):
def get(self, params={}, setting=TestGenericSetting):
url = self.edit_url(setting=setting)
return self.client.get(url, params)
def post(self, post_data={}, setting=TestGenericSetting):
url = self.edit_url(setting=setting)
return self.client.post(url, post_data)
def edit_url(self, setting):
pk = setting._get_or_create().pk
args = [setting._meta.app_label, setting._meta.model_name, pk]
return reverse("wagtailsettings:edit", args=args)
class TestGenericSettingCreateView(BaseTestGenericSettingView):
def setUp(self):
self.user = self.login()
def test_get_edit(self):
response = self.get()
self.assertEqual(response.status_code, 200)
def test_edit_invalid(self):
response = self.post(post_data={"foo": "bar"})
self.assertContains(response, "The setting could not be saved due to errors.")
self.assertContains(response, "This field is required", count=2)
def test_edit(self):
response = self.post(
post_data={
"title": "Edited setting title",
"email": "edited.email@example.com",
}
)
self.assertEqual(response.status_code, 302)
setting = TestGenericSetting.objects.first()
self.assertEqual(setting.title, "Edited setting title")
url_finder = AdminURLFinder(self.user)
expected_url = "/admin/settings/tests/testgenericsetting/%d/" % setting.pk
self.assertEqual(url_finder.get_edit_url(setting), expected_url)
def test_file_upload_multipart(self):
response = self.get(setting=FileGenericSetting)
# Ensure the form supports file uploads
self.assertContains(response, 'enctype="multipart/form-data"')
def test_create_restricted_field_without_any_permission(self):
# User has no permissions over the setting model, only access to the admin
self.user.is_superuser = False
self.user.save()
self.user.user_permissions.add(
Permission.objects.get(
content_type__app_label="wagtailadmin", codename="access_admin"
),
)
self.assertFalse(TestPermissionedGenericSetting.objects.exists())
# GET should redirect away with permission denied
response = self.get(setting=TestPermissionedGenericSetting)
self.assertRedirects(response, status_code=302, expected_url="/admin/")
# the GET might create a setting object, depending on when the permission check is done,
# so remove any created objects prior to testing the POST
TestPermissionedGenericSetting.objects.all().delete()
# POST should redirect away with permission denied
response = self.post(
post_data={"sensitive_email": "test@example.com", "title": "test"},
setting=TestPermissionedGenericSetting,
)
self.assertRedirects(response, status_code=302, expected_url="/admin/")
# The retrieved setting should contain none of the submitted data
setting = TestPermissionedGenericSetting.load()
self.assertEqual(setting.title, "")
self.assertEqual(setting.sensitive_email, "")
def test_create_restricted_field_without_field_permission(self):
# User has edit permission over the setting model, but not the sensitive_email field
self.user.is_superuser = False
self.user.save()
self.user.user_permissions.add(
Permission.objects.get(
content_type__app_label="wagtailadmin", codename="access_admin"
),
Permission.objects.get(
content_type__app_label="tests",
codename="change_testpermissionedgenericsetting",
),
)
self.assertFalse(TestPermissionedGenericSetting.objects.exists())
# GET should provide a form with title but not sensitive_email
response = self.get(setting=TestPermissionedGenericSetting)
self.assertEqual(response.status_code, 200)
self.assertIn("title", list(response.context["form"].fields))
self.assertNotIn("sensitive_email", list(response.context["form"].fields))
# the GET creates a setting object, so remove any created objects prior to testing the POST
TestPermissionedGenericSetting.objects.all().delete()
# POST should allow the title to be set, but not the sensitive_email
response = self.post(
post_data={"sensitive_email": "test@example.com", "title": "test"},
setting=TestPermissionedGenericSetting,
)
self.assertEqual(response.status_code, 302)
settings = TestPermissionedGenericSetting.objects.get()
self.assertEqual(settings.title, "test")
self.assertEqual(settings.sensitive_email, "")
def test_create_restricted_field(self):
# User has edit permission over the setting model, including the sensitive_email field
self.user.is_superuser = False
self.user.save()
self.user.user_permissions.add(
Permission.objects.get(
content_type__app_label="wagtailadmin", codename="access_admin"
),
Permission.objects.get(
content_type__app_label="tests",
codename="change_testpermissionedgenericsetting",
),
Permission.objects.get(codename="can_edit_sensitive_email_generic_setting"),
)
self.assertFalse(TestPermissionedGenericSetting.objects.exists())
# GET should provide a form with title and sensitive_email
response = self.get(setting=TestPermissionedGenericSetting)
self.assertEqual(response.status_code, 200)
self.assertIn("title", list(response.context["form"].fields))
self.assertIn("sensitive_email", list(response.context["form"].fields))
# the GET creates a setting object, so remove any created objects prior to testing the POST
TestPermissionedGenericSetting.objects.all().delete()
# POST should allow both title and sensitive_email to be set
self.assertFalse(TestPermissionedGenericSetting.objects.exists())
response = self.post(
post_data={"sensitive_email": "test@example.com", "title": "test"},
setting=TestPermissionedGenericSetting,
)
self.assertEqual(response.status_code, 302)
settings = TestPermissionedGenericSetting.objects.get()
self.assertEqual(settings.title, "test")
self.assertEqual(settings.sensitive_email, "test@example.com")
class TestGenericSettingEditView(BaseTestGenericSettingView):
def setUp(self):
self.test_setting = TestGenericSetting()
self.test_setting.title = "Setting title"
self.test_setting.save()
self.user = self.login()
def test_get_edit(self):
response = self.get()
self.assertEqual(response.status_code, 200)
def test_non_existent_model(self):
response = self.client.get(
reverse("wagtailsettings:edit", args=["test", "foo", 1])
)
self.assertEqual(response.status_code, 404)
def test_register_with_icon(self):
edit_url = reverse("wagtailsettings:edit", args=("tests", "IconGenericSetting"))
edit_response = self.client.get(edit_url, follow=True)
self.assertContains(
edit_response,
"""<svg class="icon icon-icon-setting-tag w-header__glyph" aria-hidden="true"><use href="#icon-icon-setting-tag"></use></svg>""",
)
def test_edit_invalid(self):
response = self.post(post_data={"foo": "bar"})
self.assertContains(response, "The setting could not be saved due to errors.")
self.assertContains(response, "This field is required", count=2)
def test_edit(self):
response = self.post(
post_data={
"title": "Edited setting title",
"email": "different.email@example.com",
}
)
self.assertEqual(response.status_code, 302)
setting = TestGenericSetting.objects.first()
self.assertEqual(setting.title, "Edited setting title")
def test_for_request(self):
url = reverse("wagtailsettings:edit", args=("tests", "testgenericsetting"))
response = self.client.get(url)
self.assertRedirects(
response,
status_code=302,
expected_url=f"{url}{TestGenericSetting.objects.first().pk}/",
)
def test_edit_restricted_field(self):
# User has edit permission over the setting model, including the sensitive_email field
test_setting = TestPermissionedGenericSetting()
test_setting.sensitive_email = "test@example.com"
test_setting.title = "Old title"
test_setting.save()
self.user.is_superuser = False
self.user.save()
self.user.user_permissions.add(
Permission.objects.get(
content_type__app_label="wagtailadmin", codename="access_admin"
),
Permission.objects.get(
content_type__app_label="tests",
codename="change_testpermissionedgenericsetting",
),
Permission.objects.get(codename="can_edit_sensitive_email_generic_setting"),
)
# GET should provide a form with title and sensitive_email
response = self.get(setting=TestPermissionedGenericSetting)
self.assertEqual(response.status_code, 200)
self.assertIn("title", list(response.context["form"].fields))
self.assertIn("sensitive_email", list(response.context["form"].fields))
# POST should allow both title and sensitive_email to be set
response = self.post(
setting=TestPermissionedGenericSetting,
post_data={
"sensitive_email": "test-updated@example.com",
"title": "New title",
},
)
self.assertEqual(response.status_code, 302)
test_setting.refresh_from_db()
self.assertEqual(test_setting.sensitive_email, "test-updated@example.com")
self.assertEqual(test_setting.title, "New title")
def test_edit_restricted_field_without_field_permission(self):
# User has edit permission over the setting model, but not the sensitive_email field
test_setting = TestPermissionedGenericSetting()
test_setting.sensitive_email = "test@example.com"
test_setting.title = "Old title"
test_setting.save()
self.user.is_superuser = False
self.user.save()
self.user.user_permissions.add(
Permission.objects.get(
content_type__app_label="wagtailadmin", codename="access_admin"
),
Permission.objects.get(
content_type__app_label="tests",
codename="change_testpermissionedgenericsetting",
),
)
# GET should provide a form with title but not sensitive_email
response = self.get(setting=TestPermissionedGenericSetting)
self.assertEqual(response.status_code, 200)
self.assertIn("title", list(response.context["form"].fields))
self.assertNotIn("sensitive_email", list(response.context["form"].fields))
# POST should allow the title to be set, but not the sensitive_email
response = self.post(
setting=TestPermissionedGenericSetting,
post_data={
"sensitive_email": "test-updated@example.com",
"title": "New title",
},
)
self.assertEqual(response.status_code, 302)
test_setting.refresh_from_db()
self.assertEqual(test_setting.sensitive_email, "test@example.com")
self.assertEqual(test_setting.title, "New title")
def test_edit_restricted_field_without_any_permission(self):
# User has no permissions over the setting model, only access to the admin
test_setting = TestPermissionedGenericSetting()
test_setting.sensitive_email = "test@example.com"
test_setting.title = "Old title"
test_setting.save()
self.user.is_superuser = False
self.user.save()
self.user.user_permissions.add(
Permission.objects.get(
content_type__app_label="wagtailadmin", codename="access_admin"
),
)
# GET should redirect away with permission denied
response = self.get(setting=TestPermissionedGenericSetting)
self.assertRedirects(response, status_code=302, expected_url="/admin/")
# POST should redirect away with permission denied
response = self.post(
setting=TestPermissionedGenericSetting,
post_data={
"sensitive_email": "test-updated@example.com",
"title": "new title",
},
)
self.assertRedirects(response, status_code=302, expected_url="/admin/")
# The retrieved setting should be unchanged
test_setting.refresh_from_db()
self.assertEqual(test_setting.sensitive_email, "test@example.com")
self.assertEqual(test_setting.title, "Old title")
class TestAdminPermission(WagtailTestUtils, TestCase):
def test_registered_permission(self):
permission = Permission.objects.get_by_natural_key(
app_label="tests",
model="testgenericsetting",
codename="change_testgenericsetting",
)
for fn in hooks.get_hooks("register_permissions"):
if permission in fn():
break
else:
self.fail("Change permission for tests.TestGenericSetting not registered")
class TestEditHandlers(TestCase):
def setUp(self):
get_setting_edit_handler.cache_clear()
def test_default_model_introspection(self):
handler = get_setting_edit_handler(TestGenericSetting)
self.assertIsInstance(handler, ObjectList)
self.assertEqual(len(handler.children), 2)
first = handler.children[0]
self.assertIsInstance(first, FieldPanel)
self.assertEqual(first.field_name, "title")
second = handler.children[1]
self.assertIsInstance(second, FieldPanel)
self.assertEqual(second.field_name, "email")
def test_with_custom_panels(self):
handler = get_setting_edit_handler(PanelGenericSettings)
self.assertIsInstance(handler, ObjectList)
self.assertEqual(len(handler.children), 1)
first = handler.children[0]
self.assertIsInstance(first, FieldPanel)
self.assertEqual(first.field_name, "title")
def test_with_custom_edit_handler(self):
handler = get_setting_edit_handler(TabbedGenericSettings)
self.assertIsInstance(handler, TabbedInterface)
self.assertEqual(len(handler.children), 2)

View File

@@ -0,0 +1,123 @@
from django.test import TestCase, override_settings
from wagtail.models import Site
from wagtail.test.testapp.models import ImportantPagesGenericSetting
from .base import GenericSettingsTestMixin
@override_settings(ALLOWED_HOSTS=["localhost", "other"])
class GenericSettingModelTestCase(GenericSettingsTestMixin, TestCase):
def _create_importantpagesgenericsetting_object(self):
return ImportantPagesGenericSetting.objects.create(
sign_up_page=self.default_site.root_page,
general_terms_page=self.default_site.root_page,
privacy_policy_page=self.other_site.root_page,
)
def test_request_or_site_with_site_returns_expected_settings(self):
for site, expected_settings in (
(self.default_site, self.default_settings),
(self.other_site, self.default_settings),
):
with self.subTest(site=site):
self.assertEqual(
self.default_settings.load(request_or_site=site),
expected_settings,
)
def test_request_or_site_with_request_returns_expected_settings(self):
default_site_request = self.get_request()
other_site_request = self.get_request(site=self.other_site)
for request, expected_settings in (
(default_site_request, self.default_settings),
(other_site_request, self.default_settings),
):
with self.subTest(request=request):
self.assertEqual(
self.default_settings.load(request_or_site=request),
expected_settings,
)
def test_request_or_site_with_request_result_caching(self):
# repeat test to show caching is unique per request instance,
# even when the requests are for the same site
for i, request in enumerate([self.get_request(), self.get_request()], 1):
with self.subTest(attempt=i):
# force site query beforehand
Site.find_for_request(request)
# only the first lookup should result in a query
with self.assertNumQueries(1):
for i in range(4):
self.default_settings.load(request_or_site=request)
def test_select_related(self, expected_queries=4):
"""The `select_related` attribute on setting models is `None` by default, so fetching foreign keys values requires additional queries"""
self._create_importantpagesgenericsetting_object()
# fetch settings and access foreign keys
with self.assertNumQueries(expected_queries):
settings = ImportantPagesGenericSetting.load()
settings.sign_up_page
settings.general_terms_page
settings.privacy_policy_page
def test_select_related_use_reduces_total_queries(self):
"""But, `select_related` can be used to reduce the number of queries needed to fetch foreign keys"""
try:
# set class attribute temporarily
ImportantPagesGenericSetting.select_related = [
"sign_up_page",
"general_terms_page",
"privacy_policy_page",
]
self.test_select_related(expected_queries=1)
finally:
# undo temporary change
ImportantPagesGenericSetting.select_related = None
def test_get_page_url_returns_page_urls(self):
self._create_importantpagesgenericsetting_object()
settings = ImportantPagesGenericSetting.load()
for page_fk_field, expected_result in (
("sign_up_page", "http://localhost/"),
("general_terms_page", "http://localhost/"),
("privacy_policy_page", "http://other/"),
):
with self.subTest(page_fk_field=page_fk_field):
self.assertEqual(settings.get_page_url(page_fk_field), expected_result)
self.assertEqual(
getattr(settings.page_url, page_fk_field), expected_result
)
def test_get_page_url_raises_attributeerror_if_attribute_name_invalid(self):
settings = self._create_importantpagesgenericsetting_object()
# when called directly
with self.assertRaises(AttributeError):
settings.get_page_url("not_an_attribute")
# when called indirectly via shortcut
with self.assertRaises(AttributeError):
settings.page_url.not_an_attribute
def test_get_page_url_returns_empty_string_if_attribute_value_not_a_page(self):
settings = self._create_importantpagesgenericsetting_object()
for value in (None, self.default_site):
with self.subTest(attribute_value=value):
settings.test_attribute = value
# when called directly
self.assertEqual(settings.get_page_url("test_attribute"), "")
# when called indirectly via shortcut
self.assertEqual(settings.page_url.test_attribute, "")
def test_display_as_string(self):
self._create_importantpagesgenericsetting_object()
self.assertEqual(
str(ImportantPagesGenericSetting.load()),
"Important pages settings",
)

View File

@@ -0,0 +1,24 @@
from django.test import TestCase
from django.urls import reverse
from wagtail.contrib.settings.registry import Registry
from wagtail.test.testapp.models import NotYetRegisteredGenericSetting
from wagtail.test.utils import WagtailTestUtils
class GenericSettingRegisterTestCase(WagtailTestUtils, TestCase):
def setUp(self):
self.registry = Registry()
self.login()
def test_register(self):
self.assertNotIn(NotYetRegisteredGenericSetting, self.registry)
NowRegisteredGenericSetting = self.registry.register_decorator(
NotYetRegisteredGenericSetting
)
self.assertIn(NotYetRegisteredGenericSetting, self.registry)
self.assertIs(NowRegisteredGenericSetting, NotYetRegisteredGenericSetting)
def test_icon(self):
admin = self.client.get(reverse("wagtailadmin_home"))
self.assertContains(admin, "icon-setting-tag")

View File

@@ -0,0 +1,197 @@
from django.template import Context, RequestContext, Template, engines
from django.test import TestCase
from django.test.utils import override_settings
from wagtail.coreutils import get_dummy_request
from wagtail.models import Site
from wagtail.test.utils import WagtailTestUtils
from .base import GenericSettingsTestMixin
@override_settings(ALLOWED_HOSTS=["testserver", "localhost", "other"])
class GenericSettingTemplateTestCase(
GenericSettingsTestMixin, WagtailTestUtils, TestCase
):
def render(self, request, string, context=None):
template = Template(string)
context = RequestContext(request, context)
return template.render(context)
class GenericSettingContextProcessorTestCase(GenericSettingTemplateTestCase):
def test_accessing_setting(self):
"""Check that the context processor works"""
request = self.get_request()
self.assertEqual(
self.render(request, "{{ settings.tests.TestGenericSetting.title }}"),
self.default_settings.title,
)
def test_model_case_insensitive(self):
"""Model names should be case insensitive"""
request = self.get_request()
self.assertEqual(
self.render(request, "{{ settings.tests.testgenericsetting.title }}"),
self.default_settings.title,
)
self.assertEqual(
self.render(request, "{{ settings.tests.TESTGENERICSETTING.title }}"),
self.default_settings.title,
)
self.assertEqual(
self.render(request, "{{ settings.tests.TestGenericSetting.title }}"),
self.default_settings.title,
)
self.assertEqual(
self.render(request, "{{ settings.tests.tEstgEnerICsEttIng.title }}"),
self.default_settings.title,
)
def test_models_cached(self):
"""Accessing a setting should only hit the DB once per request instance,
even if using that request to rendering multiple times"""
request = self.get_request()
get_title = "{{ settings.tests.testgenericsetting.title }}"
# force site query beforehand
Site.find_for_request(request)
with self.assertNumQueries(1):
for i in range(1, 4):
with self.subTest(attempt=i):
self.assertEqual(
self.render(request, get_title * i),
self.default_settings.title * i,
)
class GenericSettingTemplateTagTestCase(GenericSettingTemplateTestCase):
def test_no_context_processor(self):
"""
Assert that not running the context processor means settings are not in
the context, as expected.
"""
template = Template("{{ settings.tests.TestGenericSetting.title }}")
context = Context()
self.assertEqual(template.render(context), "")
def test_get_settings_request_context(self):
"""Check that the {% get_settings %} tag works"""
request = self.get_request()
context = Context({"request": request})
template = Template(
"{% load wagtailsettings_tags %}"
"{% get_settings %}"
"{{ settings.tests.testgenericsetting.title }}"
)
self.assertEqual(template.render(context), self.default_settings.title)
def test_get_settings_no_request(self):
"""Check that the {% get_settings %} tag works with no request"""
context = Context()
template = Template(
"{% load wagtailsettings_tags %}"
"{% get_settings %}"
"{{ settings.tests.testgenericsetting.title }}"
)
self.assertEqual(template.render(context), self.default_settings.title)
def test_get_settings_variable_assignment_request_context(self):
"""
Check that assigning the setting to a context variable with
{% get_settings as wagtail_settings %} works.
"""
request = self.get_request()
context = Context({"request": request})
template = Template(
"{% load wagtailsettings_tags %}"
"{% get_settings as wagtail_settings %}"
"{{ wagtail_settings.tests.testgenericsetting.title }}"
)
self.assertEqual(template.render(context), self.default_settings.title)
# Also check that the default 'settings' variable hasn't been set
template = Template(
"{% load wagtailsettings_tags %}"
"{% get_settings as wagtail_settings %}"
"{{ settings.tests.testgenericsetting.title }}"
)
self.assertEqual(template.render(context), "")
class GenericSettingJinjaContextProcessorTestCase(GenericSettingTemplateTestCase):
def setUp(self):
super().setUp()
self.engine = engines["jinja2"]
def render(self, string, context=None, request_context=True):
if context is None:
context = {}
# Add a request to the template, to simulate a RequestContext
if request_context:
site = Site.objects.get(is_default_site=True)
context["request"] = get_dummy_request(site=site)
template = self.engine.from_string(string)
return template.render(context)
def test_accessing_setting(self):
"""Check that the context processor works"""
self.assertEqual(
self.render('{{ settings("tests.TestGenericSetting").title }}'),
self.default_settings.title,
)
def test_model_case_insensitive(self):
"""Model names should be case insensitive"""
self.assertEqual(
self.render('{{ settings("tests.testgenericsetting").title }}'),
self.default_settings.title,
)
self.assertEqual(
self.render('{{ settings("tests.TESTGENERICSETTING").title }}'),
self.default_settings.title,
)
self.assertEqual(
self.render('{{ settings("tests.TestGenericSetting").title }}'),
self.default_settings.title,
)
self.assertEqual(
self.render('{{ settings("tests.tEstgEnerICsEttIng").title }}'),
self.default_settings.title,
)
def test_models_cached(self):
"""Accessing a setting should only hit the DB once per render"""
get_title = '{{ settings("tests.testgenericsetting").title }}'
request = self.get_request()
# run extra query before hand
Site.find_for_request(request)
for i in range(1, 4):
with self.assertNumQueries(1):
context = {"request": request}
template = self.engine.from_string(get_title * i)
self.assertEqual(
template.render(context), self.default_settings.title * i
)
def test_settings_no_request(self):
"""
Check that {{ settings }} does not throw an error if it can not find a
request to work with
"""
context = {}
template = '{{ settings("tests.testgenericsetting").title }}'
self.assertEqual(
self.render(template, context, request_context=False),
self.default_settings.title,
)