From e7c327b17821b2c16e622c8ec66abd343f927af5 Mon Sep 17 00:00:00 2001 From: Ian Stride Date: Tue, 21 May 2024 18:25:18 +0100 Subject: [PATCH 01/11] Upgrade Wagtail to v4.0.4 --- requirements.dev.txt | 12 ++++++------ requirements.in | 4 ++-- requirements.txt | 12 ++++++------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/requirements.dev.txt b/requirements.dev.txt index c5a62c68a..aec6c5814 100644 --- a/requirements.dev.txt +++ b/requirements.dev.txt @@ -1021,9 +1021,9 @@ urllib3[socks]==1.26.19 \ # elasticsearch # requests # selenium -wagtail==3.0.3 \ - --hash=sha256:111ed9a0a6ff26d5d881d52deb4bf52b627d79a53c43829611752dbb68a9192f \ - --hash=sha256:23b3e541401355ea183372582050ea52b049c956dd5b506197f957bb68423ab3 +wagtail==4.0.4 \ + --hash=sha256:43b35e8e29bcc83a3ddf60f08e8c22fea1e819f338bcba73a4b290771233809a \ + --hash=sha256:8799c7550cc033c8e85aeeaf6f91b5343d7dee72fecd5d4093499af9d3aeaa1d # via # -r requirements.txt # wagtail-cache @@ -1031,9 +1031,9 @@ wagtail==3.0.3 \ # wagtail-localize # wagtail-markdown # wagtailmedia -wagtail-cache==2.1.1 \ - --hash=sha256:1fe3ca20a3cdf23f31fc9df662a52f743084cd7f60bd870950094d63a87f695a \ - --hash=sha256:2ac16921d022a58240a009a19220554e41963c5172e7dfe4ef741ddd887e991f +wagtail-cache==2.2.0 \ + --hash=sha256:a231bc3941eb3ce3e8c73b9aea5d9a54e2f580009a0c87bf86ac59a6545a9609 \ + --hash=sha256:e9e6a8391795d5faab877183c025451c82905ea94b7a0d5eb4aca29ea136499f # via -r requirements.txt wagtail-factories==4.0.0 \ --hash=sha256:3e39ec1cc13b61c6e467f1bf223ce2d134e823fa9fe4dc7e32d0222cc8d350ec \ diff --git a/requirements.in b/requirements.in index 92c7a8628..5ce9dba8b 100644 --- a/requirements.in +++ b/requirements.in @@ -21,10 +21,10 @@ psycopg2~=2.9.9 pyjwt~=2.8.0 redis~=3.0 tqdm~=4.66 -wagtail-cache~=2.1.1 +wagtail-cache~=2.2.0 wagtail-localize~=1.4.0 wagtail-markdown~=0.10.0 -wagtail~=3.0.3 +wagtail~=4.0.4 wagtailmedia~=0.12.0 wagtailmenus==3.1.3 wagtailsvg==0.0.37 diff --git a/requirements.txt b/requirements.txt index d7221cae1..f048e04ed 100644 --- a/requirements.txt +++ b/requirements.txt @@ -783,18 +783,18 @@ urllib3==1.26.19 \ # via # elasticsearch # requests -wagtail==3.0.3 \ - --hash=sha256:111ed9a0a6ff26d5d881d52deb4bf52b627d79a53c43829611752dbb68a9192f \ - --hash=sha256:23b3e541401355ea183372582050ea52b049c956dd5b506197f957bb68423ab3 +wagtail==4.0.4 \ + --hash=sha256:43b35e8e29bcc83a3ddf60f08e8c22fea1e819f338bcba73a4b290771233809a \ + --hash=sha256:8799c7550cc033c8e85aeeaf6f91b5343d7dee72fecd5d4093499af9d3aeaa1d # via # -r requirements.in # wagtail-cache # wagtail-localize # wagtail-markdown # wagtailmedia -wagtail-cache==2.1.1 \ - --hash=sha256:1fe3ca20a3cdf23f31fc9df662a52f743084cd7f60bd870950094d63a87f695a \ - --hash=sha256:2ac16921d022a58240a009a19220554e41963c5172e7dfe4ef741ddd887e991f +wagtail-cache==2.2.0 \ + --hash=sha256:a231bc3941eb3ce3e8c73b9aea5d9a54e2f580009a0c87bf86ac59a6545a9609 \ + --hash=sha256:e9e6a8391795d5faab877183c025451c82905ea94b7a0d5eb4aca29ea136499f # via -r requirements.in wagtail-generic-chooser==0.5.1 \ --hash=sha256:135f8cc413d83b82bb8956f625f4b9572aa7139ab3835e2ba2d7e19f2171b354 \ From f518da746bbcfba96760daf15423be2f16f7d3d6 Mon Sep 17 00:00:00 2001 From: Ian Stride Date: Tue, 21 May 2024 18:25:50 +0100 Subject: [PATCH 02/11] Stop using deprecated SubMenu --- home/wagtail_hooks.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/home/wagtail_hooks.py b/home/wagtail_hooks.py index 555ff711f..94bef83fa 100644 --- a/home/wagtail_hooks.py +++ b/home/wagtail_hooks.py @@ -10,8 +10,7 @@ from django.utils.translation import gettext_lazy as _ from wagtail import __version__ from wagtail.admin import widgets as wagtailadmin_widgets -from wagtail.admin.menu import MenuItem, SubmenuMenuItem -from wagtail.contrib.modeladmin.menus import SubMenu +from wagtail.admin.menu import Menu, MenuItem, SubmenuMenuItem from wagtail.contrib.modeladmin.options import ModelAdmin, modeladmin_register from wagtail import hooks from wagtail.models import Page, PageViewRestriction @@ -49,7 +48,10 @@ def update_menu_items(request, menu_items): if item.name == "forms": item.label = _("Form Data") if item.name == 'translations': - item.url = f'{TranslationEntryAdmin().url_helper.get_action_url("index")}?limited=yes' + item.url = ( + TranslationEntryAdmin().url_helper.get_action_url("index") + + "?limited=yes" + ) if item.name == 'community-comment-moderations': menu_items.remove(item) @@ -87,6 +89,7 @@ def global_admin_css(): static("css/global/admin.css"), ) + @hooks.register("insert_global_admin_css") def import_fontawesome_stylesheets(): return "\n".join( @@ -118,7 +121,7 @@ def page_listing_buttons(page, page_perms, is_parent=False, next_url=None): yield wagtailadmin_widgets.PageListingButton( _('Sort child pages'), '?ordering=ord', - attrs={'title': _("Change ordering of child pages of '%(title)s'") % {'title': page.get_admin_display_title()}}, + attrs={"title": _("Change ordering of child pages of '%(title)s'") % {'title': page.get_admin_display_title()}}, priority=60 ) @@ -138,7 +141,7 @@ def about(): return SubmenuMenuItem( label="About", - menu=SubMenu(items), + menu=Menu(items=items), icon_name="info-circle", order=999999, ) @@ -188,7 +191,11 @@ class TranslationEntryAdmin(ModelAdmin): menu_label = 'Translations' menu_icon = 'edit' list_display = ('original', 'language', 'translation',) - list_filter = ('language', LimitedTranslatableStringsFilter, MissingTranslationsFilter) + list_filter = ( + 'language', + LimitedTranslatableStringsFilter, + MissingTranslationsFilter + ) search_fields = ('original', 'translation',) index_template_name = 'modeladmin/translation_manager/translationentry/index.html' menu_order = 601 From afe02fbf2ec27fe3f743b226be84cd3a6f51f892 Mon Sep 17 00:00:00 2001 From: Ian Stride Date: Wed, 22 May 2024 18:49:43 +0100 Subject: [PATCH 03/11] Create chatbot channel chooser --- messaging/blocks.py | 14 +++++++------- messaging/views.py | 17 +++++++++++++++-- messaging/wagtail_hooks.py | 15 +++++++++++++-- 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/messaging/blocks.py b/messaging/blocks.py index 550370b6a..5ad8159c5 100644 --- a/messaging/blocks.py +++ b/messaging/blocks.py @@ -1,12 +1,12 @@ -from django import forms from wagtail import blocks -from .models import ChatbotChannel +from messaging.views import chatbot_channel_viewset -class ChatBotChannelChooserBlock(blocks.ChooserBlock): - target_model = ChatbotChannel - widget = forms.Select +ChatBotChannelChooserBlock = chatbot_channel_viewset.get_block_class( + name="ChatBotChannelChooserBlock", + module_path="messaging.blocks", +) class ChatBotButtonBlock(blocks.StructBlock): @@ -16,5 +16,5 @@ class ChatBotButtonBlock(blocks.StructBlock): channel = ChatBotChannelChooserBlock() class Meta: - icon = 'code' - template = 'messaging/blocks/chatbot_button.html' + icon = "mail" + template = "messaging/blocks/chatbot_button.html" diff --git a/messaging/views.py b/messaging/views.py index 3604c803d..fbad9937c 100644 --- a/messaging/views.py +++ b/messaging/views.py @@ -1,17 +1,18 @@ from django.contrib import messages from django.contrib.auth import get_user_model +from django.contrib.auth.decorators import login_required from django.http import HttpResponseRedirect from django.shortcuts import render, redirect, get_object_or_404 from django.urls import reverse from django.utils.decorators import method_decorator from django.views import View -from django.views.generic import (DeleteView, TemplateView, ) +from django.views.generic import DeleteView, TemplateView +from wagtail.admin.viewsets.chooser import ChooserViewSet from .chat import ChatManager from .forms import MessageReplyForm, NewMessageForm from .models import Thread, UserThread -from django.contrib.auth.decorators import login_required User = get_user_model() @@ -103,3 +104,15 @@ class ThreadDeleteView(DeleteView): def delete(self, request, *args, **kwargs): self.get_object().user_threads.filter(user=request.user).update(is_active=False) return HttpResponseRedirect(reverse("messaging:inbox")) + + +class ChatbotChannelChooserViewSet(ChooserViewSet): + model = "messaging.ChatbotChannel" + icon = "code" + choose_one_text = "Choose a channel" + choose_another_text = "Choose another channel" + edit_item_text = "Edit this channel" + form_fields = ["display_name", "request_url"] + + +chatbot_channel_viewset = ChatbotChannelChooserViewSet("chatbot_channel_chooser") diff --git a/messaging/wagtail_hooks.py b/messaging/wagtail_hooks.py index d50e6536c..93bc83c11 100644 --- a/messaging/wagtail_hooks.py +++ b/messaging/wagtail_hooks.py @@ -1,6 +1,17 @@ +from wagtail import hooks from wagtail.contrib.modeladmin.options import ( - ModelAdminGroup, ModelAdmin, modeladmin_register) -from .models import ChatbotChannel + ModelAdminGroup, + ModelAdmin, + modeladmin_register, +) + +from messaging.models import ChatbotChannel +from messaging.views import chatbot_channel_viewset + + +@hooks.register("register_admin_viewset") +def register_chatbot_channel_viewset(): + return chatbot_channel_viewset class ChatbotChannelAdmin(ModelAdmin): From 7e70d460840eec73289f93d3052265f2184e0e44 Mon Sep 17 00:00:00 2001 From: Ian Stride Date: Thu, 23 May 2024 14:56:28 +0100 Subject: [PATCH 04/11] Move chatbot-specific methods from user model to messaging app --- iogt_users/models.py | 44 ++++++++----------- messaging/chat.py | 5 ++- ...et_rapidpro_authentication_header_value.py | 8 ++-- .../messaging/chatbotchannel/index.html | 37 +--------------- .../messaging/tags/chatbot_auth_tokens.html | 30 ++++++++----- .../templates/messaging/thread_detail.html | 4 +- messaging/templatetags/messaging_tags.py | 27 +++++++----- messaging/utils.py | 22 ++++++++++ 8 files changed, 85 insertions(+), 92 deletions(-) create mode 100644 messaging/utils.py diff --git a/iogt_users/models.py b/iogt_users/models.py index 801eee996..4c10639d1 100644 --- a/iogt_users/models.py +++ b/iogt_users/models.py @@ -1,42 +1,34 @@ -import base64 - -from django.conf import settings from django.contrib.auth import get_user_model from django.contrib.auth.models import AbstractUser from django.db import models from django.db.models.signals import post_save from django.dispatch import receiver -from rest_framework_simplejwt.tokens import RefreshToken class User(AbstractUser): - first_name = models.CharField('first name', max_length=150, null=True, - blank=True) - last_name = models.CharField('last name', max_length=150, null=True, - blank=True) - display_name = models.CharField('display name', max_length=255, null=True, blank=True) + first_name = models.CharField( + "first name", + max_length=150, + null=True, + blank=True, + ) + last_name = models.CharField( + "last name", + max_length=150, + null=True, + blank=True, + ) + display_name = models.CharField( + "display name", + max_length=255, + null=True, + blank=True, + ) email = models.EmailField('email address', null=True, blank=True) terms_accepted = models.BooleanField(default=False) - has_filled_registration_survey = models.BooleanField(default=False) has_viewed_registration_survey = models.BooleanField(default=False) - interactive_uuid = models.CharField(max_length=255, null=True, blank=True) - - @property - def is_rapidpro_bot_user(self): - return self.groups.filter(name=settings.RAPIDPRO_BOT_GROUP_NAME).exists() - - @classmethod - def get_rapidpro_bot_auth_tokens(cls): - users = cls.objects.filter(groups__name=settings.RAPIDPRO_BOT_GROUP_NAME) - - tokens = {} - for user in users: - tokens[user.username] = f'Bearer {RefreshToken.for_user(user).access_token}' - - return tokens - read_articles = models.ManyToManyField(to='home.Article') @classmethod diff --git a/messaging/chat.py b/messaging/chat.py index 9ed0d1c0f..ac9c2d656 100644 --- a/messaging/chat.py +++ b/messaging/chat.py @@ -6,8 +6,9 @@ from django.utils import timezone from webpush import send_user_notification -from .models import Message, Thread, UserThread +from messaging.models import Message, Thread, UserThread from messaging.rapidpro_client import RapidProClient +from messaging.utils import is_chatbot User = get_user_model() logger = logging.getLogger(__name__) @@ -81,7 +82,7 @@ def _parse_rapidpro_message(message_text): def record_reply(self, text, sender, rapidpro_message_id=None, quick_replies=None, mark_unread=True): if quick_replies is None: quick_replies = [] - if not sender.is_rapidpro_bot_user: + if not is_chatbot(sender): client = RapidProClient(self.thread) client.send_reply(text) diff --git a/messaging/management/commands/get_rapidpro_authentication_header_value.py b/messaging/management/commands/get_rapidpro_authentication_header_value.py index a5b2692fa..bb48339ff 100644 --- a/messaging/management/commands/get_rapidpro_authentication_header_value.py +++ b/messaging/management/commands/get_rapidpro_authentication_header_value.py @@ -1,15 +1,15 @@ from django.contrib.auth import get_user_model from django.core.management.base import BaseCommand -User = get_user_model() +from messaging.utils import get_auth_tokens class Command(BaseCommand): """ - This command prints the Authorization Header Value for RapidPro + Print the authorization header for each chatbot user """ - def handle(self, *args, **options): - tokens = User.get_rapidpro_bot_auth_tokens() + tokens = get_auth_tokens() + for username, token in tokens.items(): self.stdout.write(self.style.SUCCESS(f'{username}: {token}')) diff --git a/messaging/templates/messaging/chatbotchannel/index.html b/messaging/templates/messaging/chatbotchannel/index.html index 1090ee08f..d8d9fca5d 100644 --- a/messaging/templates/messaging/chatbotchannel/index.html +++ b/messaging/templates/messaging/chatbotchannel/index.html @@ -1,39 +1,6 @@ {% extends "modeladmin/index.html" %} {% load messaging_tags %} -{% load i18n modeladmin_tags wagtailadmin_tags %} -{% block header %} -
-
-
-
- {% block h1 %} -

