diff --git a/server/books/models/module.py b/server/books/models/module.py index 745a85f1..69376add 100644 --- a/server/books/models/module.py +++ b/server/books/models/module.py @@ -3,12 +3,14 @@ from django.db import models from django.utils import timezone from django.utils.translation import gettext as _ from wagtail.admin.forms import WagtailAdminPageForm -from wagtail.admin.panels import FieldPanel, TabbedInterface, ObjectList +from wagtail.admin.panels import FieldPanel, TabbedInterface, ObjectList, TitleFieldPanel from wagtail.fields import RichTextField +from django.conf import settings from core.constants import DEFAULT_RICH_TEXT_FEATURES from core.wagtail_utils import StrictHierarchyPage, get_default_settings from users.models import SchoolClass +from django.utils.text import slugify EXACT = "exact" @@ -54,9 +56,10 @@ class ModuleCategory(models.Model): class ModulePageForm(WagtailAdminPageForm): def clean(self): cleaned_data = super().clean() + print("cleaning") if "slug" in self.cleaned_data: page_slug = cleaned_data["slug"] - if not Module._slug_is_available(page_slug, self.instance): + if not Module._slug_is_available(page_slug, self.parent_page, self.instance): self.add_error( "slug", forms.ValidationError( @@ -97,7 +100,7 @@ class Module(StrictHierarchyPage): solutions_enabled_for = models.ManyToManyField(SchoolClass) content_panels = [ - FieldPanel("title", classname="full title"), + TitleFieldPanel("title", classname="full title"), FieldPanel("meta_title", classname="full title"), FieldPanel("level"), FieldPanel("category"), @@ -106,7 +109,7 @@ class Module(StrictHierarchyPage): FieldPanel("teaser"), FieldPanel("intro"), ] - base_form_class = ModulePageForm + # base_form_class = ModulePageForm edit_handler = TabbedInterface( [ObjectList(content_panels, heading="Content"), get_default_settings()] @@ -181,11 +184,42 @@ class Module(StrictHierarchyPage): return f"{self.meta_title} - {self.title}" @staticmethod - def _slug_is_available(slug, page): - # modeled after `Page._slug_is_available` - modules = Module.objects.filter(slug=slug).not_page(page) + def _slug_is_available(slug, parent_page, page=None): + """ - return not modules.exists() + # modeled after `Page._slug_is_available` + + Determine whether the given slug is available for use on a child page of + parent_page. If 'page' is passed, the slug is intended for use on that page + (and so it will be excluded from the duplicate check). + """ + if parent_page is None: + # the root page's slug can be whatever it likes... + return True + + modules = Module.objects.all() + if page: + modules = modules.not_page(page) + + return not modules.filter(slug=slug).exists() + + def _get_autogenerated_slug(self, base_slug): + # modeled after `Page._get_autogenerated_slug` + candidate_slug = base_slug + suffix = 1 + parent_page = self.get_parent() + + while not self._slug_is_available(candidate_slug, parent_page, self): + # try with incrementing suffix until we find a slug which is available + suffix += 1 + candidate_slug = "%s-%d" % (base_slug, suffix) + return candidate_slug + + def full_clean(self, *args, **kwargs): + super().full_clean(*args, **kwargs) + + if not self._slug_is_available(self.slug, self.get_parent(), self): + self.slug = self._get_autogenerated_slug(self.slug) class RecentModule(models.Model): diff --git a/server/books/tests/test_module_creation.py b/server/books/tests/test_module_creation.py new file mode 100644 index 00000000..91356e32 --- /dev/null +++ b/server/books/tests/test_module_creation.py @@ -0,0 +1,32 @@ +from django.test import TestCase, RequestFactory +from unittest import skip +from graphene.test import Client +from graphql_relay import to_global_id + +from api.schema import schema +from api.utils import get_object +from books.models import ContentBlock, Chapter +from books.factories import ModuleFactory, ModuleLevelFactory, TopicFactory +from core.factories import UserFactory +from users.models import User + + +class TestModuleCreation(TestCase): + """ + Since the modules url in the frontend is not /topic/module but /module the slug has to be unique. + This test checks if the slug is generated correctly. + """ + + def test_create_new_module_generates_slug(self): + topic = TopicFactory(title="Berufslehre") + module = ModuleFactory(title="Modul 1", parent=topic) + self.assertEqual("modul-1", module.slug) + + def test_create_new_module_different_topic(self): + topic = TopicFactory(title="Berufslehre") + module = ModuleFactory(title="Modul 1", parent=topic) + + topic2 = TopicFactory(title="Geld und Macht") + module2 = ModuleFactory(title="Modul 1", parent=topic2) + self.assertEqual("modul-1", module.slug) + self.assertEqual("modul-1-2", module2.slug, ) diff --git a/server/core/wagtail_utils.py b/server/core/wagtail_utils.py index 790a5611..06950734 100644 --- a/server/core/wagtail_utils.py +++ b/server/core/wagtail_utils.py @@ -2,6 +2,7 @@ from django.contrib import admin from wagtail.admin.panels import CommentPanel from wagtail.admin.panels import FieldPanel, ObjectList from wagtail.models import Page +from wagtail.admin.widgets.slug import SlugInput class StrictHierarchyPage(Page): @@ -41,4 +42,4 @@ def wagtail_parent_filter(parent_cls, child_cls): def get_default_settings(): - return ObjectList([FieldPanel("slug", read_only=True), CommentPanel()], heading="Settings") + return ObjectList([FieldPanel("slug", widget=SlugInput), CommentPanel()], heading="Settings")