django-constance/tests/test_admin.py

328 lines
12 KiB
Python
Raw Permalink Normal View History

from datetime import datetime
2023-03-10 17:38:31 +00:00
from unittest import mock
from django.contrib import admin
from django.contrib.auth.models import Permission
from django.contrib.auth.models import User
from django.core.exceptions import PermissionDenied
from django.http import HttpResponseRedirect
from django.template.defaultfilters import linebreaksbr
from django.test import RequestFactory
from django.test import TestCase
from django.utils.translation import gettext_lazy as _
from constance import settings
from constance.admin import Config
from constance.forms import ConstanceForm
from constance.utils import get_values
class TestAdmin(TestCase):
model = Config
def setUp(self):
super().setUp()
self.rf = RequestFactory()
2025-10-07 09:25:07 +00:00
self.superuser = User.objects.create_superuser("admin", "nimda", "a@a.cz")
self.normaluser = User.objects.create_user("normal", "nimda", "b@b.cz")
self.normaluser.is_staff = True
self.normaluser.save()
self.options = admin.site._registry[self.model]
def test_changelist(self):
2025-10-07 09:25:07 +00:00
self.client.login(username="admin", password="nimda")
request = self.rf.get("/admin/constance/config/")
request.user = self.superuser
response = self.options.changelist_view(request, {})
2013-04-12 15:34:48 +00:00
self.assertEqual(response.status_code, 200)
def test_custom_auth(self):
settings.SUPERUSER_ONLY = False
2025-10-07 09:25:07 +00:00
self.client.login(username="normal", password="nimda")
request = self.rf.get("/admin/constance/config/")
request.user = self.normaluser
self.assertRaises(PermissionDenied, self.options.changelist_view, request, {})
2025-10-07 09:25:07 +00:00
self.assertFalse(request.user.has_perm("constance.change_config"))
# reload user to reset permission cache
2025-10-07 09:25:07 +00:00
request = self.rf.get("/admin/constance/config/")
request.user = User.objects.get(pk=self.normaluser.pk)
2025-10-07 09:25:07 +00:00
request.user.user_permissions.add(Permission.objects.get(codename="change_config"))
self.assertTrue(request.user.has_perm("constance.change_config"))
response = self.options.changelist_view(request, {})
2013-04-12 15:34:48 +00:00
self.assertEqual(response.status_code, 200)
def test_linebreaks(self):
2025-10-07 09:25:07 +00:00
self.client.login(username="admin", password="nimda")
request = self.rf.get("/admin/constance/config/")
request.user = self.superuser
response = self.options.changelist_view(request, {})
2025-10-07 09:25:07 +00:00
self.assertContains(response, "LINEBREAK_VALUE")
self.assertContains(response, linebreaksbr("eggs\neggs"))
@mock.patch(
2025-10-07 09:25:07 +00:00
"constance.settings.CONFIG_FIELDSETS",
{
2025-10-07 09:25:07 +00:00
"Numbers": ("INT_VALUE",),
"Text": ("STRING_VALUE",),
},
)
def test_fieldset_headers(self):
2025-10-07 09:25:07 +00:00
self.client.login(username="admin", password="nimda")
request = self.rf.get("/admin/constance/config/")
request.user = self.superuser
response = self.options.changelist_view(request, {})
2025-11-04 16:30:02 +00:00
self.assertContains(response, "Numbers</h2>")
self.assertContains(response, "Text</h2>")
@mock.patch(
2025-10-07 09:25:07 +00:00
"constance.settings.CONFIG_FIELDSETS",
(
2025-10-07 09:25:07 +00:00
("Numbers", ("INT_VALUE",)),
("Text", ("STRING_VALUE",)),
),
)
def test_fieldset_tuple(self):
2025-10-07 09:25:07 +00:00
self.client.login(username="admin", password="nimda")
request = self.rf.get("/admin/constance/config/")
request.user = self.superuser
response = self.options.changelist_view(request, {})
2025-11-04 16:30:02 +00:00
self.assertContains(response, "Numbers</h2>")
self.assertContains(response, "Text</h2>")
@mock.patch(
2025-10-07 09:25:07 +00:00
"constance.settings.CONFIG_FIELDSETS",
{
2025-10-07 09:25:07 +00:00
"Numbers": {
"fields": (
"INT_VALUE",
"DECIMAL_VALUE",
),
2025-10-07 09:25:07 +00:00
"collapse": True,
},
2025-10-07 09:25:07 +00:00
"Text": {
"fields": (
"STRING_VALUE",
"LINEBREAK_VALUE",
),
2025-10-07 09:25:07 +00:00
"collapse": True,
},
},
)
def test_collapsed_fieldsets(self):
2025-10-07 09:25:07 +00:00
self.client.login(username="admin", password="nimda")
request = self.rf.get("/admin/constance/config/")
request.user = self.superuser
response = self.options.changelist_view(request, {})
2025-10-07 09:25:07 +00:00
self.assertContains(response, "module collapse")
2025-10-07 09:25:07 +00:00
@mock.patch("constance.settings.CONFIG_FIELDSETS", {"FieldSetOne": ("INT_VALUE",)})
@mock.patch(
2025-10-07 09:25:07 +00:00
"constance.settings.CONFIG",
{
2025-10-07 09:25:07 +00:00
"INT_VALUE": (1, "some int"),
},
)
2025-10-07 09:25:07 +00:00
@mock.patch("constance.settings.IGNORE_ADMIN_VERSION_CHECK", True)
@mock.patch("constance.forms.ConstanceForm.save", lambda _: None)
@mock.patch("constance.forms.ConstanceForm.is_valid", lambda _: True)
def test_submit(self):
"""
Test that submitting the admin page results in an http redirect when
everything is in order.
"""
2025-10-07 09:25:07 +00:00
initial_value = {"INT_VALUE": settings.CONFIG["INT_VALUE"][0]}
2025-10-07 09:25:07 +00:00
self.client.login(username="admin", password="nimda")
request = self.rf.post(
2025-10-07 09:25:07 +00:00
"/admin/constance/config/",
data={
**initial_value,
2025-10-07 09:25:07 +00:00
"version": "123",
},
)
request.user = self.superuser
request._dont_enforce_csrf_checks = True
2025-10-07 09:25:07 +00:00
with mock.patch("django.contrib.messages.add_message") as mock_message, mock.patch.object(
ConstanceForm, "__init__", **initial_value, return_value=None
2024-07-05 14:38:26 +00:00
) as mock_form:
response = self.options.changelist_view(request, {})
mock_form.assert_called_with(data=request.POST, files=request.FILES, initial=initial_value, request=request)
2025-10-07 09:25:07 +00:00
mock_message.assert_called_with(request, 25, _("Live settings updated successfully."))
self.assertIsInstance(response, HttpResponseRedirect)
2025-10-07 09:25:07 +00:00
@mock.patch("constance.settings.CONFIG_FIELDSETS", {"FieldSetOne": ("MULTILINE",)})
@mock.patch(
2025-10-07 09:25:07 +00:00
"constance.settings.CONFIG",
{
2025-10-07 09:25:07 +00:00
"MULTILINE": ("Hello\nWorld", "multiline value"),
},
)
2025-10-07 09:25:07 +00:00
@mock.patch("constance.settings.IGNORE_ADMIN_VERSION_CHECK", True)
2020-05-26 12:08:17 +00:00
def test_newlines_normalization(self):
2025-10-07 09:25:07 +00:00
self.client.login(username="admin", password="nimda")
request = self.rf.post(
2025-10-07 09:25:07 +00:00
"/admin/constance/config/",
data={
2025-10-07 09:25:07 +00:00
"MULTILINE": "Hello\r\nWorld",
"version": "123",
},
)
2020-05-26 12:08:17 +00:00
request.user = self.superuser
request._dont_enforce_csrf_checks = True
2025-10-07 09:25:07 +00:00
with mock.patch("django.contrib.messages.add_message"):
2020-05-26 12:08:17 +00:00
response = self.options.changelist_view(request, {})
self.assertIsInstance(response, HttpResponseRedirect)
2025-10-07 09:25:07 +00:00
self.assertEqual(get_values()["MULTILINE"], "Hello\nWorld")
2020-05-26 12:08:17 +00:00
@mock.patch(
2025-10-07 09:25:07 +00:00
"constance.settings.CONFIG",
{
2025-10-07 09:25:07 +00:00
"DATETIME_VALUE": (datetime(2019, 8, 7, 18, 40, 0), "some naive datetime"),
},
)
2025-10-07 09:25:07 +00:00
@mock.patch("constance.settings.IGNORE_ADMIN_VERSION_CHECK", True)
@mock.patch("tests.redis_mockup.Connection.set", mock.MagicMock())
def test_submit_aware_datetime(self):
"""
Test that submitting the admin page results in an http redirect when
everything is in order.
"""
request = self.rf.post(
2025-10-07 09:25:07 +00:00
"/admin/constance/config/",
data={
2025-10-07 09:25:07 +00:00
"DATETIME_VALUE_0": "2019-08-07",
"DATETIME_VALUE_1": "19:17:01",
"version": "123",
},
)
request.user = self.superuser
request._dont_enforce_csrf_checks = True
2025-10-07 09:25:07 +00:00
with mock.patch("django.contrib.messages.add_message"):
response = self.options.changelist_view(request, {})
self.assertIsInstance(response, HttpResponseRedirect)
@mock.patch(
2025-10-07 09:25:07 +00:00
"constance.settings.CONFIG_FIELDSETS",
{
2025-10-07 09:25:07 +00:00
"Numbers": ("INT_VALUE",),
"Text": ("STRING_VALUE",),
},
)
def test_inconsistent_fieldset_submit(self):
"""
Test that the admin page warns users if the CONFIG_FIELDSETS setting
doesn't account for every field in CONFIG.
"""
2025-10-07 09:25:07 +00:00
self.client.login(username="admin", password="nimda")
request = self.rf.post("/admin/constance/config/", data=None)
request.user = self.superuser
request._dont_enforce_csrf_checks = True
2025-10-07 09:25:07 +00:00
with mock.patch("django.contrib.messages.add_message"):
response = self.options.changelist_view(request, {})
2025-10-07 09:25:07 +00:00
self.assertContains(response, "is missing field(s)")
@mock.patch(
2025-10-07 09:25:07 +00:00
"constance.settings.CONFIG_FIELDSETS",
{
2025-10-07 09:25:07 +00:00
"Fieldsets": (
"STRING_VALUE",
"INT_VALUE",
),
},
)
def test_fieldset_ordering_1(self):
2024-07-05 14:38:26 +00:00
"""Ordering of inner list should be preserved."""
2025-10-07 09:25:07 +00:00
self.client.login(username="admin", password="nimda")
request = self.rf.get("/admin/constance/config/")
request.user = self.superuser
response = self.options.changelist_view(request, {})
response.render()
content_str = response.content.decode()
2025-10-07 09:25:07 +00:00
self.assertGreater(content_str.find("INT_VALUE"), content_str.find("STRING_VALUE"))
@mock.patch(
2025-10-07 09:25:07 +00:00
"constance.settings.CONFIG_FIELDSETS",
{
2025-10-07 09:25:07 +00:00
"Fieldsets": (
"INT_VALUE",
"STRING_VALUE",
),
},
)
def test_fieldset_ordering_2(self):
2024-07-05 14:38:26 +00:00
"""Ordering of inner list should be preserved."""
2025-10-07 09:25:07 +00:00
self.client.login(username="admin", password="nimda")
request = self.rf.get("/admin/constance/config/")
request.user = self.superuser
response = self.options.changelist_view(request, {})
response.render()
content_str = response.content.decode()
2025-10-07 09:25:07 +00:00
self.assertGreater(content_str.find("STRING_VALUE"), content_str.find("INT_VALUE"))
2018-03-06 10:16:44 +00:00
@mock.patch(
"constance.settings.ADDITIONAL_FIELDS",
{
"language_select": [
"django.forms.fields.TypedMultipleChoiceField",
{
"widget": "django.forms.CheckboxSelectMultiple",
"choices": (("en", "English"), ("de", "German"), ("fr", "French")),
"coerce": str,
},
],
},
)
@mock.patch(
"constance.settings.CONFIG",
{
"LANGUAGES": (["en", "de"], "Supported languages", "language_select"),
},
)
def test_reset_to_default_multi_select(self):
"""
Test that multi-select config values render with data-field-type='multi-select'
and a JSON-encoded data-default attribute.
"""
# Re-parse additional fields so the mock is picked up by the form
from constance.forms import FIELDS
from constance.forms import parse_additional_fields
FIELDS.update(
parse_additional_fields(
{
"language_select": [
"django.forms.fields.TypedMultipleChoiceField",
{
"widget": "django.forms.CheckboxSelectMultiple",
"choices": (("en", "English"), ("de", "German"), ("fr", "French")),
"coerce": str,
},
]
}
)
)
try:
self.client.login(username="admin", password="nimda")
request = self.rf.get("/admin/constance/config/")
request.user = self.superuser
response = self.options.changelist_view(request, {})
response.render()
content = response.content.decode()
self.assertIn('data-field-type="multi-select"', content)
self.assertIn('data-default="[&quot;en&quot;, &quot;de&quot;]"', content)
finally:
# Clean up FIELDS to avoid leaking into other tests
FIELDS.pop("language_select", None)
2018-03-06 10:16:44 +00:00
def test_labels(self):
self.assertEqual(type(self.model._meta.label), str)
self.assertEqual(type(self.model._meta.label_lower), str)