- {{ view.get_page_title }} -

- {% endblock %} -
- {% block search %}{% search_form %}{% endblock %} -
- {% block header_extra %} -
- {% if user_can_create %} -
- {% include 'modeladmin/includes/button.html' with button=view.button_helper.add_button %} -
- {% endif %} - {% if view.list_export %} - - {% endif %} -
- {% endblock %} -
- {% render_chatbot_auth_tokens %} -
+{% block extra_rows %} +{% chatbot_auth_tokens %} {% endblock %} diff --git a/messaging/templates/messaging/tags/chatbot_auth_tokens.html b/messaging/templates/messaging/tags/chatbot_auth_tokens.html index 1847c9c40..4aa4cb8db 100644 --- a/messaging/templates/messaging/tags/chatbot_auth_tokens.html +++ b/messaging/templates/messaging/tags/chatbot_auth_tokens.html @@ -1,18 +1,24 @@ -Chatbot Authentication Headers -

- {% for username, token in tokens.items %} -

- {{ username }}: - - +
+
+
+

Authentication headers

+
+ {% for username, token in tokens.items %} +
{{ username }}
+
+ {{ token }} + +
+ {% endfor %} +
+
- {% endfor %} -

+
\ No newline at end of file + diff --git a/messaging/templates/messaging/thread_detail.html b/messaging/templates/messaging/thread_detail.html index 439d333ff..751a04a0d 100644 --- a/messaging/templates/messaging/thread_detail.html +++ b/messaging/templates/messaging/thread_detail.html @@ -10,7 +10,7 @@

