8000 fix: Grouper models must not assume language grouper (#8194) by fsbraun · Pull Request #8195 · django-cms/django-cms · GitHub
[go: up one dir, main page]

Skip to content

fix: Grouper models must not assume language grouper (#8194) #8195

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 2, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
fix: Grouper models w/o must not assume language grouper (#8194)
* fix: Grouper models w/o language grouper

* Keep language field, but not as grouper

* Update cms/tests/test_grouper_admin.py

Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>

* Update cms/admin/utils.py

Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>

* Update cms/tests/test_grouper_admin.py

Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>

* Update cms/tests/test_grouper_admin.py

Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>

* Update cms/tests/test_grouper_admin.py

Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>

* Update cms/tests/test_grouper_admin.py

Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>

* Fix ruff issues

---------

Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
  • Loading branch information
fsbraun and sourcery-ai[bot] committed Apr 2, 2025
commit 5de81ff67ca6b973f3118d2b678fedcad22f84c1
7 changes: 4 additions & 3 deletions cms/admin/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from cms.models.managers import ContentAdminManager
from cms.toolbar.utils import get_object_preview_url
from cms.utils import get_language_from_request
from cms.utils.i18n import get_language_dict, get_language_tuple
from cms.utils.i18n import get_language_dict, get_language_list, get_language_tuple
from cms.utils.urlutils import admin_reverse, static_with_version


Expand Down Expand Up @@ -771,8 +771,9 @@ def update_labels(self, fields: typing.List[str]) -> None:

def clean(self) -> dict:
if (
self.cleaned_data.get(CONTENT_PREFIX + "language", None)
not in get_language_dict()
f"{CONTENT_PREFIX}language" in self.cleaned_data
and self.cleaned_data[f"{CONTENT_PREFIX}language"]
not in get_language_list()
):
raise ValidationError(
_(
Expand Down
9 changes: 9 additions & 0 deletions cms/test_utils/project/sampleapp/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
GrouperModel,
Picture,
SampleAppConfig,
SimpleGrouperModel,
SomeEditableModel,
)

Expand All @@ -31,7 +32,15 @@ def can_change_content(self, request, content_obj):
return getattr(self, "change_content", True)


class SimpleGrouperAdmin(GrouperModelAdmin):
list_display = ("category_name", "content__secret_greeting", "admin_list_actions")

def can_change_content(self, request, content_obj):
return getattr(self, "change_content", True)


admin.site.register(Category, CategoryAdmin)
admin.site.register(SampleAppConfig)
admin.site.register(SomeEditableModel, SomeEditableAdmin)
admin.site.register(GrouperModel, GrouperAdmin)
admin.site.register(SimpleGrouperModel, SimpleGrouperAdmin)
45 changes: 45 additions & 0 deletions cms/test_utils/project/sampleapp/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,51 @@ class GrouperModelContent(models.Model):
)
)

region = models.TextField(
default="world",
max_length=10,
choices=(
("world", "World"),
("americas", "Americas"),
("europe", "Europe"),
("africa", "Africa"),
("asia", "Asia"),
("australia", "Australia")
)
)

uptodate = models.BooleanField(
verbose_name="Yes/No",
default=False,
)

secret_greeting = models.TextField(
max_length=100,
)


class SimpleGrouperModel(models.Model):
category_name = models.CharField(max_length=200, default="")


class SimpleGrouperModelContent(models.Model):
# grouper field name: snake case of GrouperModel
simple_grouper_model = models.ForeignKey(
SimpleGrouperModel,
on_delete=models.CASCADE,
)

language = models.TextField(
default="en",
choices=(
("en", "English"),
("de", "German"),
("it", "Italian"),
)
)



region = models.TextField(
default="world",
max_length=10,
Expand Down
183 changes: 173 additions & 10 deletions cms/tests/test_grouper_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from cms.test_utils.project.sampleapp.models import (
GrouperModel,
GrouperModelContent,
SimpleGrouperModel,
SimpleGrouperModelContent,
)
from cms.test_utils.testcases import CMSTestCase
from cms.test_utils.util.grouper import wo_content_permission
Expand All @@ -25,6 +27,8 @@ def setUp(self) -> None:
self.changelist_url = admin_reverse("sampleapp_groupermodel_changelist")
self.admin_user = self.get_superuser()
self.admin = site._registry[GrouperModel]
self.groupermodel = "groupermodel"
self.grouper_model = "grouper_model"

def tearDown(self) -> None:
self.grouper_instance.delete()
Expand All @@ -42,13 +46,44 @@ def createContentInstance(self, language="en"):
return instance


class ChangeListActionsTestCase(SetupMixin, CMSTestCase):
class SimpleSetupMixin:
"""Create one grouper object and retrieve the admin instance"""
def setUp(self) -> None:
self.grouper_instance = SimpleGrouperModel.objects.create(
category_name="Grouper Category"
)
self.add_url = admin_reverse("sampleapp_simplegroupermodel_add")
self.change_url = admin_reverse("sampleapp_simplegroupermodel_change", args=(self.grouper_instance.pk,))
self.changelist_url = admin_reverse("sampleapp_simplegroupermodel_changelist")
self.admin_user = self.get_superuser()
self.admin = site._registry[SimpleGrouperModel]
self.groupermodel = "simplegroupermodel"
self.grouper_model = "simple_grouper_model"

def tearDown(self) -> None:
self.grouper_instance.delete()
self.admin.clear_content_cache() # The admin does this automatically for each new request.

def createContentInstance(self, language="en"):
"""Creates a content instance with a random content for a language. The random content is returned
to be able to check if it appears in forms etc."""

assert language == "en", "Only English is supported for SimpleGrouperModelContent"
instance = SimpleGrouperModelContent.objects.create(
simple_grouper_model=self.grouper_instance,
secret_greeting=get_random_string(16),
)
self.admin.clear_content_cache() # The admin does this automatically for each new request.
return instance


class SimpleChangeListActionsTestCase(SimpleSetupMixin, CMSTestCase):
def test_action_js_css(self):
"""Are js and css files loaded?
The js and css files are supposed to be arranged by the GrouperAdminMixin."""
with self.login_user_context(self.admin_user):
# Act
response = self.client.get(self.changelist_url + "?language=en")
response = self.client.get(f"{self.changelist_url}?", follow=True)
# Assert
self.assertContains(response, static("admin/js/jquery.init.js"))
self.assertContains(response, static("cms/js/admin/actions.js"))
Expand All @@ -59,11 +94,11 @@ def test_add_action(self):
The button is supposed to be arranged by the GrouperAdminMixin."""
with self.login_user_context(self.admin_user):
# Act
response = self.client.get(self.changelist_url + "?language=en")
response = self.client.get(f"{self.changelist_url}?language=en", follow=True)
# Assert
self.assertContains(response, 'class="cms-icon cms-icon-plus"')
self.assertContains(response, f'href="/en/admin/sampleapp/groupermodel/{self.grouper_instance.pk}'
f'/change/?language=en"')
self.assertContains(response, f'href="/en/admin/sampleapp/{self.groupermodel}/{self.grouper_instance.pk}'
f'/change/?')
self.assertNotContains(response, 'class="cms-icon cms-icon-view"')

def test_change_action(self):
Expand All @@ -72,11 +107,11 @@ def test_change_action(self):
self.createContentInstance("en")
with self.login_user_context(self.admin_user):
# Act
response = self.client.get(self.changelist_url + "?language=en")
response = self.client.get(f"{self.changelist_url}?language=en", follow=True)
# Assert
self.assertContains(response, 'class="cms-icon cms-icon-view"')
self.assertContains(response, f'href="/en/admin/sampleapp/groupermodel/{self.grouper_instance.pk}'
f'/change/?language=en"')
self.assertContains(response, f'href="/en/admin/sampleapp/{self.groupermodel}/{self.grouper_instance.pk}'
f'/change/?')
self.assertContains(response, 'class="cms-icon cms-icon-view"')

def test_get_action(self):
Expand Down Expand Up @@ -104,6 +139,10 @@ def test_post_action(self):
self.assertIn("cms-form-post-method", get_action)


class ChangeListActionsTestCase(SetupMixin, SimpleChangeListActionsTestCase):
pass


class GrouperModelAdminTestCase(SetupMixin, CMSTestCase):
def test_form_class_created(self):
"""The form class has automatically been enhanced with the GrouperAdminFormMixin for
Expand Down Expand Up @@ -177,14 +216,57 @@ def test_with_content_only(self) -> None:
self.assertContains(response, random_content[language])


class SimpleGrouperChangeListTestCase(SimpleSetupMixin, CMSTestCase):
def test_mixed_change_form(self):
"""Change form contains input for both grouper and content objects"""
# Arrange
random_content = self.createContentInstance("en")
with self.login_user_context(self.admin_user):
# Act
response = self.client.get(f"{self.change_url}?language=en", follow=True)
# Assert
# Contains relation to grouper as hidden input
self.assertContains(
response,
'<input type="hidden" name="content__simple_grouper_model"',
)
# Contains grouper field with category (and its value)
self.assertContains(
response,
'<input type="text" name="category_name" value="Grouper Category"',
)
# Contains content secret message as textarea
self.assertContains(response, '<textarea name="content__secret_greeting"')
self.assertContains(response, random_content.secret_greeting)

def test_empty_content(self) -> None:
"""Without any content being created the changelist shows an empty content text"""
with self.login_user_context(self.admin_user):
# Act
response = self.client.get(self.changelist_url)
# Assert
self.assertContains(response, "Empty content")

def test_with_content(self) -> None:
"""Create one content object and see if it appears in the admin"""
# Arrange
random_content = self.createContentInstance()
with self.login_user_context(self.admin_user):
# Act
response = self.client.get(self.changelist_url)
# Assert
self.assertContains(response, "Grouper Category")
self.assertContains(response, random_content.secret_greeting)


class GrouperChangeTestCase(SetupMixin, CMSTestCase):
def test_mixed_change_form(self):
"""Change form contains input for both grouper and content objects"""
# Arrange
random_content = self.createContentInstance("en")
with self.login_user_context(self.admin_user):
# Act
response = self.client.get(self.change_url + "?language=en")
response = self.client.get(f"{self.change_url}?language=en", follow=True)
# Assert
# Contains relation to grouper as hidden input
self.assertContains(
Expand All @@ -208,7 +290,7 @@ def test_mixed_change_form(self):
def test_change_form_contains_defaults_for_groupers(self) -> None:
with self.login_user_context(self.admin_user):
# Act
response = self.client.get(self.change_url + "?language=en")
response = self.client.get(self.change_url + "?language=en", follow=True)
# Assert
self.assertContains(response, 'name="content__language" value="en"')
self.assertNotContains(response, 'name="content__language" value="de"')
Expand Down Expand Up @@ -238,6 +320,11 @@ def test_change_form_wo_write_permit(self) -> None:
response,
'<input type="hidden" name="content__language" value="en" id="id_content__language">',
)
# Contains extra grouping field as hidden input
self.assertContains(
response,
'<input type="hidden" name="content__language" value="en" id="id_content__language">',
)
# Contains grouper field with category (and its value)
self.assertContains(response, '<input type="text" name="category_name" value="Grouper Category"')
# Does not contain content secret message as textarea
Expand Down Expand Up @@ -333,3 +420,79 @@ def test_create_content_model(self) -> None:
self.assertEqual(content_instance_en.secret_greeting, random_content.secret_greeting) # unchanged
self.assertIsNotNone(content_instance_de) # Exists?
self.assertEqual(content_instance_de.secret_greeting, data["content__secret_greeting"]) # Has new content


class SimpleGrouperChangeTestCase(SimpleSetupMixin, CMSTestCase):
def test_save_grouper_model(self) -> None:
# Arrange
random_content = self.createContentInstance()
data = {
"category_name": "Changed content",
"content__region": "world",
"content__language": "de",
"content__secret_greeting": random_content.secret_greeting,
}
with self.login_user_context(self.admin_user):
# Act
response = self.client.post(self.change_url, data=data)
# Assert
self.grouper_instance.refresh_from_db()
self.assertEqual(response.status_code, 302) # Expecting redirect
self.assertEqual(self.grouper_instance.category_name, data["category_name"])

def test_save_content_model(self) -> None:
# Arrange
self.createContentInstance()
data = {
"category_name": self.grouper_instance.category_name,
"content__region": "world",
"content__language": "de",
"content__secret_greeting": "New greeting",
}
# Act
with self.login_user_context(self.admin_user):
response = self.client.post(self.change_url, data=data)
content_instance = SimpleGrouperModelContent.objects.first()
# Assert
self.assertEqual(response.status_code, 302) # Expecting redirect
self.assertIsNotNone(content_instance)
self.assertEqual(content_instance.secret_greeting, data["content__secret_greeting"])

def test_create_grouper_model(self) -> None:
# Arrange
data = {
"category_name": "My new category",
"content__region": "world",
"content__language": "de",
"content__secret_greeting": "Some new content",
}
# Act
with self.login_user_context(self.admin_user):
response = self.client.post(self.add_url, data=data)
grouper_instance = SimpleGrouperModel.objects.filter(category_name=data["category_name"]).first()
content_instance = grouper_instance.simplegroupermodelcontent_set.first() # Get content

# Assert
self.assertEqual(response.status_code, 302) # Expecting redirect
self.assertEqual(SimpleGrouperModel.objects.all().count(), 2)
self.assertIsNotNone(grouper_instance)
self.assertIsNotNone(content_instance) # Should exist
self.assertEqual(content_instance.secret_greeting, data["content__secret_greeting"]) # Has new content

def test_create_content_model(self) -> None:
# Arrange
self.createContentInstance()
data = {
"category_name": self.grouper_instance.category_name,
"content__region": "world",
"content__language": "de",
"content__secret_greeting": "New content",
}
# Act
with self.login_user_context(self.admin_user):
response = self.client.post(self.change_url, data=data)
content_instance = SimpleGrouperModelContent.objects.first() # Get content
# Assert
self.assertEqual(response.status_code, 302) # Expecting redirect
self.assertIsNotNone(content_instance)
self.assertEqual(content_instance.secret_greeting, data["content__secret_greeting"]) # Has new content
Loading
0