Merged in hotfix/MS-932-WagtailCustomization (pull request #147)

Hotfix/MS-932 WagtailCustomization
This commit is contained in:
Lorenz Padberg 2024-04-15 13:04:27 +00:00 committed by Ramon Wenger
commit 000192ba36
11 changed files with 200 additions and 161 deletions

View File

@ -1,6 +1,6 @@
from django.db import models
from django.utils.text import slugify
from wagtail.admin.panels import FieldPanel
from wagtail.admin.panels import FieldPanel, TitleFieldPanel
from wagtail.fields import RichTextField, StreamField
from wagtail.images.blocks import ImageChooserBlock
@ -110,7 +110,7 @@ class BasicKnowledge(StrictHierarchyPage):
)
content_panels = [
FieldPanel("title", classname="full title"),
TitleFieldPanel("title", classname="full title"),
FieldPanel("new_type"),
FieldPanel("intro"),
FieldPanel("contents"),

View File

@ -1,6 +1,6 @@
import logging
from wagtail.admin.panels import FieldPanel, TabbedInterface, ObjectList
from wagtail.admin.panels import TabbedInterface, ObjectList, TitleFieldPanel
from core.wagtail_utils import StrictHierarchyPage, get_default_settings
@ -13,7 +13,7 @@ class Book(StrictHierarchyPage):
verbose_name_plural = 'Bücher'
content_panels = [
FieldPanel('title', classname="full title")
TitleFieldPanel('title', classname="full title")
]
edit_handler = TabbedInterface([

View File

@ -1,7 +1,7 @@
import logging
from django.db import models
from wagtail.admin.panels import FieldPanel, TabbedInterface, ObjectList
from wagtail.admin.panels import FieldPanel, TabbedInterface, ObjectList, TitleFieldPanel
from core.wagtail_utils import StrictHierarchyPage, get_default_settings
from users.models import SchoolClass
@ -18,7 +18,7 @@ class Chapter(StrictHierarchyPage, GraphqlNodeMixin):
description = models.TextField(blank=True)
content_panels = [
FieldPanel("title", classname="full title"),
TitleFieldPanel("title", classname="full title"),
FieldPanel("description", classname="full description"),
]

View File

@ -3,6 +3,7 @@ from wagtail.admin.panels import (
FieldPanel,
TabbedInterface,
ObjectList,
TitleFieldPanel,
)
from wagtail.blocks import StreamBlock
from wagtail.fields import StreamField
@ -140,7 +141,7 @@ class ContentBlock(StrictHierarchyPage, GraphqlNodeMixin):
type = models.CharField(max_length=100, choices=TYPE_CHOICES, default=NORMAL)
content_panels = [
FieldPanel("title", classname="full title"),
TitleFieldPanel("title", classname="full title"),
FieldPanel("type"),
FieldPanel("contents"),
]

View File

@ -1,14 +1,16 @@
from django import forms
from core.constants import DEFAULT_RICH_TEXT_FEATURES
from core.wagtail_utils import StrictHierarchyPage, get_default_settings
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.fields import RichTextField
from core.constants import DEFAULT_RICH_TEXT_FEATURES
from core.wagtail_utils import StrictHierarchyPage, get_default_settings
from users.models import SchoolClass
from wagtail.admin.panels import (
FieldPanel,
ObjectList,
TabbedInterface,
TitleFieldPanel,
)
from wagtail.fields import RichTextField
EXACT = "exact"
@ -51,20 +53,23 @@ class ModuleCategory(models.Model):
return f"{self.name}"
class ModulePageForm(WagtailAdminPageForm):
def clean(self):
cleaned_data = super().clean()
if "slug" in self.cleaned_data:
page_slug = cleaned_data["slug"]
if not Module._slug_is_available(page_slug, self.instance):
self.add_error(
"slug",
forms.ValidationError(
_("The slug '%(page_slug)s' is already in use")
% {"page_slug": page_slug}
),
)
return cleaned_data
# Commented out since that check is not necessary if a slug is chosen that is already in use
# a new one will be generated
# TODO: remove after pullrequest is merged
# class ModulePageForm(WagtailAdminPageForm):
# def clean(self):
# cleaned_data = super().clean()
# if "slug" in self.cleaned_data and "id" in self.cleaned_data:
# page_slug = cleaned_data["slug"]
# if not Module._slug_is_available(page_slug, self.parent_page, self.instance):
# self.add_error(
# "slug",
# forms.ValidationError(
# _("The slug '%(page_slug)s' is already in use")
# % {"page_slug": page_slug}
# ),
# )
# return cleaned_data
class Module(StrictHierarchyPage):
@ -97,7 +102,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 +111,8 @@ class Module(StrictHierarchyPage):
FieldPanel("teaser"),
FieldPanel("intro"),
]
base_form_class = ModulePageForm
# TODO remove after pullrequest is merged
# base_form_class = ModulePageForm
edit_handler = TabbedInterface(
[ObjectList(content_panels, heading="Content"), get_default_settings()]
@ -181,11 +187,44 @@ 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)
# Always create a slug if it is not available
# todo: do we really want to do this? this will silently change a slug if the users sets one that already exists, which probably isn't what they expect
if not self._slug_is_available(self.slug, self.get_parent(), self):
self.slug = self._get_autogenerated_slug(self.slug)
class RecentModule(models.Model):

View File

@ -1,7 +1,7 @@
import logging
from django.db import models
from wagtail.admin.panels import FieldPanel, TabbedInterface, ObjectList
from wagtail.admin.panels import FieldPanel, TabbedInterface, ObjectList, TitleFieldPanel
from wagtail.fields import RichTextField
from core.constants import DEFAULT_RICH_TEXT_FEATURES
@ -22,7 +22,7 @@ class Topic(StrictHierarchyPage):
instructions = models.CharField(max_length=255, blank=True, null=True, default=None)
content_panels = [
FieldPanel('title', classname="full title"),
TitleFieldPanel('title', classname="full title"),
FieldPanel('order'),
FieldPanel('teaser'),
FieldPanel('vimeo_id'),

View File

@ -0,0 +1,30 @@
from books.factories import ModuleFactory
from books.models import Chapter
from core.tests.base_test import SkillboxTestCase
from django.test.client import Client
class TestChapterCreation(SkillboxTestCase):
"""Test created for Issue MS-932"""
def setUp(self) -> None:
self.createDefault()
self.module = ModuleFactory(slug="my-module")
self.Client = Client()
self.client.login(username="admin", password="test")
def test_create_chapter_creates_slug_automatically(self):
new_chapter = Chapter(title="New Chapter")
self.module.add_child(instance=new_chapter)
new_chapter.save()
self.assertEqual("new-chapter", new_chapter.slug)
def test_create_chapter_creates_slug_automatically_if_existing(self):
new_chapter = Chapter(title="New Chapter")
self.module.add_child(instance=new_chapter)
new_chapter.save()
self.assertEqual("new-chapter", new_chapter.slug)
new_chapter2 = Chapter(title="New Chapter")
self.module.add_child(instance=new_chapter2)
new_chapter2.save()
self.assertEqual("new-chapter-2", new_chapter2.slug)

View File

@ -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, )

View File

@ -1,124 +0,0 @@
{# This template is overwritten to create a custom cms ui for the model "chapter" to improve navigation experience.#}
{# See MS-538#}
{% load i18n %}
{% load l10n %}
{% load wagtailadmin_tags %}
<table class="listing {% if full_width %}full-width{% endif %} {% block table_classname %}{% endblock %}">
{% if show_ordering_column or show_bulk_actions %}
<col width="10px"/>
{% endif %}
<col/>
{% if show_parent %}
<col/>
{% endif %}
<col width="12%"/>
<col width="12%"/>
<col width="12%"/>
<col width="10%"/>
<thead>
{% block pre_parent_page_headers %}
{% endblock %}
{% if parent_page %}
{% page_permissions parent_page as parent_page_perms %}
<tr class="index {% if not parent_page.live %} unpublished{% endif %}
{% block parent_page_row_classname %}{% endblock %}">
<td class="title"{% if show_ordering_column or show_bulk_actions %} colspan="2"{% endif %}>
{% block parent_page_title %}
{% endblock %}
</td>
<td class="updated" valign="bottom">{% if parent_page.latest_revision_created_at %}
<div class="human-readable-date"
title="{{ parent_page.latest_revision_created_at|date:"DATETIME_FORMAT" }}">
{% blocktrans with time_period=parent_page.latest_revision_created_at|timesince %}{{ time_period }}
ago{% endblocktrans %}</div>{% endif %}</td>
<td class="type" valign="bottom">
{% if not parent_page.is_root %}
{{ parent_page.content_type.model_class.get_verbose_name }}
{% endif %}
</td>
<td class="status" valign="bottom">
{% if not parent_page.is_root %}
{% include "wagtailadmin/shared/page_status_tag.html" with page=parent_page %}
{% endif %}
</td>
<td></td>
</tr>
{% endif %}
{% block post_parent_page_headers %}
{% endblock %}
</thead>
<tbody>
{% if pages %}
{% trans "Select page" as checkbox_aria_label %}
{% for page in pages %}
{% page_permissions page as page_perms %}
<tr {% if ordering == "ord" %}id="page_{{ page.id|unlocalize }}"
data-page-title="{{ page.get_admin_display_title }}"{% endif %}
class="{% if not page.live %}unpublished{% endif %} {% block page_row_classname %}{% endblock %}">
{% if show_ordering_column %}
<td class="ord">{% if orderable and ordering == "ord" %}
<div class="handle icon icon-grip text-replace">{% trans 'Drag' %}</div>{% endif %}</td>
{% elif show_bulk_actions %}
{% include "wagtailadmin/bulk_actions/listing_checkbox_cell.html" with obj_type="page" obj=page aria_labelledby_prefix="page_" aria_labelledby=page.pk|unlocalize aria_labelledby_suffix="_title" %}
{% endif %}
<td id="page_{{ page.pk|unlocalize }}_title" class="title" valign="top" data-listing-page-title>
{% block page_title %}
{% endblock %}
{% if page.content_type.model == 'chapter' %}
<div style="margin-top:10px; padding-left: 30px">
<ul>
{% for c in page.get_children %}
{% if not c.specific.user_created and not c.specific.contentblocksnapshot %}
<li>
{% if page_perms.can_edit %}
<a href="{% url 'wagtailadmin_pages:edit' c.id %}"
title="{% trans 'Edit this page' %}"> {{ c.get_admin_display_title }}
</a>
{% else %}
{{ c.get_admin_display_title }}
{% endif %}
</li>
{% endif %}
{% endfor %}
</ul>
</div>
{% endif %}
</td>
{% if show_parent %}
<td class="parent" valign="top">
{% block page_parent_page_title %}
{% with page.get_parent as parent %}
{% if parent %}
<a href="{% url 'wagtailadmin_explore' parent.id %}">{{ parent.specific_deferred.get_admin_display_title }}</a>
{% endif %}
{% endwith %}
{% endblock %}
</td>
{% endif %}
<td class="updated" valign="top">{% if page.latest_revision_created_at %}
<div class="human-readable-date"
title="{{ page.latest_revision_created_at|date:"DATETIME_FORMAT" }}">
{% blocktrans with time_period=page.latest_revision_created_at|timesince %}{{ time_period }}
ago{% endblocktrans %}</div>{% endif %}</td>
<td class="type" valign="top">{{ page.content_type.model_class.get_verbose_name }}</td>
<td class="status" valign="top">
{% include "wagtailadmin/shared/page_status_tag.html" with page=page %}
</td>
{% block page_navigation %}
{% endblock %}
</tr>
{% endfor %}
{% else %}
{% block no_results %}{% endblock %}
{% endif %}
</tbody>
</table>

View File

@ -0,0 +1,60 @@
{% load i18n wagtailadmin_tags %}
{# The title field for a page in the page listing, when in 'explore' mode #}
<div class="title-wrapper">
{% if page.is_site_root %}
{% if perms.wagtailcore.add_site or perms.wagtailcore.change_site or perms.wagtailcore.delete_site %}
<a href="{% url 'wagtailsites:index' %}" title="{% trans 'Sites menu' %}">{% icon name="site" classname="initial"
%}</a>
{% endif %}
{% endif %}
{% if page_perms.can_edit %}
<a href="{% url 'wagtailadmin_pages:edit' page.id %}" title="{% trans 'Edit this page' %}">
{% if not page.is_site_root and not page.is_leaf %}{% icon name="folder" classname="initial" %}{% endif %}
{{ page.get_admin_display_title }}
</a>
{% else %}
{% if not page.is_site_root and not page.is_leaf %}{% icon name="folder" classname="initial" %}{% endif %}
{{ page.get_admin_display_title }}
{% endif %}
{% if show_locale_labels %}
{% status page.locale.get_display_name classname="w-status--label" %}
{% endif %}
{% include "wagtailadmin/pages/listing/_privacy_indicator.html" with page=page %}
{% include "wagtailadmin/pages/listing/_locked_indicator.html" with page=page %}
</div>
<ul class="actions">
{% page_listing_buttons page request.user next_url=actions_next_url %}
</ul>
<!--Here starts the customization part. -->
<!--Commit: 3c5c9422353964aa25cdd04b296859f71c4c1a34-->
{% if page.content_type.model == 'chapter' %}
<div style="margin-top:10px; padding-left: 30px">
<ul>
{% for c in page.get_children %}
{% if not c.specific.user_created and not c.specific.contentblocksnapshot %}
<li>
{% if page_perms.can_edit %}
<a href="{% url 'wagtailadmin_pages:edit' c.id %}"
title="{% trans 'Edit this page' %}"> {{ c.get_admin_display_title }}
</a>
{% else %}
{{ c.get_admin_display_title }}
{% endif %}
</li>
{% endif %}
{% endfor %}
</ul>
</div>
{% endif %}

View File

@ -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"), CommentPanel()], heading="Settings")
return ObjectList([FieldPanel("slug", widget=SlugInput), CommentPanel()], heading="Settings")