{{ thread.subject }}

{% for message in thread_messages %} - {% if message.sender.is_rapidpro_bot_user %} + {% if message.sender|is_chatbot %}
{% else %}
@@ -55,4 +55,4 @@

{{ thread.subject }}

-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/messaging/templatetags/messaging_tags.py b/messaging/templatetags/messaging_tags.py index fba022be4..583e7d7d7 100644 --- a/messaging/templatetags/messaging_tags.py +++ b/messaging/templatetags/messaging_tags.py @@ -1,8 +1,10 @@ from django import template -from django.contrib.auth import get_user_model + +import messaging.utils as utils + register = template.Library() -User = get_user_model() + @register.filter def unread(thread, user): @@ -12,17 +14,20 @@ def unread(thread, user): return thread.user_threads.filter(user=user, is_read=False).exists() -@register.inclusion_tag('messaging/tags/quick_reply_form.html') +@register.inclusion_tag("messaging/tags/quick_reply_form.html") def render_quick_reply_form(thread, user, text): return { - 'thread': thread, - 'user': user, - 'text': text, + "thread": thread, + "user": user, + "text": text, } -@register.inclusion_tag('messaging/tags/chatbot_auth_tokens.html') -def render_chatbot_auth_tokens(): - return { - 'tokens': User.get_rapidpro_bot_auth_tokens(), - } +@register.inclusion_tag("messaging/tags/chatbot_auth_tokens.html") +def chatbot_auth_tokens(): + return {"tokens": utils.get_auth_tokens()} + + +@register.filter +def is_chatbot(user): + return utils.is_chatbot(user) diff --git a/messaging/utils.py b/messaging/utils.py new file mode 100644 index 000000000..7b5f22eac --- /dev/null +++ b/messaging/utils.py @@ -0,0 +1,22 @@ +from django.conf import settings +from django.contrib.auth import get_user_model +from rest_framework_simplejwt.tokens import RefreshToken + + +User = get_user_model() + + +def get_auth_tokens(): + users = User.objects.filter(groups__name=settings.RAPIDPRO_BOT_GROUP_NAME) + + return { + user.username: f"Bearer {RefreshToken.for_user(user).access_token}" + for user in users + } + + +def is_chatbot(user) -> bool: + try: + return user.groups.filter(name=settings.RAPIDPRO_BOT_GROUP_NAME).exists() + except Exception: + return False From baa74782c9f77a585e9d98caed13ce5abc66c4ea Mon Sep 17 00:00:00 2001 From: Ian Stride Date: Thu, 23 May 2024 15:47:42 +0100 Subject: [PATCH 05/11] Set use_json_field on StreamFields --- home/models.py | 26 +++++++++++++++++++------- questionnaires/models.py | 2 +- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/home/models.py b/home/models.py index b3cd0c413..9de93dbb8 100644 --- a/home/models.py +++ b/home/models.py @@ -686,16 +686,28 @@ class SiteSettings(BaseSetting): blank=True, use_json_field=True, ) - social_media_content_sharing_button = StreamField([ - ('social_media_content_sharing_button', SocialMediaShareButtonBlock()), - ], null=True, blank=True) + social_media_content_sharing_button = StreamField( + [ + ("social_media_content_sharing_button", SocialMediaShareButtonBlock()), + ], + null=True, + blank=True, + use_json_field=True, + ) media_file_size_threshold = models.IntegerField( default=9437184, - help_text=_('Show warning if uploaded media file size is greater than this in bytes. Default is 9 MB')) + help_text=_( + "Show warning if uploaded media file size is greater than this in bytes." + " Default is 9 MB (9,437,184 bytes)." + ) + ) allow_anonymous_comment = models.BooleanField(default=False) - registration_survey = models.ForeignKey('questionnaires.Survey', null=True, - blank=True, - on_delete=models.SET_NULL) + registration_survey = models.ForeignKey( + "questionnaires.Survey", + null=True, + blank=True, + on_delete=models.SET_NULL, + ) # Obsolete - Web Light service discontinued Dec 2022 opt_in_to_google_web_light = models.BooleanField(default=False) diff --git a/questionnaires/models.py b/questionnaires/models.py index 0035aefbd..d5b8134a3 100644 --- a/questionnaires/models.py +++ b/questionnaires/models.py @@ -322,7 +322,7 @@ class SurveyFormField(AbstractFormField): help_text=_('Column header used during CSV export of survey ' 'responses.'), ) - skip_logic = SkipLogicField(null=True, blank=True) + skip_logic = SkipLogicField(null=True, blank=True, use_json_field=True) default_value = models.TextField( verbose_name=_('default value'), blank=True, From 77ff050fc82f2d8bc5b994f26212e3be5d995e52 Mon Sep 17 00:00:00 2001 From: Ian Stride Date: Thu, 23 May 2024 17:07:10 +0100 Subject: [PATCH 06/11] Replace RichText field features with default editor config --- home/blocks.py | 32 +++++++++++++++++++++----------- home/models.py | 10 ++++++++-- iogt/settings/base.py | 29 ++++++++++++++++++++--------- questionnaires/models.py | 6 +++--- 4 files changed, 52 insertions(+), 25 deletions(-) diff --git a/home/blocks.py b/home/blocks.py index f992ccb43..e7551d1b0 100644 --- a/home/blocks.py +++ b/home/blocks.py @@ -162,20 +162,30 @@ def render_basic(self, value, context=None): class DownloadButtonBlock(blocks.StructBlock): available_text = blocks.CharBlock( - help_text=_('This text appears when it is possible for the user to install the app on their phone.')) + help_text=_( + "This text appears when it is possible for the user to install the app on" + " their phone" + ) + ) unavailable_text = blocks.CharBlock( required=False, help_text=_( - 'This text appears when the user is using a feature phone and thus cannot install the app ' - '(the button will be disabled in this case). [Currently not implemented]'), - form_classname='red-help-text') + "This text appears when the user is using a feature phone and thus cannot" + " install the app (the button will be disabled in this case)." + " [Currently not implemented]" + ), + form_classname="red-help-text", + ) offline_text = blocks.CharBlock( - required=False, help_text=_( - 'This text appears when the user is navigating the site via the offline app and ' - 'thus it doesn\'t make sense to install the offline app again ' - '(the button will be disabled in this case).')) - page = PageChooserBlock(target_model='wagtailcore.Page') - description = blocks.RichTextBlock(features=settings.WAGTAIL_RICH_TEXT_FIELD_FEATURES) + required=False, + help_text=_( + "This text appears when the user is navigating the site via the offline app" + " and thus it does not make sense to install the offline app again (the" + " button will be disabled in this case)." + ), + ) + page = PageChooserBlock(target_model="wagtailcore.Page") + description = blocks.RichTextBlock() class Meta: - template = 'blocks/download_button.html' + template = "blocks/download_button.html" diff --git a/home/models.py b/home/models.py index 9de93dbb8..c5fb9f930 100644 --- a/home/models.py +++ b/home/models.py @@ -323,8 +323,14 @@ class AbstractArticle(Page, PageUtilsMixin, CommentableMixin, TitleIconMixin): body = StreamField( [ - ('heading', blocks.CharBlock(form_classname="full title", template='blocks/heading.html')), - ('paragraph', blocks.RichTextBlock(features=settings.WAGTAIL_RICH_TEXT_FIELD_FEATURES)), + ( + 'heading', + blocks.CharBlock( + form_classname="full title", + template="blocks/heading.html", + ), + ), + ('paragraph', blocks.RichTextBlock()), ('markdown', MarkdownBlock(icon='code')), ('paragraph_v1_legacy', RawHTMLBlock(icon='code')), ('image', ImageChooserBlock(template='blocks/image.html')), diff --git a/iogt/settings/base.py b/iogt/settings/base.py index b6d91eda1..e0fc4d9c3 100644 --- a/iogt/settings/base.py +++ b/iogt/settings/base.py @@ -410,15 +410,26 @@ WAGTAILMENUS_FLAT_MENU_ITEMS_RELATED_NAME = 'iogt_flat_menu_items' -WAGTAIL_RICH_TEXT_FIELD_FEATURES = [ - 'h2', 'h3', 'h4', - 'bold', 'italic', - 'ol', 'ul', - 'hr', - 'link', - 'document-link', - 'image', -] +WAGTAILADMIN_RICH_TEXT_EDITORS = { + "default": { + "WIDGET": "wagtail.admin.rich_text.DraftailRichTextArea", + "OPTIONS": { + "features": [ + "h2", + "h3", + "h4", + "bold", + "italic", + "ol", + "ul", + "hr", + "link", + "document-link", + "image", + ], + } + }, +} # Search results SEARCH_RESULTS_PER_PAGE = 10 diff --git a/questionnaires/models.py b/questionnaires/models.py index d5b8134a3..b69e80cc9 100644 --- a/questionnaires/models.py +++ b/questionnaires/models.py @@ -64,7 +64,7 @@ class QuestionnairePage(Page, PageUtilsMixin, TitleIconMixin): description = StreamField( [ ('heading', blocks.CharBlock(form_classname="full title", template='blocks/heading.html')), - ('paragraph', blocks.RichTextBlock(features=settings.WAGTAIL_RICH_TEXT_FIELD_FEATURES)), + ('paragraph', blocks.RichTextBlock()), ('paragraph_v1_legacy', RawHTMLBlock(icon='code')), ("image", ImageChooserBlock(template='blocks/image.html')), ('list', MarkdownBlock(icon='code')), @@ -77,7 +77,7 @@ class QuestionnairePage(Page, PageUtilsMixin, TitleIconMixin): ) thank_you_text = StreamField( [ - ("paragraph", blocks.RichTextBlock(features=settings.WAGTAIL_RICH_TEXT_FIELD_FEATURES)), + ("paragraph", blocks.RichTextBlock()), ("media", MediaBlock(icon="media")), ("image", ImageChooserBlock(template='blocks/image.html')), ], @@ -107,7 +107,7 @@ class QuestionnairePage(Page, PageUtilsMixin, TitleIconMixin): terms_and_conditions = StreamField( [ - ("paragraph", blocks.RichTextBlock(features=settings.WAGTAIL_RICH_TEXT_FIELD_FEATURES)), + ("paragraph", blocks.RichTextBlock()), ('page_button', PageButtonBlock()), ], null=True, From f417a678eb9051af1d9f57f0891132bbb58e58e5 Mon Sep 17 00:00:00 2001 From: Ian Stride Date: Thu, 23 May 2024 18:36:52 +0100 Subject: [PATCH 07/11] Set block icons consistently across page types --- home/blocks.py | 24 +++++++++++++++---- home/models.py | 51 +++++++++++++++++++++------------------- questionnaires/models.py | 19 ++++++++------- 3 files changed, 57 insertions(+), 37 deletions(-) diff --git a/home/blocks.py b/home/blocks.py index e7551d1b0..863c870d4 100644 --- a/home/blocks.py +++ b/home/blocks.py @@ -1,5 +1,3 @@ -from django.conf import settings -from django.forms.utils import flatatt from django.utils.html import format_html, format_html_join from django.utils.safestring import mark_safe from django.utils.translation import gettext as _ @@ -23,7 +21,8 @@ def get_context(self, value, parent_context=None): return context class Meta: - template = 'blocks/media.html' + icon = "media" + template = "blocks/media.html" class SocialMediaLinkBlock(blocks.StructBlock): @@ -52,6 +51,9 @@ class Meta: class EmbeddedQuestionnaireBlock(blocks.StructBlock): direct_display = blocks.BooleanBlock(required=False) + class Meta: + icon = "form" + class EmbeddedPollBlock(EmbeddedQuestionnaireBlock): poll = EmbeddedQuestionnaireChooserBlock(target_model='questionnaires.Poll') @@ -119,6 +121,7 @@ def get_context(self, value, parent_context=None): return context class Meta: + icon = "link" template = 'blocks/page_button.html' @@ -137,7 +140,8 @@ def get_context(self, value, parent_context=None): return context class Meta: - template = 'blocks/article.html' + icon = "pick" + template = "blocks/article.html" class NumberedListBlock(blocks.ListBlock): @@ -152,6 +156,9 @@ def render_basic(self, value, context=None): ) return format_html("
    {0}
