Ver Fonte

Switch to Wagtail 5 (#597)

Fixes #590 

* Removed dead code from wagtail_flexible_forms.
* Combined admin and editor CSS.
* Updated dependencies and fixed tests for icalendar==5.0.*
Vince Salvino há 1 ano atrás
pai
commit
37059634bd

+ 6 - 4
azure-pipelines.yml

@@ -31,14 +31,16 @@ stages:
       vmImage: 'ubuntu-latest'
     strategy:
       matrix:
-        py3.7:
-          PYTHON_VERSION: '3.7'
         py3.8:
           PYTHON_VERSION: '3.8'
         py3.9:
           PYTHON_VERSION: '3.9'
         py3.10:
           PYTHON_VERSION: '3.10'
+        py3.11:
+          PYTHON_VERSION: '3.11'
+        py3.12:
+          PYTHON_VERSION: '3.12'
 
     steps:
     - task: UsePythonVersion@0
@@ -85,7 +87,7 @@ stages:
     - task: UsePythonVersion@0
       displayName: 'Use Python version'
       inputs:
-        versionSpec: '3.10'
+        versionSpec: '3.12'
         architecture: 'x64'
 
     - script: python -m pip install -r requirements-ci.txt
@@ -138,7 +140,7 @@ stages:
     - task: UsePythonVersion@0
       displayName: 'Use Python version'
       inputs:
-        versionSpec: '3.10'
+        versionSpec: '3.12'
         architecture: 'x64'
 
     - script: python -m pip install -r requirements-ci.txt

+ 1 - 1
coderedcms/models/page_models.py

@@ -311,7 +311,7 @@ class CoderedPage(WagtailCacheMixin, SeoMixin, Page, metaclass=CoderedPageMeta):
     ###############
 
     search_fields = Page.search_fields + [
-        index.SearchField("seo_title", partial_match=True, boost=2),
+        index.SearchField("seo_title", boost=2),
         index.SearchField("search_description", boost=2),
         index.FilterField("index_show_subpages"),
         index.FilterField("index_order_by"),

+ 4 - 3
coderedcms/models/tests/test_page_models.py

@@ -1,5 +1,6 @@
-from datetime import datetime, timedelta
+from datetime import timedelta
 from django.test import Client
+from django.utils import timezone
 from wagtail.test.utils import WagtailPageTests
 
 from coderedcms.models.page_models import (
@@ -205,8 +206,8 @@ class EventPageTestCase(ConcreteBasicPageTestCase, WagtailPageTests):
     def setUp(self):
         super().setUp()
         self.occurrence = EventOccurrence(
-            start=datetime.now(),
-            end=datetime.now() + timedelta(days=1),
+            start=timezone.now(),
+            end=timezone.now() + timedelta(days=1),
             event=self.basic_page,
         )
         self.occurrence.save()

+ 0 - 1
coderedcms/project_template/basic/project_name/settings/base.py

@@ -46,7 +46,6 @@ INSTALLED_APPS = [
     "wagtail.search",
     "wagtail",
     "wagtail.contrib.settings",
-    "wagtail.contrib.modeladmin",
     "wagtail.contrib.table_block",
     "wagtail.admin",
     # Django

+ 0 - 1
coderedcms/project_template/sass/project_name/settings/base.py

@@ -45,7 +45,6 @@ INSTALLED_APPS = [
     "wagtail.search",
     "wagtail",
     "wagtail.contrib.settings",
-    "wagtail.contrib.modeladmin",
     "wagtail.contrib.table_block",
     "wagtail.admin",
     # Django

+ 46 - 0
coderedcms/static/coderedcms/css/crx-admin.css

@@ -55,3 +55,49 @@ textarea.monospace,
 .crx-banner ~ .content-wrapper {
     padding-top: calc(1.5em + 8px);
 }
+
+
+/* Streamfield editor */
+
+.crx-collapsible {
+  padding:0;
+  margin: -16px 0 10px 0;
+}
+.crx-collapsible .crx-collapsible-target {
+  padding: 10px 0 0;
+}
+.crx-collapsible > button {
+  max-width: 140px;
+}
+
+.crx-checkbox-group {
+  margin-bottom: 1em;
+}
+.crx-checkbox-group ul {
+  margin-top: 0.5em;
+}
+.crx-checkbox-group label {
+  padding-bottom: 0.25em;
+}
+.crx-checkbox-group label input {
+  margin-right: 0.25em;
+}
+
+.crx-callout {
+  background-color: #f0f1f2;
+  border-radius: 6px;
+  display: inline-block;
+  padding: 2em 3em;
+  text-align: center;
+}
+.crx-callout .crx-big-icon {
+  font-size: 10rem;
+  color: rgba(0,0,0,0.1);
+}
+.crx-callout h3 {
+  font-size: 1.2rem;
+  font-weight: 600;
+}
+.crx-callout p {
+  font-size: 1rem;
+}

+ 1 - 1
coderedcms/static/coderedcms/css/crx-editor.css

@@ -7,7 +7,7 @@ License: https://github.com/coderedcorp/coderedcms/blob/dev/LICENSE
 
 .crx-collapsible {
     padding:0;
-    margin-top: -36px;
+    margin: -16px 0 10px 0;
 }
 .crx-collapsible .crx-collapsible-target {
     padding: 10px 0 0;

+ 0 - 1
coderedcms/tests/settings.py

@@ -46,7 +46,6 @@ INSTALLED_APPS = [
     "wagtail.search",
     "wagtail",
     "wagtail.contrib.settings",
-    "wagtail.contrib.modeladmin",
     "wagtail.contrib.table_block",
     "wagtail.admin",
     # Django

+ 9 - 9
coderedcms/tests/test_urls.py

@@ -110,9 +110,9 @@ class TestEventURLs(unittest.TestCase):
         # Get datetimes from response and compare them to datetimes on page
         # startswith() is used because older versions of Python
         # use different datetime formatting, specifically for timezones
-        split_content = str(response._container[0]).split("VALUE=DATE-TIME:")
-        start = split_content[1].split("\\")[0]
-        end = split_content[2].split("\\")[0]
+        ical = str(response._container[0])
+        start = ical.split("DTSTART:")[1].split("\r\n")[0]
+        end = ical.split("DTEND:")[1].split("\r\n")[0]
         self.assertTrue(
             start.startswith(
                 EventOccurrence.objects.get(event=event_page).start.strftime(
@@ -185,9 +185,9 @@ class TestEventURLs(unittest.TestCase):
         # Get datetimes from response and compare them to datetimes on page
         # startswith() is used because older versions of Python
         # use different datetime formatting, specifically for timezones
-        split_content = str(response._container[0]).split("VALUE=DATE-TIME:")
-        start = split_content[1].split("\\")[0]
-        end = split_content[2].split("\\")[0]
+        ical = str(response._container[0])
+        start = ical.split("DTSTART:")[1].split("\r\n")[0]
+        end = ical.split("DTEND:")[1].split("\r\n")[0]
         self.assertTrue(
             start.startswith(
                 EventOccurrence.objects.get(event=event_page).start.strftime(
@@ -247,9 +247,9 @@ class TestEventURLs(unittest.TestCase):
         # Get datetimes from response and compare them to datetimes on page
         # startswith() is used because older versions of Python
         # use different datetime formatting, specifically for timezones
-        split_content = str(response._container[0]).split("VALUE=DATE-TIME:")
-        start = split_content[1].split("\\")[0]
-        end = split_content[2].split("\\")[0]
+        ical = str(response._container[0])
+        start = ical.split("DTSTART:")[1].split("\r\n")[0]
+        end = ical.split("DTEND:")[1].split("\r\n")[0]
         self.assertTrue(
             start.startswith(
                 EventOccurrence.objects.get(event=event_page).start.strftime(

Diff do ficheiro suprimidas por serem muito extensas
+ 2 - 2
coderedcms/tests/testapp/migrations/0001_initial.py


+ 0 - 383
coderedcms/wagtail_flexible_forms/wagtail_hooks.py

@@ -1,383 +0,0 @@
-from django.contrib.admin import SimpleListFilter
-from django.contrib.admin.utils import quote
-from django.shortcuts import redirect
-from django.urls import path, reverse
-from django.utils.translation import gettext_lazy as _
-from wagtail.contrib.modeladmin.helpers import (
-    PermissionHelper,
-    PagePermissionHelper,
-    PageAdminURLHelper,
-    AdminURLHelper,
-    ButtonHelper,
-)
-from wagtail.contrib.modeladmin.options import ModelAdmin
-from wagtail.contrib.modeladmin.views import IndexView, InstanceSpecificView
-from wagtail.admin import messages
-from wagtail import hooks
-from wagtail.models import Page
-from wagtail.contrib.forms.utils import get_forms_for_user
-
-from .models import SessionFormSubmission
-
-
-class FormIndexView(IndexView):
-    page_title = _("Forms")
-
-
-class FormPermissionHelper(PagePermissionHelper):
-    def user_can_list(self, user):
-        return get_forms_for_user(user).exists()
-
-    def user_can_create(self, user):
-        return False
-
-    def user_can_edit_obj(self, user, obj):
-        return False
-
-    def user_can_delete_obj(self, user, obj):
-        return False
-
-    def user_can_publish_obj(self, user, obj):
-        return False
-
-    def user_can_unpublish_obj(self, user, obj):
-        return False
-
-    def user_can_copy_obj(self, user, obj):
-        return False
-
-    def user_can_inspect_obj(self, user, obj):
-        return False
-
-
-class FormURLHelper(PageAdminURLHelper):
-    def _get_action_url_pattern(self, action):
-        if action == "index":
-            return r"^stream_forms/$"
-        return r"^stream_forms/%s/$" % action
-
-
-class FormAdmin(ModelAdmin):
-    model = Page
-    menu_label = _("Forms")
-    menu_icon = "form"
-    list_display = (
-        "title",
-        "unprocessed_submissions_link",
-        "all_submissions_link",
-        "edit_link",
-    )
-    index_view_class = FormIndexView
-    permission_helper_class = FormPermissionHelper
-    url_helper_class = FormURLHelper
-
-    def get_queryset(self, request):
-        return get_forms_for_user(request.user)
-
-    def all_submissions_link(
-        self, obj, label=_("See all submissions"), url_suffix=""
-    ):
-        return '<a href="%s?page_id=%s%s">%s</a>' % (
-            reverse(SubmissionAdmin().url_helper.get_action_url_name("index")),
-            obj.pk,
-            url_suffix,
-            label,
-        )
-
-    all_submissions_link.short_description = ""
-    all_submissions_link.allow_tags = True
-
-    def unprocessed_submissions_link(self, obj):
-        return self.all_submissions_link(
-            obj,
-            _("See unprocessed submissions"),
-            "&status=%s" % SubmissionStatusFilter.unprocessed_status,
-        )
-
-    unprocessed_submissions_link.short_description = ""
-    unprocessed_submissions_link.allow_tags = True
-
-    def edit_link(self, obj):
-        return '<a href="%s">%s</a>' % (
-            reverse("wagtailadmin_pages:edit", args=(obj.pk,)),
-            _("Edit this form page"),
-        )
-
-    edit_link.short_description = ""
-    edit_link.allow_tags = True
-
-
-class SubmissionStatusFilter(SimpleListFilter):
-    title = _("status")
-    parameter_name = "status"
-    unprocessed_status = ",".join(
-        (SessionFormSubmission.COMPLETE, SessionFormSubmission.REVIEWED)
-    )
-
-    def lookups(self, request, model_admin):
-        yield (self.unprocessed_status, _("Complete or reviewed"))
-        for status, verbose_status in SessionFormSubmission.STATUSES:
-            if status != SessionFormSubmission.INCOMPLETE:
-                yield status, verbose_status
-
-    def queryset(self, request, queryset):
-        status = self.value()
-        if not status:
-            return queryset
-        if "," in status:
-            return queryset.filter(status__in=status.split(","))
-        return queryset.filter(status=status)
-
-
-class SubmissionPermissionHelper(PermissionHelper):
-    def user_can_list(self, user):
-        return get_forms_for_user(user).exists()
-
-    def user_can_create(self, user):
-        return False
-
-    def user_can_edit_obj(self, user, obj):
-        return False
-
-    def user_can_inspect_obj(self, user, obj):
-        return False
-
-    def user_can_set_status_obj(self, user, obj):
-        return user.can_set_status()
-
-
-class SubmissionURLHelper(AdminURLHelper):
-    def _get_action_url_pattern(self, action):
-        if action == "index":
-            return r"^%s/%s/$" % (self.opts.app_label, "submissions")
-        return r"^%s/%s/%s/$" % (self.opts.app_label, "submissions", action)
-
-    def _get_object_specific_action_url_pattern(self, action):
-        return r"^%s/%s/%s/(?P<instance_pk>[-\w]+)/$" % (
-            self.opts.app_label,
-            "submissions",
-            action,
-        )
-
-
-class SubmissionButtonHelper(ButtonHelper):
-    def set_status_button(
-        self,
-        pk,
-        status,
-        label,
-        title,
-        classnames_add=None,
-        classnames_exclude=None,
-    ):
-        if classnames_add is None:
-            classnames_add = []
-        if classnames_exclude is None:
-            classnames_exclude = []
-        classnames = self.finalise_classname(classnames_add, classnames_exclude)
-        url = self.url_helper.get_action_url("set_status", quote(pk))
-        url += "?status=" + status
-        return {
-            "url": url,
-            "label": label,
-            "classname": classnames,
-            "title": title,
-        }
-
-    def reviewed_button(self, pk, classnames_add=None, classnames_exclude=None):
-        if classnames_add is None:
-            classnames_add = []
-        return self.set_status_button(
-            pk,
-            self.model.REVIEWED,
-            _("mark as reviewed"),
-            _("Mark this submission as reviewed"),
-            classnames_add=classnames_add,
-            classnames_exclude=classnames_exclude,
-        )
-
-    def approve_button(self, pk, classnames_add=None, classnames_exclude=None):
-        if classnames_add is None:
-            classnames_add = []
-        if "button-secondary" in classnames_add:
-            classnames_add.remove("button-secondary")
-        classnames_add = ["yes"] + classnames_add
-        return self.set_status_button(
-            pk,
-            self.model.APPROVED,
-            _("approve"),
-            _("Approve this submission"),
-            classnames_add=classnames_add,
-            classnames_exclude=classnames_exclude,
-        )
-
-    def reject_button(self, pk, classnames_add=None, classnames_exclude=None):
-        if classnames_add is None:
-            classnames_add = []
-        if "button-secondary" in classnames_add:
-            classnames_add.remove("button-secondary")
-        classnames_add = ["no"] + classnames_add
-        return self.set_status_button(
-            pk,
-            self.model.REJECTED,
-            _("reject"),
-            _("Reject this submission"),
-            classnames_add=classnames_add,
-            classnames_exclude=classnames_exclude,
-        )
-
-    def get_buttons_for_obj(
-        self, obj, exclude=None, classnames_add=None, classnames_exclude=None
-    ):
-        buttons = super().get_buttons_for_obj(
-            obj,
-            exclude=exclude,
-            classnames_add=classnames_add,
-            classnames_exclude=classnames_exclude,
-        )
-        pk = getattr(obj, self.opts.pk.attname)
-        status_buttons = []
-        if obj.status != obj.REVIEWED:
-            status_buttons.append(
-                self.reviewed_button(
-                    pk,
-                    classnames_add=classnames_add,
-                    classnames_exclude=classnames_exclude,
-                )
-            )
-        if obj.status != obj.APPROVED:
-            status_buttons.append(
-                self.approve_button(
-                    pk,
-                    classnames_add=classnames_add,
-                    classnames_exclude=classnames_exclude,
-                )
-            )
-        if obj.status != obj.REJECTED:
-            status_buttons.append(
-                self.reject_button(
-                    pk,
-                    classnames_add=classnames_add,
-                    classnames_exclude=classnames_exclude,
-                )
-            )
-        return status_buttons + buttons
-
-
-class SetStatusView(InstanceSpecificView):
-    def check_action_permitted(self, user):
-        return self.permission_helper.user_can_set_status_obj(
-            user, self.instance
-        )
-
-    def get(self, request, *args, **kwargs):
-        status = request.GET.get("status")
-        if status in dict(self.model.STATUSES):
-            previous_status = self.instance.status
-            self.instance.status = status
-            self.instance.save()
-            verbose_label = self.instance.get_status_display()
-            if "revert" in request.GET:
-                messages.success(
-                    request, "Reverted to the “%s” status." % verbose_label
-                )
-            else:
-                revert_url = (
-                    self.url_helper.get_action_url(
-                        "set_status", self.instance_pk
-                    )
-                    + "?revert&status="
-                    + previous_status
-                )
-                messages.success(
-                    request,
-                    "Successfully changed the status to “%s”." % verbose_label,
-                    buttons=[messages.button(revert_url, _("Revert"))],
-                )
-        url = request.META.get("HTTP_REFERER")
-        if url is None:
-            url = (
-                self.url_helper.get_action_url("index")
-                + "?page_id=%s" % self.instance.page_id
-            )
-        return redirect(url)
-
-
-class SubmissionAdmin(ModelAdmin):
-    model = SessionFormSubmission
-    menu_icon = "form"
-    permission_helper_class = SubmissionPermissionHelper
-    url_helper_class = SubmissionURLHelper
-    button_helper_class = SubmissionButtonHelper
-    set_status_view_class = SetStatusView
-    list_display = ("status", "user", "submit_time", "last_modification")
-    list_filter = (SubmissionStatusFilter, "submit_time", "last_modification")
-    search_fields = ("user__first_name", "user__last_name")
-
-    def register_with_wagtail(self):
-        @hooks.register("register_permissions")
-        def register_permissions():
-            return self.get_permissions_for_registration()
-
-        @hooks.register("register_admin_urls")
-        def register_admin_urls():
-            return self.get_admin_urls_for_registration()
-
-    def get_queryset(self, request):
-        qs = super().get_queryset(request)
-        form_pages = get_forms_for_user(request.user)
-        return qs.filter(page__in=form_pages).exclude(
-            status=self.model.INCOMPLETE
-        )
-
-    def get_form_page(self, request):
-        form_pages = get_forms_for_user(request.user)
-        try:
-            return form_pages.get(pk=int(request.GET["page_id"])).specific
-        except (KeyError, TypeError, ValueError, Page.DoesNotExist):
-            pass
-
-    # TODO: Find a cleaner way to display data from dynamic fields.
-    def add_data_bridge(self, name, label):
-        def data_bridge(obj):
-            return obj.get_data().get(name)
-
-        data_bridge.short_description = label
-        setattr(self, name, data_bridge)
-
-    def get_list_display(self, request):
-        form_page = self.get_form_page(request)
-        if form_page is None:
-            return self.list_display
-        fields = []
-        for name, label in form_page.get_data_fields():
-            fields.append(name)
-            self.add_data_bridge(name, label)
-        return fields
-
-    def set_status_view(self, request, instance_pk):
-        kwargs = {"model_admin": self, "instance_pk": instance_pk}
-        view_class = self.set_status_view_class
-        return view_class.as_view(**kwargs)(request)
-
-    def get_admin_urls_for_registration(self):
-        urls = super().get_admin_urls_for_registration()
-        urls += (
-            path(
-                self.url_helper.get_action_url_pattern("set_status"),
-                self.set_status_view,
-                name=self.url_helper.get_action_url_name("set_status"),
-            ),
-        )
-        return urls
-
-
-# @hooks.register('construct_main_menu')
-# def hide_old_forms_module(request, menu_items):
-#     from wagtail.contrib.forms.wagtail_hooks import FormsMenuItem
-#     for menu_item in menu_items:
-#         if isinstance(menu_item, FormsMenuItem):
-#             menu_items.remove(menu_item)
-
-# modeladmin_register(FormAdmin)
-# modeladmin_register(SubmissionAdmin)

+ 5 - 11
coderedcms/wagtail_hooks.py

@@ -8,7 +8,8 @@ from django.utils.html import format_html
 from django.utils.translation import gettext_lazy as _
 from wagtail.admin.menu import MenuItem
 from wagtail import hooks
-from wagtail.models import UserPagePermissionsProxy, get_page_models
+from wagtail.models import get_page_models
+from wagtail.permission_policies.pages import PagePermissionPolicy
 from wagtailcache.cache import clear_cache
 
 from coderedcms import __version__
@@ -23,15 +24,6 @@ def global_admin_css():
     )
 
 
-@hooks.register("insert_editor_css")
-def editor_css():
-    return format_html(
-        '<link rel="stylesheet" type="text/css" href="{}?v={}">',
-        static("coderedcms/css/crx-editor.css"),
-        __version__,
-    )
-
-
 @hooks.register("insert_editor_js")
 def collapsible_js():
     return format_html(
@@ -104,7 +96,9 @@ def crx_forms(user, editable_forms):
     ]
     form_types = list(ContentType.objects.get_for_models(*form_models).values())
 
-    editable_forms = UserPagePermissionsProxy(user).editable_pages()
+    editable_forms = PagePermissionPolicy().instances_user_has_permission_for(
+        user, "change"
+    )
     editable_forms = editable_forms.filter(content_type__in=form_types)
 
     return editable_forms

+ 9 - 11
setup.py

@@ -29,33 +29,31 @@ setup(
         "Operating System :: OS Independent",
         "Programming Language :: Python",
         "Programming Language :: Python :: 3",
-        "Programming Language :: Python :: 3.7",
         "Programming Language :: Python :: 3.8",
         "Programming Language :: Python :: 3.9",
         "Programming Language :: Python :: 3.10",
         "Programming Language :: Python :: 3.11",
         "Programming Language :: Python :: 3 :: Only",
         "Framework :: Django",
-        "Framework :: Django :: 3.2",
-        "Framework :: Django :: 4.0",
         "Framework :: Django :: 4.1",
+        "Framework :: Django :: 4.2",
         "Framework :: Wagtail",
-        "Framework :: Wagtail :: 4",
+        "Framework :: Wagtail :: 5",
         "Topic :: Internet :: WWW/HTTP",
         "Topic :: Internet :: WWW/HTTP :: Dynamic Content",
         "Topic :: Internet :: WWW/HTTP :: Site Management",
     ],
-    python_requires=">=3.7",
+    python_requires=">=3.8",
     install_requires=[
         "beautifulsoup4>=4.8,<4.12",  # should be the same as wagtail
         "django-eventtools==1.0.*",
-        "django-bootstrap5==22.2",
-        "Django>=3.2,<4.2",  # should be the same as wagtail
+        "django-bootstrap5==23.3",
+        "Django>=4.1,<4.3",  # should be the same as wagtail
         "geocoder==1.38.*",
-        "icalendar==4.1.*",
-        "wagtail>=4.0,<4.3",
-        "wagtail-cache>=2.2,<3",
-        "wagtail-seo>=2.3,<3",
+        "icalendar==5.0.*",
+        "wagtail>=5.0,<6.0",
+        "wagtail-cache>=2.3,<3",
+        "wagtail-seo>=2.4,<3",
     ],
     entry_points={
         "console_scripts": ["coderedcms=coderedcms.bin.coderedcms:main"]

+ 0 - 1
tutorial/mysite/mysite/settings/base.py

@@ -46,7 +46,6 @@ INSTALLED_APPS = [
     "wagtail.search",
     "wagtail.core",
     "wagtail.contrib.settings",
-    "wagtail.contrib.modeladmin",
     "wagtail.contrib.table_block",
     "wagtail.admin",
     # Django

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff