Merged in hotfix/MS-932-WagtailCustomization (pull request #147)
Hotfix/MS-932 WagtailCustomization
This commit is contained in:
commit
000192ba36
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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([
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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, )
|
||||
|
|
@ -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>
|
||||
|
|
@ -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 %}
|
||||
|
|
@ -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")
|
||||
|
|
|
|||
Loading…
Reference in New Issue