", children) + class Meta: + icon = "list-ol" + class RawHTMLBlock(blocks.RawHTMLBlock): def render_basic(self, value, context=None): @@ -188,4 +195,13 @@ class DownloadButtonBlock(blocks.StructBlock): description = blocks.RichTextBlock() class Meta: + icon = "download" template = "blocks/download_button.html" + + +def heading_block(): + return blocks.CharBlock( + icon="h1", + form_classname="full title", + template="blocks/heading.html", + ) diff --git a/home/models.py b/home/models.py index c5fb9f930..6efa91466 100644 --- a/home/models.py +++ b/home/models.py @@ -14,7 +14,6 @@ from django.utils.encoding import force_str from django.utils.translation import gettext_lazy as _ from modelcluster.contrib.taggit import ClusterTaggableManager -from iogt.settings.base import WAGTAIL_CONTENT_LANGUAGES from modelcluster.fields import ParentalKey from rest_framework import status from taggit.models import TaggedItemBase @@ -42,8 +41,18 @@ from messaging.blocks import ChatBotButtonBlock from comments.models import CommentableMixin from home.blocks import ( - MediaBlock, SocialMediaLinkBlock, SocialMediaShareButtonBlock, EmbeddedPollBlock, EmbeddedSurveyBlock, - EmbeddedQuizBlock, PageButtonBlock, NumberedListBlock, RawHTMLBlock, ArticleBlock, DownloadButtonBlock, + ArticleBlock, + DownloadButtonBlock, + EmbeddedPollBlock, + EmbeddedSurveyBlock, + EmbeddedQuizBlock, + heading_block, + MediaBlock, + PageButtonBlock, + NumberedListBlock, + RawHTMLBlock, + SocialMediaLinkBlock, + SocialMediaShareButtonBlock, ) from .forms import SectionPageForm from .mixins import PageUtilsMixin, TitleIconMixin @@ -323,26 +332,20 @@ class AbstractArticle(Page, PageUtilsMixin, CommentableMixin, TitleIconMixin): body = StreamField( [ - ( - 'heading', - blocks.CharBlock( - form_classname="full title", - template="blocks/heading.html", - ), - ), - ('paragraph', blocks.RichTextBlock()), - ('markdown', MarkdownBlock(icon='code')), - ('paragraph_v1_legacy', RawHTMLBlock(icon='code')), - ('image', ImageChooserBlock(template='blocks/image.html')), - ('list', blocks.ListBlock(MarkdownBlock(icon='code'))), - ('numbered_list', NumberedListBlock(MarkdownBlock(icon='code'))), - ('page_button', PageButtonBlock()), - ('embedded_poll', EmbeddedPollBlock()), - ('embedded_survey', EmbeddedSurveyBlock()), - ('embedded_quiz', EmbeddedQuizBlock()), - ('media', MediaBlock(icon='media')), - ('chat_bot', ChatBotButtonBlock()), - ('download', DownloadButtonBlock()), + ("heading", heading_block()), + ("paragraph", blocks.RichTextBlock()), + ("markdown", MarkdownBlock()), + ("paragraph_v1_legacy", RawHTMLBlock(icon='code')), + ("image", ImageChooserBlock(template='blocks/image.html')), + ("list", blocks.ListBlock(MarkdownBlock(), icon="list-ul")), + ("numbered_list", NumberedListBlock(MarkdownBlock())), + ("page_button", PageButtonBlock()), + ("embedded_poll", EmbeddedPollBlock()), + ("embedded_survey", EmbeddedSurveyBlock()), + ("embedded_quiz", EmbeddedQuizBlock()), + ("media", MediaBlock()), + ("chat_bot", ChatBotButtonBlock()), + ("download", DownloadButtonBlock()), ], use_json_field=True, ) @@ -977,7 +980,7 @@ class ManifestSettings(models.Model): ) language = models.CharField( max_length=3, - choices=WAGTAIL_CONTENT_LANGUAGES, + choices=settings.WAGTAIL_CONTENT_LANGUAGES, default="en", verbose_name=_("Language"), help_text=_("Choose language"), diff --git a/questionnaires/models.py b/questionnaires/models.py index b69e80cc9..eb3a2394f 100644 --- a/questionnaires/models.py +++ b/questionnaires/models.py @@ -14,6 +14,7 @@ from wagtailsvg.models import Svg from home.blocks import ( + heading_block, MediaBlock, NumberedListBlock, PageButtonBlock, @@ -63,13 +64,13 @@ class QuestionnairePage(Page, PageUtilsMixin, TitleIconMixin): description = StreamField( [ - ('heading', blocks.CharBlock(form_classname="full title", template='blocks/heading.html')), - ('paragraph', blocks.RichTextBlock()), - ('paragraph_v1_legacy', RawHTMLBlock(icon='code')), - ("image", ImageChooserBlock(template='blocks/image.html')), - ('list', MarkdownBlock(icon='code')), - ('numbered_list', NumberedListBlock(MarkdownBlock(icon='code'))), - ('page_button', PageButtonBlock()), + ("heading", heading_block()), + ("paragraph", blocks.RichTextBlock()), + ("paragraph_v1_legacy", RawHTMLBlock(icon='code')), + ("image", ImageChooserBlock(template="blocks/image.html")), + ("list", MarkdownBlock()), + ("numbered_list", NumberedListBlock(MarkdownBlock())), + ("page_button", PageButtonBlock()), ], null=True, blank=True, @@ -78,8 +79,8 @@ class QuestionnairePage(Page, PageUtilsMixin, TitleIconMixin): thank_you_text = StreamField( [ ("paragraph", blocks.RichTextBlock()), - ("media", MediaBlock(icon="media")), - ("image", ImageChooserBlock(template='blocks/image.html')), + ("media", MediaBlock()), + ("image", ImageChooserBlock(template="blocks/image.html")), ], null=True, blank=True, From 50dc9496d92d57bceb46e6ecdf49114054623465 Mon Sep 17 00:00:00 2001 From: Ian Stride Date: Mon, 3 Jun 2024 11:17:13 +0100 Subject: [PATCH 08/11] Inherit from BaseSiteSetting instead of BaseSetting --- home/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/home/models.py b/home/models.py index 6efa91466..1eee28153 100644 --- a/home/models.py +++ b/home/models.py @@ -24,7 +24,7 @@ ObjectList, TabbedInterface, ) -from wagtail.contrib.settings.models import BaseSetting +from wagtail.contrib.settings.models import BaseSiteSetting from wagtail.contrib.settings.registry import register_setting from wagtail import blocks from wagtail.fields import StreamField @@ -608,7 +608,7 @@ def get_url(self, request=None, current_site=None): @register_setting -class SiteSettings(BaseSetting): +class SiteSettings(BaseSiteSetting): logo = models.ForeignKey( 'wagtailimages.Image', null=True, @@ -1063,7 +1063,7 @@ class Meta: @register_setting -class ThemeSettings(BaseSetting): +class ThemeSettings(BaseSiteSetting): global_background_color = models.CharField( null=True, blank=True, help_text='The background color of the website', max_length=8, default='#FFFFFF') From e525f652b049464b027ded2f9e94da38252d0da6 Mon Sep 17 00:00:00 2001 From: Ian Stride Date: Mon, 3 Jun 2024 15:05:01 +0100 Subject: [PATCH 09/11] Create database migrations for upgrade to Wagtail v4.0.4 --- home/migrations/0056_auto_20240603_1005.py | 536 ++++++++++++++++++ .../migrations/0031_auto_20240603_1005.py | 337 +++++++++++ 2 files changed, 873 insertions(+) create mode 100644 home/migrations/0056_auto_20240603_1005.py create mode 100644 questionnaires/migrations/0031_auto_20240603_1005.py diff --git a/home/migrations/0056_auto_20240603_1005.py b/home/migrations/0056_auto_20240603_1005.py new file mode 100644 index 000000000..1e858df5a --- /dev/null +++ b/home/migrations/0056_auto_20240603_1005.py @@ -0,0 +1,536 @@ +# Generated by Django 3.2.25 on 2024-06-03 10:05 + +from django.db import migrations, models +import home.blocks +import messaging.blocks +import wagtail.blocks +import wagtail.fields +import wagtail.images.blocks +import wagtailmarkdown.blocks + + +class Migration(migrations.Migration): + + dependencies = [ + ("home", "0055_enable_use_json_field"), + ] + + operations = [ + migrations.AlterField( + model_name="article", + name="body", + field=wagtail.fields.StreamField( + [ + ( + "heading", + wagtail.blocks.CharBlock( + form_classname="full title", + icon="h1", + template="blocks/heading.html", + ), + ), + ("paragraph", wagtail.blocks.RichTextBlock()), + ("markdown", wagtailmarkdown.blocks.MarkdownBlock()), + ("paragraph_v1_legacy", home.blocks.RawHTMLBlock(icon="code")), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + template="blocks/image.html" + ), + ), + ( + "list", + wagtail.blocks.ListBlock( + wagtailmarkdown.blocks.MarkdownBlock(), icon="list-ul" + ), + ), + ( + "numbered_list", + home.blocks.NumberedListBlock( + wagtailmarkdown.blocks.MarkdownBlock() + ), + ), + ( + "page_button", + wagtail.blocks.StructBlock( + [ + ("page", wagtail.blocks.PageChooserBlock()), + ( + "text", + wagtail.blocks.CharBlock( + max_length=255, required=False + ), + ), + ] + ), + ), + ( + "embedded_poll", + wagtail.blocks.StructBlock( + [ + ( + "direct_display", + wagtail.blocks.BooleanBlock(required=False), + ), + ( + "poll", + home.blocks.EmbeddedQuestionnaireChooserBlock( + page_type=["questionnaires.Poll"] + ), + ), + ] + ), + ), + ( + "embedded_survey", + wagtail.blocks.StructBlock( + [ + ( + "direct_display", + wagtail.blocks.BooleanBlock(required=False), + ), + ( + "survey", + home.blocks.EmbeddedQuestionnaireChooserBlock( + page_type=["questionnaires.Survey"] + ), + ), + ] + ), + ), + ( + "embedded_quiz", + wagtail.blocks.StructBlock( + [ + ( + "direct_display", + wagtail.blocks.BooleanBlock(required=False), + ), + ( + "quiz", + home.blocks.EmbeddedQuestionnaireChooserBlock( + page_type=["questionnaires.Quiz"] + ), + ), + ] + ), + ), + ("media", home.blocks.MediaBlock()), + ( + "chat_bot", + wagtail.blocks.StructBlock( + [ + ("subject", wagtail.blocks.CharBlock()), + ("button_text", wagtail.blocks.CharBlock()), + ("trigger_string", wagtail.blocks.CharBlock()), + ( + "channel", + messaging.blocks.ChatBotChannelChooserBlock(), + ), + ] + ), + ), + ( + "download", + wagtail.blocks.StructBlock( + [ + ( + "available_text", + wagtail.blocks.CharBlock( + help_text="This text appears when it is possible for the user to install the app on their phone" + ), + ), + ( + "unavailable_text", + wagtail.blocks.CharBlock( + form_classname="red-help-text", + help_text="This text appears when the user is using a feature phone and thus cannot install the app (the button will be disabled in this case). [Currently not implemented]", + required=False, + ), + ), + ( + "offline_text", + wagtail.blocks.CharBlock( + help_text="This text appears when the user is navigating the site via the offline app and thus it does not make sense to install the offline app again (the button will be disabled in this case).", + required=False, + ), + ), + ( + "page", + wagtail.blocks.PageChooserBlock( + page_type=["wagtailcore.Page"] + ), + ), + ("description", wagtail.blocks.RichTextBlock()), + ] + ), + ), + ], + use_json_field=True, + ), + ), + migrations.AlterField( + model_name="homepage", + name="home_featured_content", + field=wagtail.fields.StreamField( + [ + ( + "page_button", + wagtail.blocks.StructBlock( + [ + ("page", wagtail.blocks.PageChooserBlock()), + ( + "text", + wagtail.blocks.CharBlock( + max_length=255, required=False + ), + ), + ] + ), + ), + ( + "embedded_poll", + wagtail.blocks.StructBlock( + [ + ( + "direct_display", + wagtail.blocks.BooleanBlock(required=False), + ), + ( + "poll", + home.blocks.EmbeddedQuestionnaireChooserBlock( + page_type=["questionnaires.Poll"] + ), + ), + ] + ), + ), + ( + "embedded_survey", + wagtail.blocks.StructBlock( + [ + ( + "direct_display", + wagtail.blocks.BooleanBlock(required=False), + ), + ( + "survey", + home.blocks.EmbeddedQuestionnaireChooserBlock( + page_type=["questionnaires.Survey"] + ), + ), + ] + ), + ), + ( + "embedded_quiz", + wagtail.blocks.StructBlock( + [ + ( + "direct_display", + wagtail.blocks.BooleanBlock(required=False), + ), + ( + "quiz", + home.blocks.EmbeddedQuestionnaireChooserBlock( + page_type=["questionnaires.Quiz"] + ), + ), + ] + ), + ), + ( + "article", + wagtail.blocks.StructBlock( + [ + ( + "display_section_title", + wagtail.blocks.BooleanBlock(required=False), + ), + ( + "article", + wagtail.blocks.PageChooserBlock( + page_type=["home.Article"] + ), + ), + ] + ), + ), + ( + "download", + wagtail.blocks.StructBlock( + [ + ( + "available_text", + wagtail.blocks.CharBlock( + help_text="This text appears when it is possible for the user to install the app on their phone" + ), + ), + ( + "unavailable_text", + wagtail.blocks.CharBlock( + form_classname="red-help-text", + help_text="This text appears when the user is using a feature phone and thus cannot install the app (the button will be disabled in this case). [Currently not implemented]", + required=False, + ), + ), + ( + "offline_text", + wagtail.blocks.CharBlock( + help_text="This text appears when the user is navigating the site via the offline app and thus it does not make sense to install the offline app again (the button will be disabled in this case).", + required=False, + ), + ), + ( + "page", + wagtail.blocks.PageChooserBlock( + page_type=["wagtailcore.Page"] + ), + ), + ("description", wagtail.blocks.RichTextBlock()), + ] + ), + ), + ], + blank=True, + null=True, + use_json_field=True, + ), + ), + migrations.AlterField( + model_name="offlinecontentindexpage", + name="body", + field=wagtail.fields.StreamField( + [ + ( + "heading", + wagtail.blocks.CharBlock( + form_classname="full title", + icon="h1", + template="blocks/heading.html", + ), + ), + ("paragraph", wagtail.blocks.RichTextBlock()), + ("markdown", wagtailmarkdown.blocks.MarkdownBlock()), + ("paragraph_v1_legacy", home.blocks.RawHTMLBlock(icon="code")), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + template="blocks/image.html" + ), + ), + ( + "list", + wagtail.blocks.ListBlock( + wagtailmarkdown.blocks.MarkdownBlock(), icon="list-ul" + ), + ), + ( + "numbered_list", + home.blocks.NumberedListBlock( + wagtailmarkdown.blocks.MarkdownBlock() + ), + ), + ( + "page_button", + wagtail.blocks.StructBlock( + [ + ("page", wagtail.blocks.PageChooserBlock()), + ( + "text", + wagtail.blocks.CharBlock( + max_length=255, required=False + ), + ), + ] + ), + ), + ( + "embedded_poll", + wagtail.blocks.StructBlock( + [ + ( + "direct_display", + wagtail.blocks.BooleanBlock(required=False), + ), + ( + "poll", + home.blocks.EmbeddedQuestionnaireChooserBlock( + page_type=["questionnaires.Poll"] + ), + ), + ] + ), + ), + ( + "embedded_survey", + wagtail.blocks.StructBlock( + [ + ( + "direct_display", + wagtail.blocks.BooleanBlock(required=False), + ), + ( + "survey", + home.blocks.EmbeddedQuestionnaireChooserBlock( + page_type=["questionnaires.Survey"] + ), + ), + ] + ), + ), + ( + "embedded_quiz", + wagtail.blocks.StructBlock( + [ + ( + "direct_display", + wagtail.blocks.BooleanBlock(required=False), + ), + ( + "quiz", + home.blocks.EmbeddedQuestionnaireChooserBlock( + page_type=["questionnaires.Quiz"] + ), + ), + ] + ), + ), + ("media", home.blocks.MediaBlock()), + ( + "chat_bot", + wagtail.blocks.StructBlock( + [ + ("subject", wagtail.blocks.CharBlock()), + ("button_text", wagtail.blocks.CharBlock()), + ("trigger_string", wagtail.blocks.CharBlock()), + ( + "channel", + messaging.blocks.ChatBotChannelChooserBlock(), + ), + ] + ), + ), + ( + "download", + wagtail.blocks.StructBlock( + [ + ( + "available_text", + wagtail.blocks.CharBlock( + help_text="This text appears when it is possible for the user to install the app on their phone" + ), + ), + ( + "unavailable_text", + wagtail.blocks.CharBlock( + form_classname="red-help-text", + help_text="This text appears when the user is using a feature phone and thus cannot install the app (the button will be disabled in this case). [Currently not implemented]", + required=False, + ), + ), + ( + "offline_text", + wagtail.blocks.CharBlock( + help_text="This text appears when the user is navigating the site via the offline app and thus it does not make sense to install the offline app again (the button will be disabled in this case).", + required=False, + ), + ), + ( + "page", + wagtail.blocks.PageChooserBlock( + page_type=["wagtailcore.Page"] + ), + ), + ("description", wagtail.blocks.RichTextBlock()), + ] + ), + ), + ], + use_json_field=True, + ), + ), + migrations.AlterField( + model_name="section", + name="body", + field=wagtail.fields.StreamField( + [ + ( + "download", + wagtail.blocks.StructBlock( + [ + ( + "available_text", + wagtail.blocks.CharBlock( + help_text="This text appears when it is possible for the user to install the app on their phone" + ), + ), + ( + "unavailable_text", + wagtail.blocks.CharBlock( + form_classname="red-help-text", + help_text="This text appears when the user is using a feature phone and thus cannot install the app (the button will be disabled in this case). [Currently not implemented]", + required=False, + ), + ), + ( + "offline_text", + wagtail.blocks.CharBlock( + help_text="This text appears when the user is navigating the site via the offline app and thus it does not make sense to install the offline app again (the button will be disabled in this case).", + required=False, + ), + ), + ( + "page", + wagtail.blocks.PageChooserBlock( + page_type=["wagtailcore.Page"] + ), + ), + ("description", wagtail.blocks.RichTextBlock()), + ] + ), + ) + ], + blank=True, + null=True, + use_json_field=True, + ), + ), + migrations.AlterField( + model_name="sitesettings", + name="media_file_size_threshold", + field=models.IntegerField( + default=9437184, + help_text="Show warning if uploaded media file size is greater than this in bytes. Default is 9 MB (9,437,184 bytes).", + ), + ), + migrations.AlterField( + model_name="sitesettings", + name="social_media_content_sharing_button", + field=wagtail.fields.StreamField( + [ + ( + "social_media_content_sharing_button", + wagtail.blocks.StructBlock( + [ + ("platform", wagtail.blocks.CharBlock(max_length=255)), + ( + "is_active", + wagtail.blocks.BooleanBlock(required=False), + ), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + required=False, template="blocks/image.html" + ), + ), + ] + ), + ) + ], + blank=True, + null=True, + use_json_field=True, + ), + ), + ] diff --git a/questionnaires/migrations/0031_auto_20240603_1005.py b/questionnaires/migrations/0031_auto_20240603_1005.py new file mode 100644 index 000000000..ed835ba16 --- /dev/null +++ b/questionnaires/migrations/0031_auto_20240603_1005.py @@ -0,0 +1,337 @@ +# Generated by Django 3.2.25 on 2024-06-03 10:05 + +from django.db import migrations +import home.blocks +import questionnaires.blocks +import wagtail.blocks +import wagtail.fields +import wagtail.images.blocks +import wagtailmarkdown.blocks + + +class Migration(migrations.Migration): + + dependencies = [ + ("questionnaires", "0030_enable_use_json_field"), + ] + + operations = [ + migrations.AlterField( + model_name="poll", + name="description", + field=wagtail.fields.StreamField( + [ + ( + "heading", + wagtail.blocks.CharBlock( + form_classname="full title", + icon="h1", + template="blocks/heading.html", + ), + ), + ("paragraph", wagtail.blocks.RichTextBlock()), + ("paragraph_v1_legacy", home.blocks.RawHTMLBlock(icon="code")), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + template="blocks/image.html" + ), + ), + ("list", wagtailmarkdown.blocks.MarkdownBlock()), + ( + "numbered_list", + home.blocks.NumberedListBlock( + wagtailmarkdown.blocks.MarkdownBlock() + ), + ), + ( + "page_button", + wagtail.blocks.StructBlock( + [ + ("page", wagtail.blocks.PageChooserBlock()), + ( + "text", + wagtail.blocks.CharBlock( + max_length=255, required=False + ), + ), + ] + ), + ), + ], + blank=True, + null=True, + use_json_field=True, + ), + ), + migrations.AlterField( + model_name="poll", + name="terms_and_conditions", + field=wagtail.fields.StreamField( + [ + ("paragraph", wagtail.blocks.RichTextBlock()), + ( + "page_button", + wagtail.blocks.StructBlock( + [ + ("page", wagtail.blocks.PageChooserBlock()), + ( + "text", + wagtail.blocks.CharBlock( + max_length=255, required=False + ), + ), + ] + ), + ), + ], + blank=True, + null=True, + use_json_field=True, + ), + ), + migrations.AlterField( + model_name="poll", + name="thank_you_text", + field=wagtail.fields.StreamField( + [ + ("paragraph", wagtail.blocks.RichTextBlock()), + ("media", home.blocks.MediaBlock()), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + template="blocks/image.html" + ), + ), + ], + blank=True, + null=True, + use_json_field=True, + ), + ), + migrations.AlterField( + model_name="quiz", + name="description", + field=wagtail.fields.StreamField( + [ + ( + "heading", + wagtail.blocks.CharBlock( + form_classname="full title", + icon="h1", + template="blocks/heading.html", + ), + ), + ("paragraph", wagtail.blocks.RichTextBlock()), + ("paragraph_v1_legacy", home.blocks.RawHTMLBlock(icon="code")), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + template="blocks/image.html" + ), + ), + ("list", wagtailmarkdown.blocks.MarkdownBlock()), + ( + "numbered_list", + home.blocks.NumberedListBlock( + wagtailmarkdown.blocks.MarkdownBlock() + ), + ), + ( + "page_button", + wagtail.blocks.StructBlock( + [ + ("page", wagtail.blocks.PageChooserBlock()), + ( + "text", + wagtail.blocks.CharBlock( + max_length=255, required=False + ), + ), + ] + ), + ), + ], + blank=True, + null=True, + use_json_field=True, + ), + ), + migrations.AlterField( + model_name="quiz", + name="terms_and_conditions", + field=wagtail.fields.StreamField( + [ + ("paragraph", wagtail.blocks.RichTextBlock()), + ( + "page_button", + wagtail.blocks.StructBlock( + [ + ("page", wagtail.blocks.PageChooserBlock()), + ( + "text", + wagtail.blocks.CharBlock( + max_length=255, required=False + ), + ), + ] + ), + ), + ], + blank=True, + null=True, + use_json_field=True, + ), + ), + migrations.AlterField( + model_name="quiz", + name="thank_you_text", + field=wagtail.fields.StreamField( + [ + ("paragraph", wagtail.blocks.RichTextBlock()), + ("media", home.blocks.MediaBlock()), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + template="blocks/image.html" + ), + ), + ], + blank=True, + null=True, + use_json_field=True, + ), + ), + migrations.AlterField( + model_name="survey", + name="description", + field=wagtail.fields.StreamField( + [ + ( + "heading", + wagtail.blocks.CharBlock( + form_classname="full title", + icon="h1", + template="blocks/heading.html", + ), + ), + ("paragraph", wagtail.blocks.RichTextBlock()), + ("paragraph_v1_legacy", home.blocks.RawHTMLBlock(icon="code")), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + template="blocks/image.html" + ), + ), + ("list", wagtailmarkdown.blocks.MarkdownBlock()), + ( + "numbered_list", + home.blocks.NumberedListBlock( + wagtailmarkdown.blocks.MarkdownBlock() + ), + ), + ( + "page_button", + wagtail.blocks.StructBlock( + [ + ("page", wagtail.blocks.PageChooserBlock()), + ( + "text", + wagtail.blocks.CharBlock( + max_length=255, required=False + ), + ), + ] + ), + ), + ], + blank=True, + null=True, + use_json_field=True, + ), + ), + migrations.AlterField( + model_name="survey", + name="terms_and_conditions", + field=wagtail.fields.StreamField( + [ + ("paragraph", wagtail.blocks.RichTextBlock()), + ( + "page_button", + wagtail.blocks.StructBlock( + [ + ("page", wagtail.blocks.PageChooserBlock()), + ( + "text", + wagtail.blocks.CharBlock( + max_length=255, required=False + ), + ), + ] + ), + ), + ], + blank=True, + null=True, + use_json_field=True, + ), + ), + migrations.AlterField( + model_name="survey", + name="thank_you_text", + field=wagtail.fields.StreamField( + [ + ("paragraph", wagtail.blocks.RichTextBlock()), + ("media", home.blocks.MediaBlock()), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + template="blocks/image.html" + ), + ), + ], + blank=True, + null=True, + use_json_field=True, + ), + ), + migrations.AlterField( + model_name="surveyformfield", + name="skip_logic", + field=questionnaires.blocks.SkipLogicField( + [ + ( + "skip_logic", + wagtail.blocks.StructBlock( + [ + ("choice", wagtail.blocks.CharBlock(required=False)), + ( + "skip_logic", + wagtail.blocks.ChoiceBlock( + choices=[ + ("next", "Next default question"), + ("end", "End of survey"), + ("question", "Another question"), + ], + required=False, + ), + ), + ( + "question", + questionnaires.blocks.QuestionSelectBlock( + help_text="Please save the survey as a draft to populate or update the list of questions.", + required=False, + ), + ), + ] + ), + ) + ], + blank=True, + help_text="Checkbox must include exactly 2 Skip Logic Options: true and false, in that order.", + null=True, + use_json_field=True, + verbose_name="Answer options", + ), + ), + ] From 21dd2be1776607952a25f3c2fd193ff3d84f6201 Mon Sep 17 00:00:00 2001 From: Ian Stride Date: Mon, 3 Jun 2024 15:13:02 +0100 Subject: [PATCH 10/11] Increment site version --- iogt/settings/production.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iogt/settings/production.py b/iogt/settings/production.py index 9dc3dfbd7..c0f0f15bb 100644 --- a/iogt/settings/production.py +++ b/iogt/settings/production.py @@ -33,7 +33,7 @@ }, } -SITE_VERSION = '2.12.0' +SITE_VERSION = '2.13.0-rc.1' try: from .local import * From 07fab91b83cc92d97138be9479922d133cb7e54f Mon Sep 17 00:00:00 2001 From: Ian Stride Date: Mon, 3 Jun 2024 17:29:22 +0100 Subject: [PATCH 11/11] Prevent Wagtail Transfer from following ContentType model --- iogt/settings/base.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/iogt/settings/base.py b/iogt/settings/base.py index e0fc4d9c3..dbddb05b8 100644 --- a/iogt/settings/base.py +++ b/iogt/settings/base.py @@ -446,10 +446,12 @@ TRANSLATIONS_PROJECT_BASE_DIR = BASE_DIR WAGTAILTRANSFER_LOOKUP_FIELDS = { + "contenttypes.contenttype": ["app_label", "model"], 'taggit.tag': ['slug'], 'wagtailcore.locale': ['language_code'], 'iogt_users.user': ['username'], } +WAGTAILTRANSFER_NO_FOLLOW_MODELS = ["wagtailcore.page", "contenttypes.contenttype"] WAGTAILTRANSFER_SECRET_KEY = os.getenv('WAGTAILTRANSFER_SECRET_KEY') WAGTAILTRANSFER_SHOW_ERROR_FOR_REFERENCED_PAGES = True WAGTAILTRANSFER_SOURCES = {