diff --git a/constraints.txt b/constraints.txt index 20e9a025..dac63db4 100644 --- a/constraints.txt +++ b/constraints.txt @@ -16,9 +16,9 @@ django-appconf==1.0.5 django-compressor==4.0 django-filter==21.1 django-libsass==0.9 -django-modelcluster==5.3 +django-modelcluster==6.0 django-storages==1.12.3 -django-taggit==1.5.1 +django-taggit==2.1.0 django-treebeard==4.5.1 djangorestframework==3.13.1 docutils==0.15.2 @@ -58,9 +58,9 @@ tqdm==4.64.0 typed-environment-configuration==0.1.4 typepy==0.2.5 urllib3==1.26.9 -wagtail==2.15.5 +wagtail==3.0.1 wagtail-markdown==0.8.0 -wagtail-orderable==1.0.3 +wagtail-orderable==1.0.4 webencodings==0.5.1 Willow==1.4.1 xlrd==2.0.1 diff --git a/ietf/bibliography/management/commands/fix_BMI_page_links.py b/ietf/bibliography/management/commands/fix_BMI_page_links.py index 3815bb80..30841957 100644 --- a/ietf/bibliography/management/commands/fix_BMI_page_links.py +++ b/ietf/bibliography/management/commands/fix_BMI_page_links.py @@ -1,72 +1,71 @@ -from django.core.management.base import BaseCommand, CommandError - import re -from tqdm import tqdm - from bs4 import BeautifulSoup +from django.core.management.base import BaseCommand, CommandError +from tqdm import tqdm +from wagtail.models import Page -from wagtail.core.models import Page - -from ietf.standard.models import StandardPage from ietf.blog.models import BlogPage from ietf.iesg_statement.models import IESGStatementPage - from ietf.snippets.models import RFC, Charter, WorkingGroup +from ietf.standard.models import StandardPage + def change_links(page): - unfound_rfcs=set() + unfound_rfcs = set() - rfc_pattern = re.compile('^ *\\[? *RFC *(\\d{4}) *\\]? *$') + rfc_pattern = re.compile("^ *\\[? *RFC *(\\d{4}) *\\]? *$") - group_pattern1 = re.compile('\((\w+)\)\\xa0[Ww]orking [Gg]roup$') - group_pattern2 = re.compile(' *(\w+) +[Ww]orking [Gg]roup$') + group_pattern1 = re.compile("\((\w+)\)\\xa0[Ww]orking [Gg]roup$") + group_pattern2 = re.compile(" *(\w+) +[Ww]orking [Gg]roup$") for fieldname in page.CONTENT_FIELD_MAP.keys(): - field = getattr(page,fieldname) + field = getattr(page, fieldname) for item in field.stream_data: - if not item['type'] in ('paragraph','raw_html'): + if not item["type"] in ("paragraph", "raw_html"): continue - soup = BeautifulSoup(item['value'], 'html.parser') - for tag in soup.find_all('a',string=rfc_pattern): - if 'href' in tag.attrs: + soup = BeautifulSoup(item["value"], "html.parser") + for tag in soup.find_all("a", string=rfc_pattern): + if "href" in tag.attrs: continue rfc_number = rfc_pattern.match(tag.string)[1] rfc = RFC.objects.filter(rfc=rfc_number).first() if not rfc: unfound_rfcs.add(rfc_number) continue - tag['data-app'] = 'snippets' - tag['data-linktype'] = 'rfc' - tag['data-id'] = str(rfc.pk) + tag["data-app"] = "snippets" + tag["data-linktype"] = "rfc" + tag["data-id"] = str(rfc.pk) for pattern in (group_pattern1, group_pattern2): - for tag in soup.find_all('a',string=pattern): - if 'href' in tag.attrs: + for tag in soup.find_all("a", string=pattern): + if "href" in tag.attrs: continue - if 'linktype' in tag.attrs and tag['linktype']!='charter': + if "linktype" in tag.attrs and tag["linktype"] != "charter": continue if not pattern.search(tag.string): - print ("Search failure", tag.string, pattern) - print (page.url_path) + print("Search failure", tag.string, pattern) + print(page.url_path) continue acronym = pattern.search(tag.string)[1].lower() - charter = Charter.objects.filter(working_group__acronym=acronym).first() + charter = Charter.objects.filter( + working_group__acronym=acronym + ).first() if charter: - tag['data-app'] = 'snippets' - tag['data-linktype'] = 'charter' - tag['data-id'] = str(charter.pk) + tag["data-app"] = "snippets" + tag["data-linktype"] = "charter" + tag["data-id"] = str(charter.pk) else: group = WorkingGroup.objects.filter(acronym=acronym).first() if group: - tag['data-app'] = 'snippets' - tag['data-linktype'] = 'workinggroup' - tag['data-id'] = str(group.pk) + tag["data-app"] = "snippets" + tag["data-linktype"] = "workinggroup" + tag["data-id"] = str(group.pk) else: - print("Nothing found in ",str(tag)) + print("Nothing found in ", str(tag)) print("Acronym was", acronym) continue - item['value'] = str(soup) + item["value"] = str(soup) all_the_fields = list(page.CONTENT_FIELD_MAP.keys()) all_the_fields.extend(list(page.CONTENT_FIELD_MAP.values())) @@ -74,31 +73,32 @@ def change_links(page): return unfound_rfcs + class Command(BaseCommand): - help = 'Replace tag parameters on pages using BibliographyMixin' + help = "Replace tag parameters on pages using BibliographyMixin" def add_arguments(self, parser): - parser.add_argument('url_paths', nargs='*', type=str) + parser.add_argument("url_paths", nargs="*", type=str) def handle(self, *args, **options): - unfound_rfcs=set() + unfound_rfcs = set() - if options['url_paths']: - for url_path in options['url_paths']: + if options["url_paths"]: + for url_path in options["url_paths"]: page = Page.objects.filter(url_path=url_path).first() if not page: - CommandError('Page with path '+url_path+' not found') + CommandError("Page with path " + url_path + " not found") unfound_rfcs.update(change_links(page.specific)) else: - print ('Standard Pages:') + print("Standard Pages:") for page in tqdm(StandardPage.objects.all()): unfound_rfcs.update(change_links(page)) - print ('Blog Pages:') + print("Blog Pages:") for page in tqdm(BlogPage.objects.all()): unfound_rfcs.update(change_links(page)) - print ('IESGStatement Pages:') + print("IESGStatement Pages:") for page in tqdm(IESGStatementPage.objects.all()): unfound_rfcs.update(change_links(page)) if unfound_rfcs: - print ("Unfound RFCs", unfound_rfcs) + print("Unfound RFCs", unfound_rfcs) diff --git a/ietf/bibliography/models.py b/ietf/bibliography/models.py index 4343b32a..c456fe1f 100644 --- a/ietf/bibliography/models.py +++ b/ietf/bibliography/models.py @@ -1,14 +1,12 @@ from bs4 import BeautifulSoup, NavigableString - -from django.template import TemplateDoesNotExist -from django.template.loader import get_template +from django.apps import apps from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType -from django.db import models -from django.apps import apps from django.core.exceptions import ObjectDoesNotExist - -from wagtail.core.models import Page +from django.db import models +from django.template import TemplateDoesNotExist +from django.template.loader import get_template +from wagtail.models import Page from ietf.utils import OrderedSet @@ -26,7 +24,7 @@ class BibliographyItem(models.Model): ) page = models.ForeignKey( Page, - related_name='bibliography_items', + related_name="bibliography_items", help_text="The page that this item links to.", on_delete=models.CASCADE, ) @@ -41,23 +39,22 @@ class BibliographyItem(models.Model): max_length=127, help_text='The "value" with which this item was created, eg. "3514" in [[rfc:3514]].', ) - content_long_title = models.CharField( - max_length=127, - blank=True - ) + content_long_title = models.CharField(max_length=127, blank=True) content_title = models.CharField( max_length=127, help_text='The link title for this item, eg. "RFC 7168" for [[rfc:7168]].', ) content_type = models.ForeignKey( ContentType, - blank=True, null=True, + blank=True, + null=True, on_delete=models.CASCADE, ) object_id = models.PositiveIntegerField( - blank=True, null=True, + blank=True, + null=True, ) - content_object = GenericForeignKey('content_type', 'object_id') + content_object = GenericForeignKey("content_type", "object_id") def render_title(self): if not self.content_object: @@ -73,10 +70,10 @@ def render_uri(self): @property def link(self): - soup = BeautifulSoup("", 'html5lib') - link = soup.new_tag('a', href="#bibliography" + str(self.ordering)) - link['class'] = "bibliography-reference" - link['data-ordering'] = str(self.ordering) + soup = BeautifulSoup("", "html5lib") + link = soup.new_tag("a", href="#bibliography" + str(self.ordering)) + link["class"] = "bibliography-reference" + link["data-ordering"] = str(self.ordering) link.insert(0, NavigableString(self.content_title)) return link @@ -91,24 +88,21 @@ def render(self, request=None): else: try: template = get_template( - 'bibliography/item_{}.html'.format(self.content_key) + "bibliography/item_{}.html".format(self.content_key) ) except TemplateDoesNotExist: template = None BibliographyItem.TEMPLATE_CACHE[self.content_key] = template if template: - return template.render({ - 'object': self.content_object, - 'item': self - }, request=request) + return template.render( + {"object": self.content_object, "item": self}, request=request + ) else: return str(object) def __str__(self): - return "Bibliography Item #{}: {}".format( - self.ordering, self.content_object - ) + return "Bibliography Item #{}: {}".format(self.ordering, self.content_object) class BibliographyMixin(models.Model): @@ -125,59 +119,70 @@ def save(self, *args, **kwargs): # Don't update prepared content fields if none of the source fields are being updated (e.g. when saving a draft) # NB - We have to update all prepared and source fields or none, as there's no way of determining which field a # given BibliographyItem appears in. - update_fields = kwargs.get('update_fields') + update_fields = kwargs.get("update_fields") recreate_bibliography_items = True if update_fields is not None: - source_fields_being_updated = [source_field in update_fields for source_field in self.CONTENT_FIELD_MAP.values()] - prepared_fields_being_updated = [prepared_field in update_fields for prepared_field in self.CONTENT_FIELD_MAP.keys()] + source_fields_being_updated = [ + source_field in update_fields + for source_field in self.CONTENT_FIELD_MAP.values() + ] + prepared_fields_being_updated = [ + prepared_field in update_fields + for prepared_field in self.CONTENT_FIELD_MAP.keys() + ] if any(source_fields_being_updated) or any(prepared_fields_being_updated): - if not all(source_fields_being_updated) or not all(prepared_fields_being_updated): - raise ValueError('Either all prepared content fields must be updated or none') + if not all(source_fields_being_updated) or not all( + prepared_fields_being_updated + ): + raise ValueError( + "Either all prepared content fields must be updated or none" + ) else: recreate_bibliography_items = False if recreate_bibliography_items: self.bibliography_items.all().delete() - all_content = "".join([ - str(getattr(self, content_field)) or '' for content_field - in self.CONTENT_FIELD_MAP.keys() - ]) - all_soup = BeautifulSoup(all_content, 'html.parser') + all_content = "".join( + [ + str(getattr(self, content_field)) or "" + for content_field in self.CONTENT_FIELD_MAP.keys() + ] + ) + all_soup = BeautifulSoup(all_content, "html.parser") subsoups = { prepared_content_field: BeautifulSoup( - str(getattr(self, content_field)) or '', 'html.parser' - ) for content_field, prepared_content_field in - self.CONTENT_FIELD_MAP.items() + str(getattr(self, content_field)) or "", "html.parser" + ) + for content_field, prepared_content_field in self.CONTENT_FIELD_MAP.items() } - tags = OrderedSet(all_soup.find_all('a', attrs={'data-app': True})) + tags = OrderedSet(all_soup.find_all("a", attrs={"data-app": True})) for tag in tags: - app = tag['data-app'] - model = tag['data-linktype'] - obj_id = tag['data-id'] + app = tag["data-app"] + model = tag["data-linktype"] + obj_id = tag["data-id"] try: - obj = apps.get_model( - app_label=app, - model_name=model - ).objects.get(pk=obj_id) + obj = apps.get_model(app_label=app, model_name=model).objects.get( + pk=obj_id + ) try: long_title = obj.long_title except AttributeError: long_title = "" object_details = { - 'content_object': obj, - 'content_long_title': long_title, - 'content_title': obj.__str__() + "content_object": obj, + "content_long_title": long_title, + "content_title": obj.__str__(), } except ObjectDoesNotExist: object_details = { - 'content_object': None, - 'content_long_title': "", - 'content_title': '(removed)' + "content_object": None, + "content_long_title": "", + "content_title": "(removed)", } item = BibliographyItem.objects.create( page=self, @@ -187,11 +192,14 @@ def save(self, *args, **kwargs): **object_details ) for soup in subsoups.values(): - for t in soup.find_all('a', attrs={ - 'data-app': app, - 'data-linktype': model, - 'data-id': obj_id - }): + for t in soup.find_all( + "a", + attrs={ + "data-app": app, + "data-linktype": model, + "data-id": obj_id, + }, + ): t.replaceWith(item.link) for prepared_content_field, prepared_soup in subsoups.items(): diff --git a/ietf/bibliography/views.py b/ietf/bibliography/views.py index 1f897190..181584e4 100644 --- a/ietf/bibliography/views.py +++ b/ietf/bibliography/views.py @@ -1,52 +1,65 @@ -from django.shortcuts import render from django.contrib.contenttypes.models import ContentType from django.db.models import Count - -from wagtail.core.models import Page +from django.shortcuts import render +from wagtail.models import Page from .models import BibliographyItem def referenced_types(request): - content_types = BibliographyItem.objects.exclude( - content_type=None - ).order_by().values_list( - 'content_type' - ).distinct().annotate(num=Count('content_type')).order_by('-num') - return render(request, 'bibliography/referenced_types.html', { - 'types': [ - (ContentType.objects.get(pk=type_id), count) - for type_id, count in content_types - ] - }) + content_types = ( + BibliographyItem.objects.exclude(content_type=None) + .order_by() + .values_list("content_type") + .distinct() + .annotate(num=Count("content_type")) + .order_by("-num") + ) + return render( + request, + "bibliography/referenced_types.html", + { + "types": [ + (ContentType.objects.get(pk=type_id), count) + for type_id, count in content_types + ] + }, + ) def referenced_objects(request, content_type_id): content_type = ContentType.objects.get(pk=content_type_id) - object_ids = BibliographyItem.objects.filter( - content_type=content_type_id - ).order_by().values_list('object_id').distinct().annotate( - num=Count('object_id') - ).order_by('-num') - return render(request, 'bibliography/referenced_objects.html', { - 'title': content_type._meta.verbose_name, - 'content_type_id': content_type_id, - 'objects': [ - (content_type.get_object_for_this_type(id=object_id), count) - for object_id, count in object_ids - ] - }) + object_ids = ( + BibliographyItem.objects.filter(content_type=content_type_id) + .order_by() + .values_list("object_id") + .distinct() + .annotate(num=Count("object_id")) + .order_by("-num") + ) + return render( + request, + "bibliography/referenced_objects.html", + { + "title": content_type._meta.verbose_name, + "content_type_id": content_type_id, + "objects": [ + (content_type.get_object_for_this_type(id=object_id), count) + for object_id, count in object_ids + ], + }, + ) def referencing_pages(request, content_type_id, object_id): content_type = ContentType.objects.get(pk=content_type_id) obj = content_type.get_object_for_this_type(id=object_id) page_ids = BibliographyItem.objects.filter( - content_type=content_type_id, - object_id=object_id - ).values_list('page', flat=True) + content_type=content_type_id, object_id=object_id + ).values_list("page", flat=True) pages = Page.objects.filter(pk__in=page_ids) - return render(request, 'bibliography/referencing_pages.html', { - 'title': obj.__str__(), - 'pages': pages - }) + return render( + request, + "bibliography/referencing_pages.html", + {"title": obj.__str__(), "pages": pages}, + ) diff --git a/ietf/bibliography/wagtail_hooks.py b/ietf/bibliography/wagtail_hooks.py index 2e32274f..4007ce38 100644 --- a/ietf/bibliography/wagtail_hooks.py +++ b/ietf/bibliography/wagtail_hooks.py @@ -1,12 +1,13 @@ from django.urls import reverse - -from wagtail.core import hooks +from wagtail import hooks from wagtail.admin.menu import MenuItem -@hooks.register('register_admin_menu_item') +@hooks.register("register_admin_menu_item") def register_references_menu_item(): - return MenuItem('References', - reverse('referenced_types'), - classnames='icon icon-folder-inverse', - order=10000) + return MenuItem( + "References", + reverse("referenced_types"), + classnames="icon icon-folder-inverse", + order=10000, + ) diff --git a/ietf/blog/feeds.py b/ietf/blog/feeds.py index 4a55e787..438259bc 100644 --- a/ietf/blog/feeds.py +++ b/ietf/blog/feeds.py @@ -1,6 +1,7 @@ from django.contrib.syndication.views import Feed from django.db.models.functions import Coalesce -from wagtail.core.models import Site +from wagtail.models import Site + from ..blog.models import BlogPage from ..utils.models import FeedSettings @@ -15,8 +16,11 @@ def __call__(self, request, *args, **kwargs): return super().__call__(request, *args, **kwargs) def items(self): - return BlogPage.objects.live().annotate( - d=Coalesce('date_published', 'first_published_at')).order_by('-d') + return ( + BlogPage.objects.live() + .annotate(d=Coalesce("date_published", "first_published_at")) + .order_by("-d") + ) def item_title(self, item): return item.title diff --git a/ietf/blog/migrations/0001_initial.py b/ietf/blog/migrations/0001_initial.py index 72ebdf08..9033b944 100644 --- a/ietf/blog/migrations/0001_initial.py +++ b/ietf/blog/migrations/0001_initial.py @@ -2,15 +2,15 @@ # Generated by Django 1.11.29 on 2020-04-10 18:46 from __future__ import unicode_literals -from django.db import migrations, models import django.db.models.deletion import modelcluster.fields +import wagtail.blocks import wagtail.contrib.routable_page.models import wagtail.contrib.table_block.blocks -import wagtail.core.blocks -import wagtail.core.fields import wagtail.embeds.blocks +import wagtail.fields import wagtail.images.blocks +from django.db import migrations, models class Migration(migrations.Migration): @@ -18,55 +18,214 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('wagtailcore', '0040_page_draft_title'), - ('snippets', '0001_initial'), - ('images', '0001_initial'), + ("wagtailcore", "0040_page_draft_title"), + ("snippets", "0001_initial"), + ("images", "0001_initial"), ] operations = [ migrations.CreateModel( - name='BlogIndexPage', + name="BlogIndexPage", fields=[ - ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')), + ( + "page_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="wagtailcore.Page", + ), + ), ], options={ - 'verbose_name': 'Blog Index Page', + "verbose_name": "Blog Index Page", }, - bases=(wagtail.contrib.routable_page.models.RoutablePageMixin, 'wagtailcore.page'), + bases=( + wagtail.contrib.routable_page.models.RoutablePageMixin, + "wagtailcore.page", + ), ), migrations.CreateModel( - name='BlogPage', + name="BlogPage", fields=[ - ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')), - ('social_text', models.CharField(blank=True, help_text='Description of this page as it should appear when shared on social networks, or in Google results', max_length=255)), - ('date_published', models.DateTimeField(blank=True, help_text='Use this field to override the date that the blog post appears to have been published.', null=True)), - ('introduction', models.CharField(help_text='The page introduction text.', max_length=511)), - ('body', wagtail.core.fields.StreamField([('heading', wagtail.core.blocks.CharBlock(icon='title')), ('paragraph', wagtail.core.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.images.blocks.ImageChooserBlock(icon='image', template='includes/imageblock.html')), ('embed', wagtail.embeds.blocks.EmbedBlock(icon='code')), ('raw_html', wagtail.core.blocks.RawHTMLBlock(icon='placeholder')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}))])), - ('prepared_body', models.TextField(blank=True, help_text='The prepared body content after bibliography styling has been applied. Auto-generated on each save.', null=True)), - ('author_group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='snippets.Group')), - ('feed_image', models.ForeignKey(blank=True, help_text='This image will be used in listings and indexes across the site, if no feed image is added, the social image will be used.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.IETFImage')), - ('social_image', models.ForeignKey(blank=True, help_text="Image to appear alongside 'social text', particularly for sharing on social networks", null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.IETFImage')), + ( + "page_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="wagtailcore.Page", + ), + ), + ( + "social_text", + models.CharField( + blank=True, + help_text="Description of this page as it should appear when shared on social networks, or in Google results", + max_length=255, + ), + ), + ( + "date_published", + models.DateTimeField( + blank=True, + help_text="Use this field to override the date that the blog post appears to have been published.", + null=True, + ), + ), + ( + "introduction", + models.CharField( + help_text="The page introduction text.", max_length=511 + ), + ), + ( + "body", + wagtail.fields.StreamField( + [ + ("heading", wagtail.blocks.CharBlock(icon="title")), + ("paragraph", wagtail.blocks.RichTextBlock(icon="pilcrow")), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + icon="image", template="includes/imageblock.html" + ), + ), + ("embed", wagtail.embeds.blocks.EmbedBlock(icon="code")), + ( + "raw_html", + wagtail.blocks.RawHTMLBlock(icon="placeholder"), + ), + ( + "table", + wagtail.contrib.table_block.blocks.TableBlock( + table_options={"renderer": "html"} + ), + ), + ] + ), + ), + ( + "prepared_body", + models.TextField( + blank=True, + help_text="The prepared body content after bibliography styling has been applied. Auto-generated on each save.", + null=True, + ), + ), + ( + "author_group", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to="snippets.Group", + ), + ), + ( + "feed_image", + models.ForeignKey( + blank=True, + help_text="This image will be used in listings and indexes across the site, if no feed image is added, the social image will be used.", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to="images.IETFImage", + ), + ), + ( + "social_image", + models.ForeignKey( + blank=True, + help_text="Image to appear alongside 'social text', particularly for sharing on social networks", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to="images.IETFImage", + ), + ), ], options={ - 'verbose_name': 'Blog Page', + "verbose_name": "Blog Page", }, - bases=('wagtailcore.page', models.Model), + bases=("wagtailcore.page", models.Model), ), migrations.CreateModel( - name='BlogPageAuthor', + name="BlogPageAuthor", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('author', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='snippets.Person')), - ('page', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='authors', to='blog.BlogPage')), - ('role', models.ForeignKey(blank=True, help_text="Override the person's current role for this blog post.", null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='snippets.Role')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "author", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to="snippets.Person", + ), + ), + ( + "page", + modelcluster.fields.ParentalKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="authors", + to="blog.BlogPage", + ), + ), + ( + "role", + models.ForeignKey( + blank=True, + help_text="Override the person's current role for this blog post.", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to="snippets.Role", + ), + ), ], ), migrations.CreateModel( - name='BlogPageTopic', + name="BlogPageTopic", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('page', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='topics', to='blog.BlogPage')), - ('topic', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='snippets.Topic')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "page", + modelcluster.fields.ParentalKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="topics", + to="blog.BlogPage", + ), + ), + ( + "topic", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to="snippets.Topic", + ), + ), ], ), ] diff --git a/ietf/blog/migrations/0002_auto_20210325_0442.py b/ietf/blog/migrations/0002_auto_20210325_0442.py index 1b758bba..ee561a33 100644 --- a/ietf/blog/migrations/0002_auto_20210325_0442.py +++ b/ietf/blog/migrations/0002_auto_20210325_0442.py @@ -1,23 +1,43 @@ # Generated by Django 2.2.16 on 2021-03-25 04:42 -from django.db import migrations +import wagtail.blocks import wagtail.contrib.table_block.blocks -import wagtail.core.blocks -import wagtail.core.fields import wagtail.embeds.blocks +import wagtail.fields import wagtail.images.blocks +from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('blog', '0001_initial'), + ("blog", "0001_initial"), ] operations = [ migrations.AlterField( - model_name='blogpage', - name='body', - field=wagtail.core.fields.StreamField([('heading', wagtail.core.blocks.CharBlock(icon='title')), ('paragraph', wagtail.core.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.images.blocks.ImageChooserBlock(icon='image', template='includes/imageblock.html')), ('embed', wagtail.embeds.blocks.EmbedBlock(icon='code')), ('raw_html', wagtail.core.blocks.RawHTMLBlock(icon='placeholder')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}, template='includes/tableblock.html'))]), + model_name="blogpage", + name="body", + field=wagtail.fields.StreamField( + [ + ("heading", wagtail.blocks.CharBlock(icon="title")), + ("paragraph", wagtail.blocks.RichTextBlock(icon="pilcrow")), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + icon="image", template="includes/imageblock.html" + ), + ), + ("embed", wagtail.embeds.blocks.EmbedBlock(icon="code")), + ("raw_html", wagtail.blocks.RawHTMLBlock(icon="placeholder")), + ( + "table", + wagtail.contrib.table_block.blocks.TableBlock( + table_options={"renderer": "html"}, + template="includes/tableblock.html", + ), + ), + ] + ), ), ] diff --git a/ietf/blog/migrations/0003_auto_20211101_0113.py b/ietf/blog/migrations/0003_auto_20211101_0113.py index bf92f984..a006494b 100644 --- a/ietf/blog/migrations/0003_auto_20211101_0113.py +++ b/ietf/blog/migrations/0003_auto_20211101_0113.py @@ -1,24 +1,45 @@ # Generated by Django 2.2.19 on 2021-11-01 01:13 -from django.db import migrations +import wagtail.blocks import wagtail.contrib.table_block.blocks -import wagtail.core.blocks -import wagtail.core.fields import wagtail.embeds.blocks +import wagtail.fields import wagtail.images.blocks import wagtailmarkdown.blocks +from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('blog', '0002_auto_20210325_0442'), + ("blog", "0002_auto_20210325_0442"), ] operations = [ migrations.AlterField( - model_name='blogpage', - name='body', - field=wagtail.core.fields.StreamField([('heading', wagtail.core.blocks.CharBlock(icon='title')), ('paragraph', wagtail.core.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.images.blocks.ImageChooserBlock(icon='image', template='includes/imageblock.html')), ('markdown', wagtailmarkdown.blocks.MarkdownBlock(icon='code')), ('embed', wagtail.embeds.blocks.EmbedBlock(icon='code')), ('raw_html', wagtail.core.blocks.RawHTMLBlock(icon='placeholder')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}, template='includes/tableblock.html'))]), + model_name="blogpage", + name="body", + field=wagtail.fields.StreamField( + [ + ("heading", wagtail.blocks.CharBlock(icon="title")), + ("paragraph", wagtail.blocks.RichTextBlock(icon="pilcrow")), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + icon="image", template="includes/imageblock.html" + ), + ), + ("markdown", wagtailmarkdown.blocks.MarkdownBlock(icon="code")), + ("embed", wagtail.embeds.blocks.EmbedBlock(icon="code")), + ("raw_html", wagtail.blocks.RawHTMLBlock(icon="placeholder")), + ( + "table", + wagtail.contrib.table_block.blocks.TableBlock( + table_options={"renderer": "html"}, + template="includes/tableblock.html", + ), + ), + ] + ), ), ] diff --git a/ietf/blog/migrations/0004_alter_blogpage_body.py b/ietf/blog/migrations/0004_alter_blogpage_body.py new file mode 100644 index 00000000..dc2a0134 --- /dev/null +++ b/ietf/blog/migrations/0004_alter_blogpage_body.py @@ -0,0 +1,24 @@ +# Generated by Django 3.2.13 on 2022-09-02 04:24 + +from django.db import migrations +import wagtail.blocks +import wagtail.contrib.table_block.blocks +import wagtail.embeds.blocks +import wagtail.fields +import wagtail.images.blocks +import wagtailmarkdown.blocks + + +class Migration(migrations.Migration): + + dependencies = [ + ('blog', '0003_auto_20211101_0113'), + ] + + operations = [ + migrations.AlterField( + model_name='blogpage', + name='body', + field=wagtail.fields.StreamField([('heading', wagtail.blocks.CharBlock(icon='title')), ('paragraph', wagtail.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.images.blocks.ImageChooserBlock(icon='image', template='includes/imageblock.html')), ('markdown', wagtailmarkdown.blocks.MarkdownBlock(icon='code')), ('embed', wagtail.embeds.blocks.EmbedBlock(icon='code')), ('raw_html', wagtail.blocks.RawHTMLBlock(icon='placeholder')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}, template='includes/tableblock.html'))], use_json_field=True), + ), + ] diff --git a/ietf/blog/models.py b/ietf/blog/models.py index 17018179..622ba11f 100644 --- a/ietf/blog/models.py +++ b/ietf/blog/models.py @@ -1,40 +1,33 @@ from datetime import datetime from functools import partial +from django.core.exceptions import ObjectDoesNotExist from django.db import models from django.db.models.functions import Coalesce from django.http import Http404 -from django.shortcuts import redirect, get_object_or_404 -from django.core.exceptions import ObjectDoesNotExist +from django.shortcuts import get_object_or_404, redirect from django.utils import functional from django.utils.safestring import mark_safe - from modelcluster.fields import ParentalKey - -from wagtail.core.models import Page, Site -from wagtail.core.fields import StreamField -from wagtail.snippets.edit_handlers import ( - SnippetChooserPanel -) -from wagtail.search import index -from wagtail.admin.edit_handlers import ( - StreamFieldPanel, FieldPanel, InlinePanel -) +from wagtail.admin.panels import FieldPanel, InlinePanel from wagtail.contrib.routable_page.models import RoutablePageMixin, route +from wagtail.fields import StreamField +from wagtail.models import Page, Site +from wagtail.search import index from ..bibliography.models import BibliographyMixin -from ..utils.models import FeedSettings, PromoteMixin +from ..snippets.models import Topic from ..utils.blocks import StandardBlock -from ..snippets.models import Topic +from ..utils.models import FeedSettings, PromoteMixin def ordered_live_annotated_blogs(sibling=None): - blogs = BlogPage.objects.live().prefetch_related('authors') + blogs = BlogPage.objects.live().prefetch_related("authors") if sibling: blogs = blogs.sibling_of(sibling) - blogs = blogs.annotate( - d=Coalesce('date_published', 'first_published_at') - ).order_by('-d') + blogs = blogs.annotate(d=Coalesce("date_published", "first_published_at")).order_by( + "-d" + ) return blogs @@ -53,36 +46,34 @@ def parse_date_search_input(date): def build_filter_text(**kwargs): if any(kwargs): text_fragments = [] - if kwargs.get('topic'): + if kwargs.get("topic"): + text_fragments.append("{}".format(kwargs.get("topic"))) + if kwargs.get("secondary_topic"): # for legacy URI support text_fragments.append( - '{}'.format(kwargs.get('topic')) - ) - if kwargs.get('secondary_topic'): # for legacy URI support + "{}".format(kwargs.get("secondary_topic")) + ) + if kwargs.get("date_from") and kwargs.get("date_to"): text_fragments.append( - '{}'.format(kwargs.get('secondary_topic')) + "dates between {} & {}".format( + kwargs["date_from"], kwargs["date_to"] ) - if kwargs.get('date_from') and kwargs.get('date_to'): + ) + elif kwargs.get("date_from"): text_fragments.append( - 'dates between {} & {}'.format( - kwargs['date_from'], kwargs['date_to'] - ) + "dates after {}".format(kwargs["date_from"]) + ) + elif kwargs.get("date_to"): + text_fragments.append( + "dates before {}".format(kwargs["date_to"]) ) - elif kwargs.get('date_from'): - text_fragments.append('dates after {}'.format( - kwargs['date_from'] - )) - elif kwargs.get('date_to'): - text_fragments.append('dates before {}'.format( - kwargs['date_to'] - )) - return ', '.join(text_fragments) + return ", ".join(text_fragments) else: return "" parameter_functions_map = { - 'date_from': [parse_date_search_input, filter_pages_by_date_from], - 'date_to': [parse_date_search_input, filter_pages_by_date_to] + "date_from": [parse_date_search_input, filter_pages_by_date_from], + "date_to": [parse_date_search_input, filter_pages_by_date_to], } @@ -91,19 +82,15 @@ class BlogPageTopic(models.Model): A through model from :model:`blog.BlogPage` to :model:`snippets.Topic` """ - page = ParentalKey( - 'blog.BlogPage', - related_name='topics' - ) + + page = ParentalKey("blog.BlogPage", related_name="topics") topic = models.ForeignKey( - 'snippets.Topic', - related_name='+', + "snippets.Topic", + related_name="+", on_delete=models.CASCADE, ) - panels = [ - SnippetChooserPanel('topic') - ] + panels = [FieldPanel("topic")] class BlogPageAuthor(models.Model): @@ -111,27 +98,27 @@ class BlogPageAuthor(models.Model): A through model from :model:`blog.BlogPage` to :model:`snippets.Person` """ - page = ParentalKey( - 'blog.BlogPage', - related_name='authors' - ) + + page = ParentalKey("blog.BlogPage", related_name="authors") author = models.ForeignKey( - 'snippets.Person', + "snippets.Person", on_delete=models.CASCADE, - related_name='+', - null=True, blank=True, + related_name="+", + null=True, + blank=True, ) role = models.ForeignKey( - 'snippets.Role', - null=True, blank=True, + "snippets.Role", + null=True, + blank=True, on_delete=models.SET_NULL, - related_name='+', - help_text="Override the person's current role for this blog post." + related_name="+", + help_text="Override the person's current role for this blog post.", ) panels = [ - SnippetChooserPanel('author'), - SnippetChooserPanel('role'), + FieldPanel("author"), + FieldPanel("role"), ] @@ -139,34 +126,37 @@ class BlogPage(Page, BibliographyMixin, PromoteMixin): """ A page for the IETF's news and commentary. """ + author_group = models.ForeignKey( - 'snippets.Group', - null=True, blank=True, + "snippets.Group", + null=True, + blank=True, on_delete=models.SET_NULL, - related_name='+', + related_name="+", ) date_published = models.DateTimeField( - null=True, blank=True, + null=True, + blank=True, help_text="Use this field to override the date that the " - "blog post appears to have been published." + "blog post appears to have been published.", ) introduction = models.CharField( - max_length=511, - help_text="The page introduction text." + max_length=511, help_text="The page introduction text." ) - body = StreamField(StandardBlock()) + body = StreamField(StandardBlock(), use_json_field=True) search_fields = Page.search_fields + [ - index.SearchField('introduction'), - index.SearchField('body'), + index.SearchField("introduction"), + index.SearchField("body"), ] # for bibliography prepared_body = models.TextField( - blank=True, null=True, + blank=True, + null=True, help_text="The prepared body content after bibliography styling has been applied. Auto-generated on each save.", ) - CONTENT_FIELD_MAP = {'body': 'prepared_body'} + CONTENT_FIELD_MAP = {"body": "prepared_body"} def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -187,14 +177,20 @@ def date(self): def next(self): if not self.date: return None - after = sorted([p for p in self.siblings if p.date > self.date], key=lambda o:o.date) + after = sorted( + [p for p in self.siblings if p.date > self.date], key=lambda o: o.date + ) return after and after[0] or None @property def previous(self): if not self.date: return None - before = sorted([p for p in self.siblings if p.date < self.date], key=lambda o:o.date, reverse=True) + before = sorted( + [p for p in self.siblings if p.date < self.date], + key=lambda o: o.date, + reverse=True, + ) return before and before[0] or None def coalesced_published_date(self): @@ -207,9 +203,13 @@ def feed_text(self): @functional.cached_property def siblings(self): """Published siblings that match filter_topic, most recent first""" - qs = self.__class__.objects.live().sibling_of(self).exclude(pk=self.pk).annotate( - d=Coalesce('date_published', 'first_published_at') - ).order_by('-d') + qs = ( + self.__class__.objects.live() + .sibling_of(self) + .exclude(pk=self.pk) + .annotate(d=Coalesce("date_published", "first_published_at")) + .order_by("-d") + ) if self.filter_topic: qs = qs.filter(topics__topic=self.filter_topic) return qs @@ -230,8 +230,9 @@ def get_context(self, request, *args, **kwargs): related_object = functions[0](search_query) siblings = functions[1](siblings, related_object) query_string += "%s=%s&" % (parameter, search_query) - filter_text_builder = partial(filter_text_builder, - **{parameter: related_object.__str__()}) + filter_text_builder = partial( + filter_text_builder, **{parameter: related_object.__str__()} + ) except (ValueError, ObjectDoesNotExist): pass @@ -239,7 +240,7 @@ def get_context(self, request, *args, **kwargs): if self.filter_topic: if filter_text: - filter_text = ','.join([self.filter_topic.title, filter_text]) + filter_text = ",".join([self.filter_topic.title, filter_text]) else: filter_text = self.filter_topic.title @@ -247,65 +248,69 @@ def get_context(self, request, *args, **kwargs): if siblings: filter_text = mark_safe("You have filtered by " + filter_text) else: - filter_text = mark_safe("No results for " + filter_text + ", showing latest") + filter_text = mark_safe( + "No results for " + filter_text + ", showing latest" + ) context.update( parent_url=self.get_parent().url, - filter_text = filter_text, - filter_topic = self.filter_topic, + filter_text=filter_text, + filter_topic=self.filter_topic, siblings=siblings[:max_siblings_to_show], - topics=BlogPageTopic.objects.all().values_list( - 'topic__pk', 'topic__title' - ).distinct(), + topics=BlogPageTopic.objects.all() + .values_list("topic__pk", "topic__title") + .distinct(), query_string=query_string, - blog_feed_title=feed_settings.blog_feed_title + blog_feed_title=feed_settings.blog_feed_title, ) return context def serve(self, request, *args, **kwargs): - topic_id = request.GET.get('topic') + topic_id = request.GET.get("topic") if not topic_id: - topic_id = request.GET.get('secondary_topic') # For legacy URI support + topic_id = request.GET.get("secondary_topic") # For legacy URI support if topic_id: try: topic_id = int(topic_id) except ValueError: raise Http404 - filter_topic = get_object_or_404(Topic,id=topic_id) - query_string_segments=[] + filter_topic = get_object_or_404(Topic, id=topic_id) + query_string_segments = [] for parameter, function in parameter_functions_map.items(): search_query = request.GET.get(parameter) if search_query: - query_string_segments.append('%s=%s' % (parameter, search_query)) - query_string = '&'.join(query_string_segments) - target_url = self.get_parent().specific.reverse_subpage('redirect_first',args=(filter_topic.slug,)) + query_string_segments.append("%s=%s" % (parameter, search_query)) + query_string = "&".join(query_string_segments) + target_url = self.get_parent().specific.reverse_subpage( + "redirect_first", args=(filter_topic.slug,) + ) if query_string: - target_url = target_url + '?' + query_string + target_url = target_url + "?" + query_string return redirect(target_url) else: return super().serve(request, *args, **kwargs) def serve_preview(self, request, mode_name): - """ This is another hack to overcome the MRO issue we were seeing """ + """This is another hack to overcome the MRO issue we were seeing""" return BibliographyMixin.serve_preview(self, request, mode_name) class Meta: verbose_name = "Blog Page" + BlogPage.content_panels = Page.content_panels + [ - InlinePanel('authors', label="Authors"), - SnippetChooserPanel('author_group'), - FieldPanel('date_published'), - FieldPanel('introduction'), - StreamFieldPanel('body'), - InlinePanel('topics', label="Topics"), + InlinePanel("authors", label="Authors"), + FieldPanel("author_group"), + FieldPanel("date_published"), + FieldPanel("introduction"), + FieldPanel("body"), + InlinePanel("topics", label="Topics"), ] BlogPage.promote_panels = Page.promote_panels + PromoteMixin.panels class BlogIndexPage(RoutablePageMixin, Page): - def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.filter_topic = None @@ -316,49 +321,53 @@ def get_context(self, request): if self.filter_topic: entry_qs = entry_qs.filter(topics__topic=self.filter_topic) entry_qs = entry_qs.annotate( - coalesced_published_date=Coalesce('date_published', 'first_published_at') - ).order_by('-coalesced_published_date') - context['entries'] = entry_qs - context['topics'] = sorted(set([p.topic for p in BlogPageTopic.objects.all()]),key=lambda x:x.title) + coalesced_published_date=Coalesce("date_published", "first_published_at") + ).order_by("-coalesced_published_date") + context["entries"] = entry_qs + context["topics"] = sorted( + set([p.topic for p in BlogPageTopic.objects.all()]), key=lambda x: x.title + ) return context - @route(r'^all/$') + @route(r"^all/$") def all_entries(self, request, *args, **kwargs): return super().serve(request, *args, **kwargs) - @route(r'^([-\w]+)/all/$') + @route(r"^([-\w]+)/all/$") def filtered_entries(self, request, slug, *args, **kwargs): - self.filter_topic = get_object_or_404(Topic,slug=slug) + self.filter_topic = get_object_or_404(Topic, slug=slug) return super().serve(request, *args, **kwargs) - @route(r'^([-\w]+)/$') - @route(r'^$') + @route(r"^([-\w]+)/$") + @route(r"^$") def redirect_first(self, request, slug=None, *args, **kwargs): # IESG statements were moved under the IESG about/groups page. Queries to the # base /blog/ page that used a query string to filter for IESG statements can't # be redirected through ordinary redirection, so we're doing it here. - if request.GET.get('primary_topic')=='7': - query_string = '' - topic = request.GET.get('secondary_topic') + if request.GET.get("primary_topic") == "7": + query_string = "" + topic = request.GET.get("secondary_topic") if topic: - query_string = query_string + 'topic=' + topic - date_from = request.GET.get('date_from') + query_string = query_string + "topic=" + topic + date_from = request.GET.get("date_from") if date_from: - separator = '&' if query_string else '' - query_string = query_string + separator + 'date_from=' + date_from - date_to = request.GET.get('date_to') + separator = "&" if query_string else "" + query_string = query_string + separator + "date_from=" + date_from + date_to = request.GET.get("date_to") if date_to: - separator = '&' if query_string else '' - query_string = query_string + separator + 'date_to' + date_to - target_url = '/about/groups/iesg/statements' + separator = "&" if query_string else "" + query_string = query_string + separator + "date_to" + date_to + target_url = "/about/groups/iesg/statements" if query_string: - target_url = target_url + '?' + query_string + target_url = target_url + "?" + query_string return redirect(target_url) else: if slug: self.filter_topic = Topic.objects.filter(slug=slug).first() if not self.filter_topic: - blog_page = get_object_or_404(BlogPage.objects.prefetch_related('authors'), slug=slug) + blog_page = get_object_or_404( + BlogPage.objects.prefetch_related("authors"), slug=slug + ) return blog_page.serve(request, *args, **kwargs) blogs = ordered_live_annotated_blogs() @@ -379,7 +388,7 @@ def redirect_first(self, request, slug=None, *args, **kwargs): query_string += "%s=%s&" % (parameter, search_query) except (ValueError, ObjectDoesNotExist): pass - + if blogs: first_blog = blogs.first() @@ -388,10 +397,9 @@ def redirect_first(self, request, slug=None, *args, **kwargs): return first_blog.serve(request, *args, **kwargs) + search_fields = Page.search_fields + [] - search_fields = [] - - subpage_types = ['blog.BlogPage'] + subpage_types = ["blog.BlogPage"] class Meta: verbose_name = "Blog Index Page" diff --git a/ietf/blog/tests.py b/ietf/blog/tests.py index f01437f4..a447e5ad 100644 --- a/ietf/blog/tests.py +++ b/ietf/blog/tests.py @@ -1,10 +1,10 @@ -from django.test import TestCase from datetime import datetime, timedelta -from .models import BlogPage, BlogIndexPage -from ..home.models import HomePage +from django.test import TestCase +from wagtail.models import Page, Site -from wagtail.core.models import Page, Site +from ..home.models import HomePage +from .models import BlogIndexPage, BlogPage class BlogTests(TestCase): @@ -12,12 +12,12 @@ def setUp(self): root = Page.get_first_root_node() home = HomePage( - slug = 'homepageslug', - title = 'home page title', - heading = 'home page heading', - introduction = 'home page introduction', - request_for_comments_section_body = 'rfc section body', - working_groups_section_body = 'wg section body', + slug="homepageslug", + title="home page title", + heading="home page heading", + introduction="home page introduction", + request_for_comments_section_body="rfc section body", + working_groups_section_body="wg section body", ) root.add_child(instance=home) @@ -25,59 +25,58 @@ def setUp(self): Site.objects.all().delete() Site.objects.create( - hostname='localhost', - root_page = home, + hostname="localhost", + root_page=home, is_default_site=True, - site_name='testingsitename', + site_name="testingsitename", ) self.blog_index = BlogIndexPage( - slug = 'blog', - title = 'blog index title', + slug="blog", + title="blog index title", ) - home.add_child(instance = self.blog_index) + home.add_child(instance=self.blog_index) now = datetime.utcnow() self.otherblog = BlogPage( - slug = 'otherpost', - title = 'other title', - introduction = 'other introduction', - body = 'other body', - date_published = (now - timedelta(minutes = 10)) + slug="otherpost", + title="other title", + introduction="other introduction", + body="other body", + date_published=(now - timedelta(minutes=10)), ) - self.blog_index.add_child(instance = self.otherblog) + self.blog_index.add_child(instance=self.otherblog) self.otherblog.save self.prevblog = BlogPage( - slug = 'prevpost', - title = 'prev title', - introduction = 'prev introduction', - body = 'prev body', - date_published = (now - timedelta(minutes = 5)) + slug="prevpost", + title="prev title", + introduction="prev introduction", + body="prev body", + date_published=(now - timedelta(minutes=5)), ) - self.blog_index.add_child(instance = self.prevblog) + self.blog_index.add_child(instance=self.prevblog) self.prevblog.save() - self.blog = BlogPage( - slug = 'blogpost', - title = 'blog title', - introduction = 'blog introduction', - body = 'blog body', - first_published_at = (now + timedelta(minutes=1)) + slug="blogpost", + title="blog title", + introduction="blog introduction", + body="blog body", + first_published_at=(now + timedelta(minutes=1)), ) - self.blog_index.add_child(instance = self.blog) + self.blog_index.add_child(instance=self.blog) self.blog.save() self.nextblog = BlogPage( - slug = 'nextpost', - title = 'next title', - introduction = 'next introduction', - body = 'next body', - first_published_at = (now + timedelta(minutes=5)) + slug="nextpost", + title="next title", + introduction="next introduction", + body="next body", + first_published_at=(now + timedelta(minutes=5)), ) - self.blog_index.add_child(instance = self.nextblog) + self.blog_index.add_child(instance=self.nextblog) self.nextblog.save() def test_blog(self): diff --git a/ietf/context_processors.py b/ietf/context_processors.py index f0a77b80..e2f1e900 100644 --- a/ietf/context_processors.py +++ b/ietf/context_processors.py @@ -37,7 +37,7 @@ def global_pages(request): "BLOG_INDEX": BlogIndexPage.objects.first(), "MENU": menu(), "SECONDARY_MENU": secondary_menu(), - "BASE_URL": getattr(settings, "BASE_URL", ""), + "BASE_URL": getattr(settings, "WAGTAILADMIN_BASE_URL", ""), "DEBUG": getattr(settings, "DEBUG", ""), "FB_APP_ID": getattr(settings, "FB_APP_ID", ""), } diff --git a/ietf/events/migrations/0001_initial.py b/ietf/events/migrations/0001_initial.py index da60b6c6..d22eddf6 100644 --- a/ietf/events/migrations/0001_initial.py +++ b/ietf/events/migrations/0001_initial.py @@ -2,17 +2,18 @@ # Generated by Django 1.11.29 on 2020-04-10 18:46 from __future__ import unicode_literals -from django.db import migrations, models import django.db.models.deletion -import ietf.snippets.models import modelcluster.fields +import wagtail.blocks import wagtail.contrib.table_block.blocks -import wagtail.core.blocks -import wagtail.core.fields import wagtail.documents.blocks import wagtail.embeds.blocks +import wagtail.fields import wagtail.images.blocks import wagtail.snippets.blocks +from django.db import migrations, models + +import ietf.snippets.models class Migration(migrations.Migration): @@ -20,73 +21,384 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('wagtailcore', '0040_page_draft_title'), - ('images', '0001_initial'), - ('snippets', '0001_initial'), + ("wagtailcore", "0040_page_draft_title"), + ("images", "0001_initial"), + ("snippets", "0001_initial"), ] operations = [ migrations.CreateModel( - name='EventListingPage', + name="EventListingPage", fields=[ - ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')), - ('social_text', models.CharField(blank=True, help_text='Description of this page as it should appear when shared on social networks, or in Google results', max_length=255)), - ('introduction', models.CharField(blank=True, max_length=511)), - ('feed_image', models.ForeignKey(blank=True, help_text='This image will be used in listings and indexes across the site, if no feed image is added, the social image will be used.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.IETFImage')), - ('social_image', models.ForeignKey(blank=True, help_text="Image to appear alongside 'social text', particularly for sharing on social networks", null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.IETFImage')), + ( + "page_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="wagtailcore.Page", + ), + ), + ( + "social_text", + models.CharField( + blank=True, + help_text="Description of this page as it should appear when shared on social networks, or in Google results", + max_length=255, + ), + ), + ("introduction", models.CharField(blank=True, max_length=511)), + ( + "feed_image", + models.ForeignKey( + blank=True, + help_text="This image will be used in listings and indexes across the site, if no feed image is added, the social image will be used.", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to="images.IETFImage", + ), + ), + ( + "social_image", + models.ForeignKey( + blank=True, + help_text="Image to appear alongside 'social text', particularly for sharing on social networks", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to="images.IETFImage", + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, - bases=('wagtailcore.page', models.Model), + bases=("wagtailcore.page", models.Model), ), migrations.CreateModel( - name='EventListingPagePromotedEvent', + name="EventListingPagePromotedEvent", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('page', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='promoted_events', to='events.EventListingPage')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "page", + modelcluster.fields.ParentalKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="promoted_events", + to="events.EventListingPage", + ), + ), ], ), migrations.CreateModel( - name='EventPage', + name="EventPage", fields=[ - ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')), - ('social_text', models.CharField(blank=True, help_text='Description of this page as it should appear when shared on social networks, or in Google results', max_length=255)), - ('start_date', models.DateField(blank=True, help_text='The start date date of the event.', null=True)), - ('end_date', models.DateField(blank=True, help_text='The end date of the event.', null=True)), - ('introduction', models.CharField(help_text='The introduction for the event page. Limited to 511 characters.', max_length=200)), - ('body', wagtail.core.fields.StreamField([('heading', wagtail.core.blocks.CharBlock(icon='title')), ('paragraph', wagtail.core.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.images.blocks.ImageChooserBlock(icon='image', template='includes/imageblock.html')), ('embed', wagtail.embeds.blocks.EmbedBlock(icon='code')), ('raw_html', wagtail.core.blocks.RawHTMLBlock(icon='placeholder')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}))])), - ('venue_section_title', models.CharField(blank=True, default='Meeting venue information', max_length=255)), - ('venue', wagtail.core.fields.StreamField([('address_line', wagtail.core.blocks.CharBlock(classname='full title'))], blank=True)), - ('extras', wagtail.core.fields.StreamField([('extra', wagtail.core.blocks.CharBlock(classname='full title'))], blank=True)), - ('reservation_name', models.CharField(blank=True, max_length=255)), - ('room_rates', wagtail.core.fields.StreamField([('room_rate', wagtail.core.blocks.CharBlock(classname='full title')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}))], blank=True)), - ('reservations_open', models.DateField(blank=True, null=True)), - ('contact_details', wagtail.core.fields.StreamField([('contact_detail', wagtail.core.blocks.CharBlock(classname='full title'))], blank=True)), - ('key_details', wagtail.core.fields.StreamField([('item', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.CharBlock()), ('link_group', wagtail.core.blocks.StreamBlock([('link', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.CharBlock()), ('link_external', wagtail.core.blocks.URLBlock(required=False)), ('link_page', wagtail.core.blocks.PageChooserBlock(required=False)), ('link_document', wagtail.documents.blocks.DocumentChooserBlock(required=False))]))]))]))], blank=True)), - ('key_details_expanded', models.BooleanField(default=False, help_text='Show the key details items expanded when the page first loads')), - ('sponsors', wagtail.core.fields.StreamField([('sponsor_category', wagtail.core.blocks.StructBlock([('category_title', wagtail.core.blocks.CharBlock()), ('sponsor_group', wagtail.core.blocks.StreamBlock([('sponsor', wagtail.snippets.blocks.SnippetChooserBlock(ietf.snippets.models.Sponsor))]))]))], blank=True)), - ('listing_location', models.CharField(blank=True, help_text='Add a short location name to appear on the event listing.', max_length=255)), - ('feed_image', models.ForeignKey(blank=True, help_text='This image will be used in listings and indexes across the site, if no feed image is added, the social image will be used.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.IETFImage')), - ('main_image', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.IETFImage')), - ('social_image', models.ForeignKey(blank=True, help_text="Image to appear alongside 'social text', particularly for sharing on social networks", null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.IETFImage')), + ( + "page_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="wagtailcore.Page", + ), + ), + ( + "social_text", + models.CharField( + blank=True, + help_text="Description of this page as it should appear when shared on social networks, or in Google results", + max_length=255, + ), + ), + ( + "start_date", + models.DateField( + blank=True, + help_text="The start date date of the event.", + null=True, + ), + ), + ( + "end_date", + models.DateField( + blank=True, help_text="The end date of the event.", null=True + ), + ), + ( + "introduction", + models.CharField( + help_text="The introduction for the event page. Limited to 511 characters.", + max_length=200, + ), + ), + ( + "body", + wagtail.fields.StreamField( + [ + ("heading", wagtail.blocks.CharBlock(icon="title")), + ("paragraph", wagtail.blocks.RichTextBlock(icon="pilcrow")), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + icon="image", template="includes/imageblock.html" + ), + ), + ("embed", wagtail.embeds.blocks.EmbedBlock(icon="code")), + ( + "raw_html", + wagtail.blocks.RawHTMLBlock(icon="placeholder"), + ), + ( + "table", + wagtail.contrib.table_block.blocks.TableBlock( + table_options={"renderer": "html"} + ), + ), + ] + ), + ), + ( + "venue_section_title", + models.CharField( + blank=True, default="Meeting venue information", max_length=255 + ), + ), + ( + "venue", + wagtail.fields.StreamField( + [ + ( + "address_line", + wagtail.blocks.CharBlock(classname="full title"), + ) + ], + blank=True, + ), + ), + ( + "extras", + wagtail.fields.StreamField( + [("extra", wagtail.blocks.CharBlock(classname="full title"))], + blank=True, + ), + ), + ("reservation_name", models.CharField(blank=True, max_length=255)), + ( + "room_rates", + wagtail.fields.StreamField( + [ + ( + "room_rate", + wagtail.blocks.CharBlock(classname="full title"), + ), + ( + "table", + wagtail.contrib.table_block.blocks.TableBlock( + table_options={"renderer": "html"} + ), + ), + ], + blank=True, + ), + ), + ("reservations_open", models.DateField(blank=True, null=True)), + ( + "contact_details", + wagtail.fields.StreamField( + [ + ( + "contact_detail", + wagtail.blocks.CharBlock(classname="full title"), + ) + ], + blank=True, + ), + ), + ( + "key_details", + wagtail.fields.StreamField( + [ + ( + "item", + wagtail.blocks.StructBlock( + [ + ("title", wagtail.blocks.CharBlock()), + ( + "link_group", + wagtail.blocks.StreamBlock( + [ + ( + "link", + wagtail.blocks.StructBlock( + [ + ( + "title", + wagtail.blocks.CharBlock(), + ), + ( + "link_external", + wagtail.blocks.URLBlock( + required=False + ), + ), + ( + "link_page", + wagtail.blocks.PageChooserBlock( + required=False + ), + ), + ( + "link_document", + wagtail.documents.blocks.DocumentChooserBlock( + required=False + ), + ), + ] + ), + ) + ] + ), + ), + ] + ), + ) + ], + blank=True, + ), + ), + ( + "key_details_expanded", + models.BooleanField( + default=False, + help_text="Show the key details items expanded when the page first loads", + ), + ), + ( + "sponsors", + wagtail.fields.StreamField( + [ + ( + "sponsor_category", + wagtail.blocks.StructBlock( + [ + ("category_title", wagtail.blocks.CharBlock()), + ( + "sponsor_group", + wagtail.blocks.StreamBlock( + [ + ( + "sponsor", + wagtail.snippets.blocks.SnippetChooserBlock( + ietf.snippets.models.Sponsor + ), + ) + ] + ), + ), + ] + ), + ) + ], + blank=True, + ), + ), + ( + "listing_location", + models.CharField( + blank=True, + help_text="Add a short location name to appear on the event listing.", + max_length=255, + ), + ), + ( + "feed_image", + models.ForeignKey( + blank=True, + help_text="This image will be used in listings and indexes across the site, if no feed image is added, the social image will be used.", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to="images.IETFImage", + ), + ), + ( + "main_image", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to="images.IETFImage", + ), + ), + ( + "social_image", + models.ForeignKey( + blank=True, + help_text="Image to appear alongside 'social text', particularly for sharing on social networks", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to="images.IETFImage", + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, - bases=('wagtailcore.page', models.Model), + bases=("wagtailcore.page", models.Model), ), migrations.CreateModel( - name='EventPageHost', + name="EventPageHost", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('host', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='snippets.Sponsor')), - ('page', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='hosts', to='events.EventPage')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "host", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to="snippets.Sponsor", + ), + ), + ( + "page", + modelcluster.fields.ParentalKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="hosts", + to="events.EventPage", + ), + ), ], ), migrations.AddField( - model_name='eventlistingpagepromotedevent', - name='promoted_event', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='events.EventPage'), + model_name="eventlistingpagepromotedevent", + name="promoted_event", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to="events.EventPage", + ), ), ] diff --git a/ietf/events/migrations/0002_auto_20210325_0442.py b/ietf/events/migrations/0002_auto_20210325_0442.py index c593a35f..5a964237 100644 --- a/ietf/events/migrations/0002_auto_20210325_0442.py +++ b/ietf/events/migrations/0002_auto_20210325_0442.py @@ -1,23 +1,43 @@ # Generated by Django 2.2.16 on 2021-03-25 04:42 -from django.db import migrations +import wagtail.blocks import wagtail.contrib.table_block.blocks -import wagtail.core.blocks -import wagtail.core.fields import wagtail.embeds.blocks +import wagtail.fields import wagtail.images.blocks +from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('events', '0001_initial'), + ("events", "0001_initial"), ] operations = [ migrations.AlterField( - model_name='eventpage', - name='body', - field=wagtail.core.fields.StreamField([('heading', wagtail.core.blocks.CharBlock(icon='title')), ('paragraph', wagtail.core.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.images.blocks.ImageChooserBlock(icon='image', template='includes/imageblock.html')), ('embed', wagtail.embeds.blocks.EmbedBlock(icon='code')), ('raw_html', wagtail.core.blocks.RawHTMLBlock(icon='placeholder')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}, template='includes/tableblock.html'))]), + model_name="eventpage", + name="body", + field=wagtail.fields.StreamField( + [ + ("heading", wagtail.blocks.CharBlock(icon="title")), + ("paragraph", wagtail.blocks.RichTextBlock(icon="pilcrow")), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + icon="image", template="includes/imageblock.html" + ), + ), + ("embed", wagtail.embeds.blocks.EmbedBlock(icon="code")), + ("raw_html", wagtail.blocks.RawHTMLBlock(icon="placeholder")), + ( + "table", + wagtail.contrib.table_block.blocks.TableBlock( + table_options={"renderer": "html"}, + template="includes/tableblock.html", + ), + ), + ] + ), ), ] diff --git a/ietf/events/migrations/0004_auto_20211101_0113.py b/ietf/events/migrations/0004_auto_20211101_0113.py index 4f4f8b2e..8952e0bb 100644 --- a/ietf/events/migrations/0004_auto_20211101_0113.py +++ b/ietf/events/migrations/0004_auto_20211101_0113.py @@ -1,24 +1,45 @@ # Generated by Django 2.2.19 on 2021-11-01 01:13 -from django.db import migrations +import wagtail.blocks import wagtail.contrib.table_block.blocks -import wagtail.core.blocks -import wagtail.core.fields import wagtail.embeds.blocks +import wagtail.fields import wagtail.images.blocks import wagtailmarkdown.blocks +from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('events', '0003_auto_20210704_2343'), + ("events", "0003_auto_20210704_2343"), ] operations = [ migrations.AlterField( - model_name='eventpage', - name='body', - field=wagtail.core.fields.StreamField([('heading', wagtail.core.blocks.CharBlock(icon='title')), ('paragraph', wagtail.core.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.images.blocks.ImageChooserBlock(icon='image', template='includes/imageblock.html')), ('markdown', wagtailmarkdown.blocks.MarkdownBlock(icon='code')), ('embed', wagtail.embeds.blocks.EmbedBlock(icon='code')), ('raw_html', wagtail.core.blocks.RawHTMLBlock(icon='placeholder')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}, template='includes/tableblock.html'))]), + model_name="eventpage", + name="body", + field=wagtail.fields.StreamField( + [ + ("heading", wagtail.blocks.CharBlock(icon="title")), + ("paragraph", wagtail.blocks.RichTextBlock(icon="pilcrow")), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + icon="image", template="includes/imageblock.html" + ), + ), + ("markdown", wagtailmarkdown.blocks.MarkdownBlock(icon="code")), + ("embed", wagtail.embeds.blocks.EmbedBlock(icon="code")), + ("raw_html", wagtail.blocks.RawHTMLBlock(icon="placeholder")), + ( + "table", + wagtail.contrib.table_block.blocks.TableBlock( + table_options={"renderer": "html"}, + template="includes/tableblock.html", + ), + ), + ] + ), ), ] diff --git a/ietf/events/migrations/0005_auto_20220902_0524.py b/ietf/events/migrations/0005_auto_20220902_0524.py new file mode 100644 index 00000000..bb51e7c6 --- /dev/null +++ b/ietf/events/migrations/0005_auto_20220902_0524.py @@ -0,0 +1,57 @@ +# Generated by Django 3.2.13 on 2022-09-02 04:24 + +from django.db import migrations +import ietf.snippets.models +import wagtail.blocks +import wagtail.contrib.table_block.blocks +import wagtail.documents.blocks +import wagtail.embeds.blocks +import wagtail.fields +import wagtail.images.blocks +import wagtail.snippets.blocks +import wagtailmarkdown.blocks + + +class Migration(migrations.Migration): + + dependencies = [ + ('events', '0004_auto_20211101_0113'), + ] + + operations = [ + migrations.AlterField( + model_name='eventpage', + name='body', + field=wagtail.fields.StreamField([('heading', wagtail.blocks.CharBlock(icon='title')), ('paragraph', wagtail.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.images.blocks.ImageChooserBlock(icon='image', template='includes/imageblock.html')), ('markdown', wagtailmarkdown.blocks.MarkdownBlock(icon='code')), ('embed', wagtail.embeds.blocks.EmbedBlock(icon='code')), ('raw_html', wagtail.blocks.RawHTMLBlock(icon='placeholder')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}, template='includes/tableblock.html'))], use_json_field=True), + ), + migrations.AlterField( + model_name='eventpage', + name='contact_details', + field=wagtail.fields.StreamField([('contact_detail', wagtail.blocks.CharBlock(form_classname='full title'))], blank=True, use_json_field=True), + ), + migrations.AlterField( + model_name='eventpage', + name='extras', + field=wagtail.fields.StreamField([('extra', wagtail.blocks.CharBlock(form_classname='full title'))], blank=True, use_json_field=True), + ), + migrations.AlterField( + model_name='eventpage', + name='key_details', + field=wagtail.fields.StreamField([('item', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock()), ('link_group', wagtail.blocks.StreamBlock([('link', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock()), ('link_external', wagtail.blocks.URLBlock(required=False)), ('link_page', wagtail.blocks.PageChooserBlock(required=False)), ('link_document', wagtail.documents.blocks.DocumentChooserBlock(required=False))]))]))]))], blank=True, use_json_field=True), + ), + migrations.AlterField( + model_name='eventpage', + name='room_rates', + field=wagtail.fields.StreamField([('room_rate', wagtail.blocks.CharBlock(form_classname='full title')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}))], blank=True, use_json_field=True), + ), + migrations.AlterField( + model_name='eventpage', + name='sponsors', + field=wagtail.fields.StreamField([('sponsor_category', wagtail.blocks.StructBlock([('category_title', wagtail.blocks.CharBlock()), ('sponsor_group', wagtail.blocks.StreamBlock([('sponsor', wagtail.snippets.blocks.SnippetChooserBlock(ietf.snippets.models.Sponsor))]))]))], blank=True, use_json_field=True), + ), + migrations.AlterField( + model_name='eventpage', + name='venue', + field=wagtail.fields.StreamField([('address_line', wagtail.blocks.CharBlock(form_classname='full title'))], blank=True, use_json_field=True), + ), + ] diff --git a/ietf/events/models.py b/ietf/events/models.py index 9be4bd00..1e8f80a3 100644 --- a/ietf/events/models.py +++ b/ietf/events/models.py @@ -1,36 +1,32 @@ from datetime import datetime from django.db import models - from modelcluster.fields import ParentalKey - -from wagtail.core.models import Page -from wagtail.images.edit_handlers import ImageChooserPanel -from wagtail.core.fields import StreamField -from wagtail.core import blocks -from wagtail.contrib.table_block.blocks import TableBlock -from wagtail.admin.edit_handlers import ( - FieldPanel, StreamFieldPanel, PageChooserPanel, InlinePanel +from wagtail import blocks +from wagtail.admin.panels import ( + FieldPanel, + InlinePanel, ) -from wagtail.snippets.edit_handlers import ( - SnippetChooserPanel +from wagtail.blocks import ( + CharBlock, + PageChooserBlock, + StreamBlock, + StructBlock, + URLBlock, ) +from wagtail.contrib.table_block.blocks import TableBlock +from wagtail.documents.blocks import DocumentChooserBlock +from wagtail.fields import StreamField +from wagtail.models import Page from wagtail.snippets.blocks import SnippetChooserBlock -from wagtail.core.blocks import ( - CharBlock, URLBlock, PageChooserBlock, - StructBlock, StreamBlock -) -from wagtail.documents.blocks import ( - DocumentChooserBlock -) from ..snippets.models import Sponsor -from ..utils.models import PromoteMixin from ..utils.blocks import StandardBlock - +from ..utils.models import PromoteMixin # Links + class LinkBlock(StructBlock): title = CharBlock() link_external = URLBlock(required=False) @@ -39,13 +35,13 @@ class LinkBlock(StructBlock): def get_context(self, value, parent_context=None): context = super(LinkBlock, self).get_context(value, parent_context) - if value['link_page']: - link = value['link_page'].url - elif value['link_document']: - link = value['link_document'].url + if value["link_page"]: + link = value["link_page"].url + elif value["link_document"]: + link = value["link_document"].url else: - link = value['link_external'] - context.update(link=link, title=value['title']) + link = value["link_external"] + context.update(link=link, title=value["title"]) return context class Meta: @@ -63,6 +59,7 @@ class NamedLinkGroupBlock(StructBlock): # Sponsors + class SponsorGroupBlock(StreamBlock): sponsor = SnippetChooserBlock(Sponsor) @@ -74,25 +71,23 @@ class SponsorCategoryBlock(StructBlock): # Hosts + class EventPageHost(models.Model): - page = ParentalKey( - 'events.EventPage', - related_name='hosts' - ) + page = ParentalKey("events.EventPage", related_name="hosts") host = models.ForeignKey( - 'snippets.Sponsor', - null=True, blank=True, + "snippets.Sponsor", + null=True, + blank=True, on_delete=models.SET_NULL, - related_name='+' + related_name="+", ) - panels = [ - SnippetChooserPanel('host') - ] + panels = [FieldPanel("host")] # Pages + class EventPage(Page, PromoteMixin): """ A page that describes the IETF's events. @@ -101,141 +96,136 @@ class EventPage(Page, PromoteMixin): single block streamfields to allow items to be added and removed and to allow arbitrary nesting of Panels. """ + start_date = models.DateField( - null=True, blank=True, - help_text="The start date date of the event." + null=True, blank=True, help_text="The start date date of the event." ) end_date = models.DateField( - null=True, blank=True, - help_text="The end date of the event." + null=True, blank=True, help_text="The end date of the event." ) introduction = models.CharField( max_length=511, - help_text="The introduction for the event page. " - "Limited to 511 characters." + help_text="The introduction for the event page. " "Limited to 511 characters.", ) - body = StreamField(StandardBlock()) + body = StreamField(StandardBlock(), use_json_field=True) main_image = models.ForeignKey( - 'images.IETFImage', - null=True, blank=True, + "images.IETFImage", + null=True, + blank=True, on_delete=models.SET_NULL, - related_name='+', + related_name="+", ) venue_section_title = models.CharField( - max_length=255, - default="Meeting venue information", - blank=True + max_length=255, default="Meeting venue information", blank=True + ) + venue = StreamField( + [("address_line", blocks.CharBlock(classname="full title"))], blank=True, use_json_field=True + ) + extras = StreamField( + [("extra", blocks.CharBlock(classname="full title"))], blank=True, use_json_field=True ) - venue = StreamField([ - ('address_line', blocks.CharBlock(classname="full title")) - ], blank=True) - extras = StreamField([ - ('extra', blocks.CharBlock(classname="full title")) - ], blank=True) reservation_name = models.CharField(max_length=255, blank=True) - room_rates = StreamField([ - ('room_rate', blocks.CharBlock(classname="full title")), - ('table', TableBlock(table_options={'renderer': 'html'})) - ], blank=True) - reservations_open = models.DateField( - null=True, blank=True + room_rates = StreamField( + [ + ("room_rate", blocks.CharBlock(classname="full title")), + ("table", TableBlock(table_options={"renderer": "html"})), + ], + blank=True, + use_json_field=True, + ) + reservations_open = models.DateField(null=True, blank=True) + contact_details = StreamField( + [("contact_detail", blocks.CharBlock(classname="full title"))], blank=True, use_json_field=True ) - contact_details = StreamField([ - ('contact_detail', blocks.CharBlock(classname="full title")) - ], blank=True) - key_details = StreamField([ - ('item', NamedLinkGroupBlock()) - ], blank=True) + key_details = StreamField([("item", NamedLinkGroupBlock())], blank=True, use_json_field=True) key_details_expanded = models.BooleanField( default=False, help_text="Show the key details items expanded when the page first loads", ) - sponsors = StreamField([ - ('sponsor_category', SponsorCategoryBlock()) - ], blank=True) + sponsors = StreamField([("sponsor_category", SponsorCategoryBlock())], blank=True, use_json_field=True) listing_location = models.CharField( max_length=255, blank=True, - help_text="Add a short location name to appear on the event listing." + help_text="Add a short location name to appear on the event listing.", ) @property def siblings(self): - return self.get_siblings().live().public().filter( - show_in_menus=True).specific() + return self.get_siblings().live().public().filter(show_in_menus=True).specific() EventPage.content_panels = Page.content_panels + [ - FieldPanel('start_date'), - FieldPanel('end_date'), - FieldPanel('introduction'), - StreamFieldPanel('body'), - ImageChooserPanel('main_image'), - FieldPanel('venue_section_title'), - StreamFieldPanel('venue'), - StreamFieldPanel('extras'), - FieldPanel('reservation_name'), - StreamFieldPanel('room_rates'), - FieldPanel('reservations_open'), - StreamFieldPanel('contact_details'), - - FieldPanel('key_details_expanded'), - StreamFieldPanel('key_details'), - InlinePanel('hosts', label="Hosts"), - StreamFieldPanel('sponsors') + FieldPanel("start_date"), + FieldPanel("end_date"), + FieldPanel("introduction"), + FieldPanel("body"), + FieldPanel("main_image"), + FieldPanel("venue_section_title"), + FieldPanel("venue"), + FieldPanel("extras"), + FieldPanel("reservation_name"), + FieldPanel("room_rates"), + FieldPanel("reservations_open"), + FieldPanel("contact_details"), + FieldPanel("key_details_expanded"), + FieldPanel("key_details"), + InlinePanel("hosts", label="Hosts"), + FieldPanel("sponsors"), ] -EventPage.promote_panels = Page.promote_panels + PromoteMixin.panels + [ - FieldPanel('listing_location') -] +EventPage.promote_panels = ( + Page.promote_panels + PromoteMixin.panels + [FieldPanel("listing_location")] +) class EventListingPagePromotedEvent(models.Model): - page = ParentalKey( - 'events.EventListingPage', - related_name='promoted_events' - ) + page = ParentalKey("events.EventListingPage", related_name="promoted_events") promoted_event = models.ForeignKey( - 'events.EventPage', - related_name='+', + "events.EventPage", + related_name="+", on_delete=models.CASCADE, ) - panels = [ - PageChooserPanel('promoted_event') - ] + panels = [FieldPanel("promoted_event")] class EventListingPage(Page, PromoteMixin): - introduction = models.CharField( - blank=True, - max_length=511 - ) + introduction = models.CharField(blank=True, max_length=511) + @property def upcoming_events(self): - return EventPage.objects.filter( - end_date__gte=datetime.today() - ).descendant_of(self).live().exclude( - pk__in=self.promoted_events.all().values_list( - 'promoted_event__pk', flat=True + return ( + EventPage.objects.filter(end_date__gte=datetime.today()) + .descendant_of(self) + .live() + .exclude( + pk__in=self.promoted_events.all().values_list( + "promoted_event__pk", flat=True + ) ) - ).order_by('start_date') + .order_by("start_date") + ) @property def past_events(self): - return EventPage.objects.filter( - end_date__lt=datetime.today() - ).descendant_of(self).live().exclude( - pk__in=self.promoted_events.all().values_list( - 'promoted_event__pk', flat=True + return ( + EventPage.objects.filter(end_date__lt=datetime.today()) + .descendant_of(self) + .live() + .exclude( + pk__in=self.promoted_events.all().values_list( + "promoted_event__pk", flat=True + ) ) - ).order_by('-start_date') + .order_by("-start_date") + ) + EventListingPage.content_panels = Page.content_panels + [ - FieldPanel('introduction'), - InlinePanel('promoted_events', label="Promoted Events") + FieldPanel("introduction"), + InlinePanel("promoted_events", label="Promoted Events"), ] EventListingPage.promote_panels = Page.promote_panels + PromoteMixin.panels diff --git a/ietf/events/tests.py b/ietf/events/tests.py index b7cd2194..92f779c9 100644 --- a/ietf/events/tests.py +++ b/ietf/events/tests.py @@ -1,23 +1,22 @@ from django.test import TestCase +from wagtail.models import Page, Site -from .models import EventListingPage, EventPage from ..home.models import HomePage +from .models import EventListingPage, EventPage -from wagtail.core.models import Page, Site class EventPageTests(TestCase): - def test_event_page(self): root = Page.get_first_root_node() home = HomePage( - slug = 'homepageslug', - title = 'home page title', - heading = 'home page heading', - introduction = 'home page introduction', - request_for_comments_section_body = 'rfc section body', - working_groups_section_body = 'wg section body', + slug="homepageslug", + title="home page title", + heading="home page heading", + introduction="home page introduction", + request_for_comments_section_body="rfc section body", + working_groups_section_body="wg section body", ) root.add_child(instance=home) @@ -25,25 +24,25 @@ def test_event_page(self): Site.objects.all().delete() Site.objects.create( - hostname='localhost', - root_page = home, + hostname="localhost", + root_page=home, is_default_site=True, - site_name='testingsitename', + site_name="testingsitename", ) eventlisting = EventListingPage( - slug = 'eventlisting', - title = 'event listing page title', - introduction = 'event listing page introduction' + slug="eventlisting", + title="event listing page title", + introduction="event listing page introduction", ) - home.add_child(instance = eventlisting) + home.add_child(instance=eventlisting) eventpage = EventPage( - slug = 'event', - title = 'event title', - introduction = 'event introduction', + slug="event", + title="event title", + introduction="event introduction", ) - eventlisting.add_child(instance = eventpage) + eventlisting.add_child(instance=eventpage) rindex = self.client.get(path=eventlisting.url) self.assertEqual(rindex.status_code, 200) diff --git a/ietf/forms/migrations/0001_initial.py b/ietf/forms/migrations/0001_initial.py index 3058b0de..c7b6f257 100644 --- a/ietf/forms/migrations/0001_initial.py +++ b/ietf/forms/migrations/0001_initial.py @@ -2,10 +2,10 @@ # Generated by Django 1.11.29 on 2020-04-10 18:46 from __future__ import unicode_literals -from django.db import migrations, models import django.db.models.deletion import modelcluster.fields -import wagtail.core.fields +import wagtail.fields +from django.db import migrations, models class Migration(migrations.Migration): @@ -13,45 +13,139 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('wagtailcore', '0040_page_draft_title'), + ("wagtailcore", "0040_page_draft_title"), ] operations = [ migrations.CreateModel( - name='FormField', + name="FormField", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('sort_order', models.IntegerField(blank=True, editable=False, null=True)), - ('label', models.CharField(help_text='The label of the form field', max_length=255, verbose_name='label')), - ('field_type', models.CharField(choices=[('singleline', 'Single line text'), ('multiline', 'Multi-line text'), ('email', 'Email'), ('number', 'Number'), ('url', 'URL'), ('checkbox', 'Checkbox'), ('checkboxes', 'Checkboxes'), ('dropdown', 'Drop down'), ('multiselect', 'Multiple select'), ('radio', 'Radio buttons'), ('date', 'Date'), ('datetime', 'Date/time'), ('hidden', 'Hidden field')], max_length=16, verbose_name='field type')), - ('required', models.BooleanField(default=True, verbose_name='required')), - ('choices', models.TextField(blank=True, help_text='Comma separated list of choices. Only applicable in checkboxes, radio and dropdown.', verbose_name='choices')), - ('default_value', models.CharField(blank=True, help_text='Default value. Comma separated values supported for checkboxes.', max_length=255, verbose_name='default value')), - ('help_text', models.CharField(blank=True, max_length=255, verbose_name='help text')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "sort_order", + models.IntegerField(blank=True, editable=False, null=True), + ), + ( + "label", + models.CharField( + help_text="The label of the form field", + max_length=255, + verbose_name="label", + ), + ), + ( + "field_type", + models.CharField( + choices=[ + ("singleline", "Single line text"), + ("multiline", "Multi-line text"), + ("email", "Email"), + ("number", "Number"), + ("url", "URL"), + ("checkbox", "Checkbox"), + ("checkboxes", "Checkboxes"), + ("dropdown", "Drop down"), + ("multiselect", "Multiple select"), + ("radio", "Radio buttons"), + ("date", "Date"), + ("datetime", "Date/time"), + ("hidden", "Hidden field"), + ], + max_length=16, + verbose_name="field type", + ), + ), + ( + "required", + models.BooleanField(default=True, verbose_name="required"), + ), + ( + "choices", + models.TextField( + blank=True, + help_text="Comma separated list of choices. Only applicable in checkboxes, radio and dropdown.", + verbose_name="choices", + ), + ), + ( + "default_value", + models.CharField( + blank=True, + help_text="Default value. Comma separated values supported for checkboxes.", + max_length=255, + verbose_name="default value", + ), + ), + ( + "help_text", + models.CharField( + blank=True, max_length=255, verbose_name="help text" + ), + ), ], options={ - 'ordering': ['sort_order'], - 'abstract': False, + "ordering": ["sort_order"], + "abstract": False, }, ), migrations.CreateModel( - name='FormPage', + name="FormPage", fields=[ - ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')), - ('to_address', models.CharField(blank=True, help_text='Optional - form submissions will be emailed to these addresses. Separate multiple addresses by comma.', max_length=255, verbose_name='to address')), - ('from_address', models.CharField(blank=True, max_length=255, verbose_name='from address')), - ('subject', models.CharField(blank=True, max_length=255, verbose_name='subject')), - ('intro', wagtail.core.fields.RichTextField(blank=True)), - ('thank_you_text', wagtail.core.fields.RichTextField(blank=True)), + ( + "page_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="wagtailcore.Page", + ), + ), + ( + "to_address", + models.CharField( + blank=True, + help_text="Optional - form submissions will be emailed to these addresses. Separate multiple addresses by comma.", + max_length=255, + verbose_name="to address", + ), + ), + ( + "from_address", + models.CharField( + blank=True, max_length=255, verbose_name="from address" + ), + ), + ( + "subject", + models.CharField( + blank=True, max_length=255, verbose_name="subject" + ), + ), + ("intro", wagtail.fields.RichTextField(blank=True)), + ("thank_you_text", wagtail.fields.RichTextField(blank=True)), ], options={ - 'abstract': False, + "abstract": False, }, - bases=('wagtailcore.page',), + bases=("wagtailcore.page",), ), migrations.AddField( - model_name='formfield', - name='page', - field=modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='form_fields', to='forms.FormPage'), + model_name="formfield", + name="page", + field=modelcluster.fields.ParentalKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="form_fields", + to="forms.FormPage", + ), ), ] diff --git a/ietf/forms/migrations/0003_auto_20220722_0302.py b/ietf/forms/migrations/0003_auto_20220722_0302.py new file mode 100644 index 00000000..abee178c --- /dev/null +++ b/ietf/forms/migrations/0003_auto_20220722_0302.py @@ -0,0 +1,34 @@ +# Generated by Django 3.2.13 on 2022-07-22 02:02 + +from django.db import migrations, models +import wagtail.contrib.forms.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('forms', '0002_formfield_clean_name'), + ] + + operations = [ + migrations.AlterField( + model_name='formfield', + name='choices', + field=models.TextField(blank=True, help_text='Comma or new line separated list of choices. Only applicable in checkboxes, radio and dropdown.', verbose_name='choices'), + ), + migrations.AlterField( + model_name='formfield', + name='default_value', + field=models.TextField(blank=True, help_text='Default value. Comma or new line separated values supported for checkboxes.', verbose_name='default value'), + ), + migrations.AlterField( + model_name='formpage', + name='from_address', + field=models.EmailField(blank=True, max_length=255, verbose_name='from address'), + ), + migrations.AlterField( + model_name='formpage', + name='to_address', + field=models.CharField(blank=True, help_text='Optional - form submissions will be emailed to these addresses. Separate multiple addresses by comma.', max_length=255, validators=[wagtail.contrib.forms.models.validate_to_address], verbose_name='to address'), + ), + ] diff --git a/ietf/forms/migrations/0004_convert_unicode_to_text.py b/ietf/forms/migrations/0004_convert_unicode_to_text.py new file mode 100644 index 00000000..1266ca54 --- /dev/null +++ b/ietf/forms/migrations/0004_convert_unicode_to_text.py @@ -0,0 +1,20 @@ + +from django.db import migrations, models + +def convert_unicode_to_text(apps, schema_editor): + if apps.is_installed('wagtailforms'): + Submissions = apps.get_model('wagtailforms', 'formsubmission') + for submission in Submissions.objects.all(): + submission.form_data = str(submission.form_data.replace('\\u0000','')) + submission.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('forms', '0003_auto_20220722_0302'), + ] + + operations = [ + migrations.RunPython(convert_unicode_to_text) + ] diff --git a/ietf/forms/models.py b/ietf/forms/models.py index 508b6586..44c88e19 100644 --- a/ietf/forms/models.py +++ b/ietf/forms/models.py @@ -3,9 +3,9 @@ from django.contrib import messages from django.views.defaults import server_error from modelcluster.fields import ParentalKey -from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel +from wagtail.admin.panels import FieldPanel, InlinePanel, MultiFieldPanel from wagtail.contrib.forms.models import AbstractEmailForm, AbstractFormField -from wagtail.core.fields import RichTextField +from wagtail.fields import RichTextField from ietf.views import server_error diff --git a/ietf/forms/templatetags/form_tags.py b/ietf/forms/templatetags/form_tags.py index 5f6fcd28..cbcb027a 100644 --- a/ietf/forms/templatetags/form_tags.py +++ b/ietf/forms/templatetags/form_tags.py @@ -1,6 +1,5 @@ -from wagtail.core.utils import camelcase_to_underscore - from django import template +from wagtail.coreutils import camelcase_to_underscore register = template.Library() @@ -14,16 +13,17 @@ def fieldtype(bound_field): def widgettype(bound_field): return camelcase_to_underscore(bound_field.field.widget.__class__.__name__) -@register.filter(name='add_attr') + +@register.filter(name="add_attr") def add_attr(bound_field, value): attrs = {} - definition = value.split(',') + definition = value.split(",") for d in definition: - if ':' not in d: - attrs['class'] = d + if ":" not in d: + attrs["class"] = d else: - key, val = d.split(':') + key, val = d.split(":") attrs[key] = val return bound_field.as_widget(attrs=attrs) diff --git a/ietf/forms/tests.py b/ietf/forms/tests.py index 5f603a66..d114e113 100644 --- a/ietf/forms/tests.py +++ b/ietf/forms/tests.py @@ -1,5 +1,5 @@ from django.test import TestCase -from wagtail.core.models import Page, Site +from wagtail.models import Page, Site from ..home.models import HomePage from .models import FormPage diff --git a/ietf/glossary/models.py b/ietf/glossary/models.py index c717e991..6f3bdcfb 100644 --- a/ietf/glossary/models.py +++ b/ietf/glossary/models.py @@ -1,45 +1,40 @@ from django.db import models - -from wagtail.core.models import Page, Orderable -from wagtail.search.backends import get_search_backend -from wagtail.admin.edit_handlers import ( - FieldPanel, InlinePanel -) -from wagtail.snippets.edit_handlers import ( - SnippetChooserPanel -) - from modelcluster.fields import ParentalKey +from wagtail.admin.panels import FieldPanel, InlinePanel +from wagtail.models import Orderable, Page +from wagtail.search.backends import get_search_backend from ..snippets.models import GlossaryItem from ..utils.models import PromoteMixin, RelatedLink class GlossaryPageRelatedLink(Orderable, RelatedLink): - page = ParentalKey('glossary.GlossaryPage', related_name='related_links') + page = ParentalKey("glossary.GlossaryPage", related_name="related_links") class GlossaryPage(Page, PromoteMixin): """ This page lists all :models:`snippets.GlossaryItem` snippets. """ + introduction = models.CharField( blank=True, max_length=511, - help_text="The page introduction text. You can only use 511 " - "characters." + help_text="The page introduction text. You can only use 511 " "characters.", ) call_to_action = models.ForeignKey( - 'snippets.CallToAction', - null=True, blank=True, + "snippets.CallToAction", + null=True, + blank=True, on_delete=models.SET_NULL, - related_name='+' + related_name="+", ) mailing_list_signup = models.ForeignKey( - 'snippets.MailingListSignup', + "snippets.MailingListSignup", null=True, - blank=True, on_delete=models.SET_NULL, - related_name='+' + blank=True, + on_delete=models.SET_NULL, + related_name="+", ) @property @@ -50,28 +45,26 @@ def get_context(self, request, *args, **kwargs): context = super(GlossaryPage, self).get_context(request, *args, **kwargs) glossary_items = GlossaryItem.objects.all() - if request.GET.get('query'): + if request.GET.get("query"): s = get_search_backend() - glossary_items = s.search( - request.GET.get('query'), glossary_items - ) + glossary_items = s.search(request.GET.get("query"), glossary_items) - context['glossary_items'] = {} + context["glossary_items"] = {} for item in glossary_items: - if item.title[0:1].upper() not in context['glossary_items'].keys(): - context['glossary_items'][item.title[0:1].upper()] = [item] + if item.title[0:1].upper() not in context["glossary_items"].keys(): + context["glossary_items"][item.title[0:1].upper()] = [item] else: - context['glossary_items'][item.title[0:1].upper()].append(item) - context['search_query'] = request.GET.get('query') + context["glossary_items"][item.title[0:1].upper()].append(item) + context["search_query"] = request.GET.get("query") return context GlossaryPage.content_panels = Page.content_panels + [ - FieldPanel('introduction'), - InlinePanel('related_links', label="Related Links"), - SnippetChooserPanel('call_to_action'), - SnippetChooserPanel('mailing_list_signup'), + FieldPanel("introduction"), + InlinePanel("related_links", label="Related Links"), + FieldPanel("call_to_action"), + FieldPanel("mailing_list_signup"), ] GlossaryPage.promote_panels = Page.promote_panels + PromoteMixin.panels diff --git a/ietf/glossary/tests.py b/ietf/glossary/tests.py index 28b623e3..9f2d9a6f 100644 --- a/ietf/glossary/tests.py +++ b/ietf/glossary/tests.py @@ -1,23 +1,22 @@ from django.test import TestCase +from wagtail.models import Page, Site -from .models import GlossaryPage from ..home.models import HomePage +from .models import GlossaryPage -from wagtail.core.models import Page, Site class GlossaryPageTests(TestCase): - def test_glossary_page(self): root = Page.get_first_root_node() home = HomePage( - slug = 'homepageslug', - title = 'home page title', - heading = 'home page heading', - introduction = 'home page introduction', - request_for_comments_section_body = 'rfc section body', - working_groups_section_body = 'wg section body', + slug="homepageslug", + title="home page title", + heading="home page heading", + introduction="home page introduction", + request_for_comments_section_body="rfc section body", + working_groups_section_body="wg section body", ) root.add_child(instance=home) @@ -25,18 +24,18 @@ def test_glossary_page(self): Site.objects.all().delete() Site.objects.create( - hostname='localhost', - root_page = home, + hostname="localhost", + root_page=home, is_default_site=True, - site_name='testingsitename', + site_name="testingsitename", ) glossary = GlossaryPage( - slug = 'glossary', - title = 'glossary title', - introduction = 'glossary introduction', + slug="glossary", + title="glossary title", + introduction="glossary introduction", ) - home.add_child(instance = glossary) + home.add_child(instance=glossary) r = self.client.get(path=glossary.url) self.assertEqual(r.status_code, 200) diff --git a/ietf/home/models.py b/ietf/home/models.py index 3275d3cb..7e3fc4ea 100644 --- a/ietf/home/models.py +++ b/ietf/home/models.py @@ -1,82 +1,89 @@ from __future__ import unicode_literals + from datetime import datetime from django.db import models from django.db.models.expressions import RawSQL - from modelcluster.fields import ParentalKey - -from wagtail.core.models import Page -from wagtail.images.edit_handlers import ImageChooserPanel -from wagtail.search import index -from wagtail.admin.edit_handlers import ( - FieldPanel, MultiFieldPanel, InlinePanel, PageChooserPanel +from wagtail.admin.panels import ( + FieldPanel, + InlinePanel, + MultiFieldPanel, ) -from wagtail.snippets.edit_handlers import SnippetChooserPanel +from wagtail.models import Page +from wagtail.search import index + +from ietf.blog.models import BlogIndexPage, BlogPage +from ..events.models import EventListingPage, EventPage +from ..topics.models import PrimaryTopicPage, TopicIndexPage from ..utils.models import RelatedLink -from ..topics.models import TopicIndexPage, PrimaryTopicPage -from ..events.models import EventPage, EventListingPage -from ietf.blog.models import BlogPage, BlogIndexPage class RequestForCommentsSectionLinks(RelatedLink): - page = ParentalKey('home.HomePage', - related_name='request_for_comments_section_links') + page = ParentalKey( + "home.HomePage", related_name="request_for_comments_section_links" + ) class WorkingGroupsSectionLinks(RelatedLink): - page = ParentalKey('home.HomePage', - related_name='working_groups_section_links') + page = ParentalKey("home.HomePage", related_name="working_groups_section_links") class HomePage(Page): heading = models.CharField(max_length=255) introduction = models.CharField(max_length=255) main_image = models.ForeignKey( - 'images.IETFImage', - null=True, blank=True, + "images.IETFImage", + null=True, + blank=True, on_delete=models.SET_NULL, - related_name='+', + related_name="+", ) button_text = models.CharField(max_length=255, blank=True) button_link = models.ForeignKey( - 'wagtailcore.Page', - null=True, blank=True, + "wagtailcore.Page", + null=True, + blank=True, on_delete=models.SET_NULL, - related_name='+' + related_name="+", ) request_for_comments_section_body = models.CharField(max_length=500) highlighted_request_for_comment = models.ForeignKey( - 'snippets.RFC', - null=True, blank=True, + "snippets.RFC", + null=True, + blank=True, on_delete=models.SET_NULL, - related_name='+' + related_name="+", ) working_groups_section_body = models.CharField(max_length=500) highlighted_working_group = models.ForeignKey( - 'snippets.WorkingGroup', - null=True, blank=True, + "snippets.WorkingGroup", + null=True, + blank=True, on_delete=models.SET_NULL, - related_name='+' + related_name="+", ) call_to_action = models.ForeignKey( - 'snippets.CallToAction', - null=True, blank=True, + "snippets.CallToAction", + null=True, + blank=True, on_delete=models.SET_NULL, - related_name='+' + related_name="+", ) search_fields = Page.search_fields + [ - index.SearchField('heading'), - index.SearchField('request_for_comments_section_body'), - index.SearchField('working_groups_section_body'), + index.SearchField("heading"), + index.SearchField("request_for_comments_section_body"), + index.SearchField("working_groups_section_body"), ] - def topic_index(self,): + def topic_index( + self, + ): return TopicIndexPage.objects.live().first() def primary_topics(self): @@ -86,9 +93,11 @@ def primary_topics(self): return [] def upcoming_events(self): - return EventPage.objects.filter( - end_date__gte=datetime.today() - ).live().order_by('start_date')[:2] + return ( + EventPage.objects.filter(end_date__gte=datetime.today()) + .live() + .order_by("start_date")[:2] + ) def event_index(self): return EventListingPage.objects.live().first() @@ -97,28 +106,43 @@ def blog_index(self): return BlogIndexPage.objects.live().first() def blogs(self): - return BlogPage.objects.live() \ - .annotate(date_sql=RawSQL('CASE WHEN (date_published IS NOT NULL) THEN date_published ELSE first_published_at END', ()))\ - .order_by('-date_sql')\ - [:2] + return ( + BlogPage.objects.live() + .annotate( + date_sql=RawSQL( + "CASE WHEN (date_published IS NOT NULL) THEN date_published ELSE first_published_at END", + (), + ) + ) + .order_by("-date_sql")[:2] + ) content_panels = Page.content_panels + [ - MultiFieldPanel([ - FieldPanel('heading'), - FieldPanel('introduction'), - FieldPanel('button_text'), - ImageChooserPanel('main_image'), - PageChooserPanel('button_link') - ], "Header"), - MultiFieldPanel([ - FieldPanel('request_for_comments_section_body'), - SnippetChooserPanel('highlighted_request_for_comment'), - InlinePanel('request_for_comments_section_links', label="Link"), - ], "Request For Comments Section"), - MultiFieldPanel([ - FieldPanel('working_groups_section_body'), - SnippetChooserPanel('highlighted_working_group'), - InlinePanel('working_groups_section_links', label="Link"), - ], "Working Groups Section"), - SnippetChooserPanel('call_to_action') + MultiFieldPanel( + [ + FieldPanel("heading"), + FieldPanel("introduction"), + FieldPanel("button_text"), + FieldPanel("main_image"), + FieldPanel("button_link"), + ], + "Header", + ), + MultiFieldPanel( + [ + FieldPanel("request_for_comments_section_body"), + FieldPanel("highlighted_request_for_comment"), + InlinePanel("request_for_comments_section_links", label="Link"), + ], + "Request For Comments Section", + ), + MultiFieldPanel( + [ + FieldPanel("working_groups_section_body"), + FieldPanel("highlighted_working_group"), + InlinePanel("working_groups_section_links", label="Link"), + ], + "Working Groups Section", + ), + FieldPanel("call_to_action"), ] diff --git a/ietf/home/tests.py b/ietf/home/tests.py index 018b1978..a37be46a 100644 --- a/ietf/home/tests.py +++ b/ietf/home/tests.py @@ -1,24 +1,23 @@ from django.test import TestCase +from wagtail.models import Page, Site -from .models import HomePage, RequestForCommentsSectionLinks, WorkingGroupsSectionLinks -from ..blog.models import BlogPage, BlogIndexPage +from ..blog.models import BlogIndexPage, BlogPage from ..snippets.models import RFC, WorkingGroup +from .models import HomePage, RequestForCommentsSectionLinks, WorkingGroupsSectionLinks -from wagtail.core.models import Page, Site class HomeTests(TestCase): - def test_homepage(self): root = Page.get_first_root_node() home = HomePage( - slug = 'homepageslug', - title = 'home page title', - heading = 'home page heading', - introduction = 'home page introduction', - request_for_comments_section_body = 'rfc section body', - working_groups_section_body = 'wg section body', + slug="homepageslug", + title="home page title", + heading="home page heading", + introduction="home page introduction", + request_for_comments_section_body="rfc section body", + working_groups_section_body="wg section body", ) root.add_child(instance=home) @@ -28,27 +27,27 @@ def test_homepage(self): Site.objects.all().delete() Site.objects.create( - hostname='localhost', - root_page = home, + hostname="localhost", + root_page=home, is_default_site=True, - site_name='testingsitename', + site_name="testingsitename", ) blogindex = BlogIndexPage( - slug = 'blog', - title = 'blog index title', + slug="blog", + title="blog index title", ) - home.add_child(instance = blogindex) + home.add_child(instance=blogindex) blog = BlogPage( - slug = 'blogpost', - title = 'blog title', - introduction = 'blog introduction', - body = 'blog body' + slug="blogpost", + title="blog title", + introduction="blog introduction", + body="blog body", ) - blogindex.add_child(instance = blog) + blogindex.add_child(instance=blog) - home.button_text = 'blog button text' + home.button_text = "blog button text" home.button_link = blog home.save() @@ -60,7 +59,6 @@ def test_homepage(self): self.assertIn(home.button_text.encode(), r.content) self.assertIn(('href="%s"' % blog.url).encode(), r.content) - # other_page = BlogPage.objects.create( # introduction = 'blog introduction', # title='blog title', @@ -77,6 +75,3 @@ def test_homepage(self): # r = self.client.get(url=home.url_path) # self.assertEqual(r.status_code, 200) - - - diff --git a/ietf/home/wagtail_hooks.py b/ietf/home/wagtail_hooks.py index 72aada2d..76cabb98 100644 --- a/ietf/home/wagtail_hooks.py +++ b/ietf/home/wagtail_hooks.py @@ -1,14 +1,13 @@ from django.urls import reverse - -from wagtail.core import hooks +from wagtail import hooks from wagtail.admin.menu import MenuItem -@hooks.register('register_admin_menu_item') +@hooks.register("register_admin_menu_item") def register_resource_menu_item(): - return MenuItem('Documentation', - reverse('django-admindocs-docroot'), - classnames='icon icon-folder-inverse', - order=10000) - - + return MenuItem( + "Documentation", + reverse("django-admindocs-docroot"), + classnames="icon icon-folder-inverse", + order=10000, + ) diff --git a/ietf/iesg_statement/migrations/0001_initial.py b/ietf/iesg_statement/migrations/0001_initial.py index 4964aec3..3bb27277 100644 --- a/ietf/iesg_statement/migrations/0001_initial.py +++ b/ietf/iesg_statement/migrations/0001_initial.py @@ -2,15 +2,15 @@ # Generated by Django 1.11.29 on 2020-04-10 18:46 from __future__ import unicode_literals -from django.db import migrations, models import django.db.models.deletion import modelcluster.fields +import wagtail.blocks import wagtail.contrib.routable_page.models import wagtail.contrib.table_block.blocks -import wagtail.core.blocks -import wagtail.core.fields import wagtail.embeds.blocks +import wagtail.fields import wagtail.images.blocks +from django.db import migrations, models class Migration(migrations.Migration): @@ -18,45 +18,161 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('wagtailcore', '0040_page_draft_title'), - ('images', '0001_initial'), - ('snippets', '0001_initial'), + ("wagtailcore", "0040_page_draft_title"), + ("images", "0001_initial"), + ("snippets", "0001_initial"), ] operations = [ migrations.CreateModel( - name='IESGStatementIndexPage', + name="IESGStatementIndexPage", fields=[ - ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')), + ( + "page_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="wagtailcore.Page", + ), + ), ], options={ - 'verbose_name': 'IESG Statements Index Page', + "verbose_name": "IESG Statements Index Page", }, - bases=(wagtail.contrib.routable_page.models.RoutablePageMixin, 'wagtailcore.page'), + bases=( + wagtail.contrib.routable_page.models.RoutablePageMixin, + "wagtailcore.page", + ), ), migrations.CreateModel( - name='IESGStatementPage', + name="IESGStatementPage", fields=[ - ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')), - ('social_text', models.CharField(blank=True, help_text='Description of this page as it should appear when shared on social networks, or in Google results', max_length=255)), - ('date_published', models.DateTimeField(blank=True, help_text='Use this field to override the date that the blog post appears to have been published.', null=True)), - ('introduction', models.CharField(help_text='The page introduction text.', max_length=511)), - ('body', wagtail.core.fields.StreamField([('heading', wagtail.core.blocks.CharBlock(icon='title')), ('paragraph', wagtail.core.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.images.blocks.ImageChooserBlock(icon='image', template='includes/imageblock.html')), ('embed', wagtail.embeds.blocks.EmbedBlock(icon='code')), ('raw_html', wagtail.core.blocks.RawHTMLBlock(icon='placeholder')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}))])), - ('prepared_body', models.TextField(blank=True, help_text='The prepared body content after bibliography styling has been applied. Auto-generated on each save.', null=True)), - ('feed_image', models.ForeignKey(blank=True, help_text='This image will be used in listings and indexes across the site, if no feed image is added, the social image will be used.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.IETFImage')), - ('social_image', models.ForeignKey(blank=True, help_text="Image to appear alongside 'social text', particularly for sharing on social networks", null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.IETFImage')), + ( + "page_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="wagtailcore.Page", + ), + ), + ( + "social_text", + models.CharField( + blank=True, + help_text="Description of this page as it should appear when shared on social networks, or in Google results", + max_length=255, + ), + ), + ( + "date_published", + models.DateTimeField( + blank=True, + help_text="Use this field to override the date that the blog post appears to have been published.", + null=True, + ), + ), + ( + "introduction", + models.CharField( + help_text="The page introduction text.", max_length=511 + ), + ), + ( + "body", + wagtail.fields.StreamField( + [ + ("heading", wagtail.blocks.CharBlock(icon="title")), + ("paragraph", wagtail.blocks.RichTextBlock(icon="pilcrow")), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + icon="image", template="includes/imageblock.html" + ), + ), + ("embed", wagtail.embeds.blocks.EmbedBlock(icon="code")), + ( + "raw_html", + wagtail.blocks.RawHTMLBlock(icon="placeholder"), + ), + ( + "table", + wagtail.contrib.table_block.blocks.TableBlock( + table_options={"renderer": "html"} + ), + ), + ] + ), + ), + ( + "prepared_body", + models.TextField( + blank=True, + help_text="The prepared body content after bibliography styling has been applied. Auto-generated on each save.", + null=True, + ), + ), + ( + "feed_image", + models.ForeignKey( + blank=True, + help_text="This image will be used in listings and indexes across the site, if no feed image is added, the social image will be used.", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to="images.IETFImage", + ), + ), + ( + "social_image", + models.ForeignKey( + blank=True, + help_text="Image to appear alongside 'social text', particularly for sharing on social networks", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to="images.IETFImage", + ), + ), ], options={ - 'verbose_name': 'IESG Statement Page', + "verbose_name": "IESG Statement Page", }, - bases=('wagtailcore.page', models.Model), + bases=("wagtailcore.page", models.Model), ), migrations.CreateModel( - name='IESGStatementTopic', + name="IESGStatementTopic", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('page', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='topics', to='iesg_statement.IESGStatementPage')), - ('topic', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='snippets.Topic')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "page", + modelcluster.fields.ParentalKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="topics", + to="iesg_statement.IESGStatementPage", + ), + ), + ( + "topic", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to="snippets.Topic", + ), + ), ], ), ] diff --git a/ietf/iesg_statement/migrations/0002_auto_20210325_0442.py b/ietf/iesg_statement/migrations/0002_auto_20210325_0442.py index 7b137ed8..22d8b1a9 100644 --- a/ietf/iesg_statement/migrations/0002_auto_20210325_0442.py +++ b/ietf/iesg_statement/migrations/0002_auto_20210325_0442.py @@ -1,23 +1,43 @@ # Generated by Django 2.2.16 on 2021-03-25 04:42 -from django.db import migrations +import wagtail.blocks import wagtail.contrib.table_block.blocks -import wagtail.core.blocks -import wagtail.core.fields import wagtail.embeds.blocks +import wagtail.fields import wagtail.images.blocks +from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('iesg_statement', '0001_initial'), + ("iesg_statement", "0001_initial"), ] operations = [ migrations.AlterField( - model_name='iesgstatementpage', - name='body', - field=wagtail.core.fields.StreamField([('heading', wagtail.core.blocks.CharBlock(icon='title')), ('paragraph', wagtail.core.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.images.blocks.ImageChooserBlock(icon='image', template='includes/imageblock.html')), ('embed', wagtail.embeds.blocks.EmbedBlock(icon='code')), ('raw_html', wagtail.core.blocks.RawHTMLBlock(icon='placeholder')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}, template='includes/tableblock.html'))]), + model_name="iesgstatementpage", + name="body", + field=wagtail.fields.StreamField( + [ + ("heading", wagtail.blocks.CharBlock(icon="title")), + ("paragraph", wagtail.blocks.RichTextBlock(icon="pilcrow")), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + icon="image", template="includes/imageblock.html" + ), + ), + ("embed", wagtail.embeds.blocks.EmbedBlock(icon="code")), + ("raw_html", wagtail.blocks.RawHTMLBlock(icon="placeholder")), + ( + "table", + wagtail.contrib.table_block.blocks.TableBlock( + table_options={"renderer": "html"}, + template="includes/tableblock.html", + ), + ), + ] + ), ), ] diff --git a/ietf/iesg_statement/migrations/0003_auto_20211101_0113.py b/ietf/iesg_statement/migrations/0003_auto_20211101_0113.py index c4ab6110..2c31adea 100644 --- a/ietf/iesg_statement/migrations/0003_auto_20211101_0113.py +++ b/ietf/iesg_statement/migrations/0003_auto_20211101_0113.py @@ -1,24 +1,45 @@ # Generated by Django 2.2.19 on 2021-11-01 01:13 -from django.db import migrations +import wagtail.blocks import wagtail.contrib.table_block.blocks -import wagtail.core.blocks -import wagtail.core.fields import wagtail.embeds.blocks +import wagtail.fields import wagtail.images.blocks import wagtailmarkdown.blocks +from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('iesg_statement', '0002_auto_20210325_0442'), + ("iesg_statement", "0002_auto_20210325_0442"), ] operations = [ migrations.AlterField( - model_name='iesgstatementpage', - name='body', - field=wagtail.core.fields.StreamField([('heading', wagtail.core.blocks.CharBlock(icon='title')), ('paragraph', wagtail.core.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.images.blocks.ImageChooserBlock(icon='image', template='includes/imageblock.html')), ('markdown', wagtailmarkdown.blocks.MarkdownBlock(icon='code')), ('embed', wagtail.embeds.blocks.EmbedBlock(icon='code')), ('raw_html', wagtail.core.blocks.RawHTMLBlock(icon='placeholder')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}, template='includes/tableblock.html'))]), + model_name="iesgstatementpage", + name="body", + field=wagtail.fields.StreamField( + [ + ("heading", wagtail.blocks.CharBlock(icon="title")), + ("paragraph", wagtail.blocks.RichTextBlock(icon="pilcrow")), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + icon="image", template="includes/imageblock.html" + ), + ), + ("markdown", wagtailmarkdown.blocks.MarkdownBlock(icon="code")), + ("embed", wagtail.embeds.blocks.EmbedBlock(icon="code")), + ("raw_html", wagtail.blocks.RawHTMLBlock(icon="placeholder")), + ( + "table", + wagtail.contrib.table_block.blocks.TableBlock( + table_options={"renderer": "html"}, + template="includes/tableblock.html", + ), + ), + ] + ), ), ] diff --git a/ietf/iesg_statement/models.py b/ietf/iesg_statement/models.py index 4eac5e89..a3ffd799 100644 --- a/ietf/iesg_statement/models.py +++ b/ietf/iesg_statement/models.py @@ -1,32 +1,26 @@ from datetime import datetime from functools import partial +from django.core.exceptions import ObjectDoesNotExist from django.db import models from django.db.models.functions import Coalesce from django.shortcuts import redirect - -from django.core.exceptions import ObjectDoesNotExist from django.utils import functional from django.utils.safestring import mark_safe - from modelcluster.fields import ParentalKey - -from wagtail.core.models import Page -from wagtail.core.fields import StreamField -from wagtail.snippets.edit_handlers import ( - SnippetChooserPanel -) -from wagtail.search import index -from wagtail.admin.edit_handlers import ( - StreamFieldPanel, FieldPanel, InlinePanel -) +from wagtail.admin.panels import FieldPanel, InlinePanel from wagtail.contrib.routable_page.models import RoutablePageMixin, route +from wagtail.fields import StreamField +from wagtail.models import Page +from wagtail.search import index from ..bibliography.models import BibliographyMixin -#from ..utils.models import FeedSettings, PromoteMixin -from ..utils.models import PromoteMixin -from ..utils.blocks import StandardBlock from ..snippets.models import Topic +from ..utils.blocks import StandardBlock + +# from ..utils.models import FeedSettings, PromoteMixin +from ..utils.models import PromoteMixin + def filter_pages_by_topic(pages, topic): return pages.filter(topics__topic=topic) @@ -51,81 +45,75 @@ def parse_date_search_input(date): def build_filter_text(**kwargs): if any(kwargs): text_fragments = [] - if kwargs.get('topic'): + if kwargs.get("topic"): + text_fragments.append("{}".format(kwargs.get("topic"))) + if kwargs.get("date_from") and kwargs.get("date_to"): text_fragments.append( - '{}'.format(kwargs.get('topic')) + "dates between {} & {}".format( + kwargs["date_from"], kwargs["date_to"] ) - if kwargs.get('date_from') and kwargs.get('date_to'): + ) + elif kwargs.get("date_from"): text_fragments.append( - 'dates between {} & {}'.format( - kwargs['date_from'], kwargs['date_to'] - ) + "dates after {}".format(kwargs["date_from"]) + ) + elif kwargs.get("date_to"): + text_fragments.append( + "dates before {}".format(kwargs["date_to"]) ) - elif kwargs.get('date_from'): - text_fragments.append('dates after {}'.format( - kwargs['date_from'] - )) - elif kwargs.get('date_to'): - text_fragments.append('dates before {}'.format( - kwargs['date_to'] - )) - return ', '.join(text_fragments) + return ", ".join(text_fragments) else: return "" parameter_functions_map = { - 'topic': [get_topic_by_id, filter_pages_by_topic], - 'date_from': [parse_date_search_input, filter_pages_by_date_from], - 'date_to': [parse_date_search_input, filter_pages_by_date_to] + "topic": [get_topic_by_id, filter_pages_by_topic], + "date_from": [parse_date_search_input, filter_pages_by_date_from], + "date_to": [parse_date_search_input, filter_pages_by_date_to], } class IESGStatementTopic(models.Model): - page = ParentalKey( - 'iesg_statement.IESGStatementPage', - related_name='topics' - ) + page = ParentalKey("iesg_statement.IESGStatementPage", related_name="topics") topic = models.ForeignKey( - 'snippets.Topic', - related_name='+', + "snippets.Topic", + related_name="+", on_delete=models.CASCADE, ) - panels = [ - SnippetChooserPanel('topic') - ] + panels = [FieldPanel("topic")] + class IESGStatementPage(Page, BibliographyMixin, PromoteMixin): date_published = models.DateTimeField( - null=True, blank=True, + null=True, + blank=True, help_text="Use this field to override the date that the " - "blog post appears to have been published." + "blog post appears to have been published.", ) introduction = models.CharField( - max_length=511, - help_text="The page introduction text." + max_length=511, help_text="The page introduction text." ) body = StreamField(StandardBlock()) search_fields = Page.search_fields + [ - index.SearchField('introduction'), - index.SearchField('body'), + index.SearchField("introduction"), + index.SearchField("body"), ] # for bibliography prepared_body = models.TextField( - blank=True, null=True, + blank=True, + null=True, help_text="The prepared body content after bibliography styling has been applied. Auto-generated on each save.", ) - CONTENT_FIELD_MAP = {'body': 'prepared_body'} + CONTENT_FIELD_MAP = {"body": "prepared_body"} - parent_page_types = ['iesg_statement.IESGStatementIndexPage'] + parent_page_types = ["iesg_statement.IESGStatementIndexPage"] subpage_types = [] - @property def date(self): return self.date_published or self.first_published_at @@ -134,14 +122,21 @@ def date(self): def next(self): if not self.date: return None - after = sorted([p for p in self.siblings.exclude(pk=self.pk) if p.date > self.date], key=lambda o:o.date) + after = sorted( + [p for p in self.siblings.exclude(pk=self.pk) if p.date > self.date], + key=lambda o: o.date, + ) return after and after[0] or None @property def previous(self): if not self.date: return None - before = sorted([p for p in self.siblings.exclude(pk=self.pk) if p.date < self.date], key=lambda o:o.date, reverse=True) + before = sorted( + [p for p in self.siblings.exclude(pk=self.pk) if p.date < self.date], + key=lambda o: o.date, + reverse=True, + ) return before and before[0] or None def coalesced_published_date(self): @@ -153,9 +148,12 @@ def feed_text(self): @functional.cached_property def siblings(self): - return self.__class__.objects.live().sibling_of(self).annotate( - d=Coalesce('date_published', 'first_published_at') - ).order_by('-d') + return ( + self.__class__.objects.live() + .sibling_of(self) + .annotate(d=Coalesce("date_published", "first_published_at")) + .order_by("-d") + ) def get_context(self, request, *args, **kwargs): context = super(IESGStatementPage, self).get_context(request, *args, **kwargs) @@ -170,8 +168,9 @@ def get_context(self, request, *args, **kwargs): related_object = functions[0](search_query) siblings = functions[1](siblings, related_object) query_string += "%s=%s&" % (parameter, search_query) - filter_text_builder = partial(filter_text_builder, - **{parameter: related_object.__str__()}) + filter_text_builder = partial( + filter_text_builder, **{parameter: related_object.__str__()} + ) except (ValueError, ObjectDoesNotExist): pass @@ -179,56 +178,60 @@ def get_context(self, request, *args, **kwargs): siblings = siblings.filter(d__lt=self.coalesced_published_date())[:5] - if filter_text: if siblings: filter_text = mark_safe("You have filtered by " + filter_text) else: - filter_text = mark_safe("No results for " + filter_text + ", showing latest") + filter_text = mark_safe( + "No results for " + filter_text + ", showing latest" + ) context.update( parent_url=self.get_parent().url, - filter_text = filter_text, + filter_text=filter_text, siblings=siblings, - topics=IESGStatementTopic.objects.all().values_list( - 'topic__pk', 'topic__title' - ).distinct(), + topics=IESGStatementTopic.objects.all() + .values_list("topic__pk", "topic__title") + .distinct(), query_string=query_string, # TODO blog_feed_title=feed_settings.blog_feed_title ) return context def serve_preview(self, request, mode_name): - """ This is another hack to overcome the MRO issue we were seeing """ + """This is another hack to overcome the MRO issue we were seeing""" return BibliographyMixin.serve_preview(self, request, mode_name) class Meta: verbose_name = "IESG Statement Page" + IESGStatementPage.content_panels = Page.content_panels + [ - FieldPanel('date_published'), - FieldPanel('introduction'), - StreamFieldPanel('body'), - InlinePanel('topics', label="Topics"), + FieldPanel("date_published"), + FieldPanel("introduction"), + FieldPanel("body"), + InlinePanel("topics", label="Topics"), ] IESGStatementPage.promote_panels = Page.promote_panels + PromoteMixin.panels class IESGStatementIndexPage(RoutablePageMixin, Page): - def get_context(self, request): context = super().get_context(request) - context['statements'] = IESGStatementPage.objects.child_of(self).live().annotate( - d=Coalesce('date_published', 'first_published_at') - ).order_by('-d') + context["statements"] = ( + IESGStatementPage.objects.child_of(self) + .live() + .annotate(d=Coalesce("date_published", "first_published_at")) + .order_by("-d") + ) return context - @route(r'^all/$') + @route(r"^all/$") def all_entries(self, request, *args, **kwargs): return super().serve(request, *args, **kwargs) - @route(r'^$') + @route(r"^$") def redirect_first(self, request, *args, **kwargs): has_filter = False for parameter, _ in parameter_functions_map.items(): @@ -236,10 +239,13 @@ def redirect_first(self, request, *args, **kwargs): has_filter = True break - if (has_filter): - statements = IESGStatementPage.objects.child_of(self).live().annotate( - d=Coalesce('date_published', 'first_published_at') - ).order_by('-d') + if has_filter: + statements = ( + IESGStatementPage.objects.child_of(self) + .live() + .annotate(d=Coalesce("date_published", "first_published_at")) + .order_by("-d") + ) first_statement_url = statements.first().url query_string = "?" @@ -256,11 +262,9 @@ def redirect_first(self, request, *args, **kwargs): first_statement_url = statements.first().url return redirect(first_statement_url + query_string) else: - return super().serve(request,*args,**kwargs) + return super().serve(request, *args, **kwargs) - - subpage_types = ['iesg_statement.IESGStatementPage'] + subpage_types = ["iesg_statement.IESGStatementPage"] class Meta: verbose_name = "IESG Statements Index Page" - diff --git a/ietf/iesg_statement/tests.py b/ietf/iesg_statement/tests.py index f56d8d29..becd3e55 100644 --- a/ietf/iesg_statement/tests.py +++ b/ietf/iesg_statement/tests.py @@ -1,23 +1,22 @@ from django.test import Client, TestCase +from wagtail.models import Page, Site -from .models import IESGStatementIndexPage, IESGStatementPage from ..home.models import HomePage +from .models import IESGStatementIndexPage, IESGStatementPage -from wagtail.core.models import Page, Site class IESGStatementPageTests(TestCase): - def test_iesg_statement_page(self): root = Page.get_first_root_node() home = HomePage( - slug = 'homepageslug', - title = 'home page title', - heading = 'home page heading', - introduction = 'home page introduction', - request_for_comments_section_body = 'rfc section body', - working_groups_section_body = 'wg section body', + slug="homepageslug", + title="home page title", + heading="home page heading", + introduction="home page introduction", + request_for_comments_section_body="rfc section body", + working_groups_section_body="wg section body", ) root.add_child(instance=home) @@ -25,24 +24,24 @@ def test_iesg_statement_page(self): Site.objects.all().delete() Site.objects.create( - hostname='localhost', - root_page = home, + hostname="localhost", + root_page=home, is_default_site=True, - site_name='testingsitename', + site_name="testingsitename", ) iesg_statement_index = IESGStatementIndexPage( - slug = 'iesg_statement_index', - title = 'iesg statement index page title', + slug="iesg_statement_index", + title="iesg statement index page title", ) - home.add_child(instance = iesg_statement_index) + home.add_child(instance=iesg_statement_index) iesg_statement_page = IESGStatementPage( - slug = 'iesgstatement', - title = 'iesg statement title', - introduction = 'iesg statement introduction', + slug="iesgstatement", + title="iesg statement title", + introduction="iesg statement introduction", ) - iesg_statement_index.add_child(instance = iesg_statement_page) + iesg_statement_index.add_child(instance=iesg_statement_page) rindex = self.client.get(path=iesg_statement_index.url) self.assertEqual(rindex.status_code, 200) diff --git a/ietf/images/migrations/0001_initial.py b/ietf/images/migrations/0001_initial.py index 5731ece4..d5dffcf0 100644 --- a/ietf/images/migrations/0001_initial.py +++ b/ietf/images/migrations/0001_initial.py @@ -2,13 +2,13 @@ # Generated by Django 1.11.29 on 2020-04-10 18:46 from __future__ import unicode_literals -from django.conf import settings -from django.db import migrations, models import django.db.models.deletion import taggit.managers -import wagtail.core.models import wagtail.images.models +import wagtail.models import wagtail.search.index +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): @@ -17,50 +17,135 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('taggit', '0002_auto_20150616_2121'), - ('wagtailcore', '0040_page_draft_title'), + ("taggit", "0002_auto_20150616_2121"), + ("wagtailcore", "0040_page_draft_title"), ] operations = [ migrations.CreateModel( - name='IETFImage', + name="IETFImage", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('title', models.CharField(max_length=255, verbose_name='title')), - ('file', models.ImageField(height_field='height', upload_to=wagtail.images.models.get_upload_to, verbose_name='file', width_field='width')), - ('width', models.IntegerField(editable=False, verbose_name='width')), - ('height', models.IntegerField(editable=False, verbose_name='height')), - ('created_at', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='created at')), - ('focal_point_x', models.PositiveIntegerField(blank=True, null=True)), - ('focal_point_y', models.PositiveIntegerField(blank=True, null=True)), - ('focal_point_width', models.PositiveIntegerField(blank=True, null=True)), - ('focal_point_height', models.PositiveIntegerField(blank=True, null=True)), - ('file_size', models.PositiveIntegerField(editable=False, null=True)), - ('file_hash', models.CharField(blank=True, editable=False, max_length=40)), - ('caption', models.CharField(blank=True, max_length=255, null=True)), - ('collection', models.ForeignKey(default=wagtail.core.models.get_root_collection_id, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='wagtailcore.Collection', verbose_name='collection')), - ('tags', taggit.managers.TaggableManager(blank=True, help_text=None, through='taggit.TaggedItem', to='taggit.Tag', verbose_name='tags')), - ('uploaded_by_user', models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='uploaded by user')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("title", models.CharField(max_length=255, verbose_name="title")), + ( + "file", + models.ImageField( + height_field="height", + upload_to=wagtail.images.models.get_upload_to, + verbose_name="file", + width_field="width", + ), + ), + ("width", models.IntegerField(editable=False, verbose_name="width")), + ("height", models.IntegerField(editable=False, verbose_name="height")), + ( + "created_at", + models.DateTimeField( + auto_now_add=True, db_index=True, verbose_name="created at" + ), + ), + ("focal_point_x", models.PositiveIntegerField(blank=True, null=True)), + ("focal_point_y", models.PositiveIntegerField(blank=True, null=True)), + ( + "focal_point_width", + models.PositiveIntegerField(blank=True, null=True), + ), + ( + "focal_point_height", + models.PositiveIntegerField(blank=True, null=True), + ), + ("file_size", models.PositiveIntegerField(editable=False, null=True)), + ( + "file_hash", + models.CharField(blank=True, editable=False, max_length=40), + ), + ("caption", models.CharField(blank=True, max_length=255, null=True)), + ( + "collection", + models.ForeignKey( + default=wagtail.models.get_root_collection_id, + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to="wagtailcore.Collection", + verbose_name="collection", + ), + ), + ( + "tags", + taggit.managers.TaggableManager( + blank=True, + help_text=None, + through="taggit.TaggedItem", + to="taggit.Tag", + verbose_name="tags", + ), + ), + ( + "uploaded_by_user", + models.ForeignKey( + blank=True, + editable=False, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + verbose_name="uploaded by user", + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, bases=(wagtail.search.index.Indexed, models.Model), ), migrations.CreateModel( - name='IETFRendition', + name="IETFRendition", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('filter_spec', models.CharField(db_index=True, max_length=255)), - ('file', models.ImageField(height_field='height', upload_to=wagtail.images.models.get_rendition_upload_to, width_field='width')), - ('width', models.IntegerField(editable=False)), - ('height', models.IntegerField(editable=False)), - ('focal_point_key', models.CharField(blank=True, default='', editable=False, max_length=16)), - ('image', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='renditions', to='images.IETFImage')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("filter_spec", models.CharField(db_index=True, max_length=255)), + ( + "file", + models.ImageField( + height_field="height", + upload_to=wagtail.images.models.get_rendition_upload_to, + width_field="width", + ), + ), + ("width", models.IntegerField(editable=False)), + ("height", models.IntegerField(editable=False)), + ( + "focal_point_key", + models.CharField( + blank=True, default="", editable=False, max_length=16 + ), + ), + ( + "image", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="renditions", + to="images.IETFImage", + ), + ), ], ), migrations.AlterUniqueTogether( - name='ietfrendition', - unique_together=set([('image', 'filter_spec', 'focal_point_key')]), + name="ietfrendition", + unique_together=set([("image", "filter_spec", "focal_point_key")]), ), ] diff --git a/ietf/images/migrations/0002_alter_ietfimage_file_hash.py b/ietf/images/migrations/0002_alter_ietfimage_file_hash.py new file mode 100644 index 00000000..cd64bf42 --- /dev/null +++ b/ietf/images/migrations/0002_alter_ietfimage_file_hash.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.13 on 2022-07-22 02:02 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('images', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='ietfimage', + name='file_hash', + field=models.CharField(blank=True, db_index=True, editable=False, max_length=40), + ), + ] diff --git a/ietf/search/views.py b/ietf/search/views.py index c1ddab7b..4dd0b0bb 100644 --- a/ietf/search/views.py +++ b/ietf/search/views.py @@ -1,17 +1,16 @@ +from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator from django.shortcuts import render -from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger - -from wagtail.core.models import Page -from wagtail.search.models import Query from wagtail.contrib.search_promotions.models import SearchPromotion +from wagtail.models import Page +from wagtail.search.models import Query def search(request): - search_query = request.GET.get('query', None) - page = request.GET.get('page', 1) + search_query = request.GET.get("query", None) + page = request.GET.get("page", 1) # Search - if search_query and '\x00' not in search_query: + if search_query and "\x00" not in search_query: search_results = Page.objects.live().search(search_query) query = Query.get(search_query) @@ -33,8 +32,12 @@ def search(request): except EmptyPage: search_results = paginator.page(paginator.num_pages) - return render(request, 'search/search.html', { - 'search_query': search_query, - 'search_results': search_results, - 'search_picks': search_picks, - }) + return render( + request, + "search/search.html", + { + "search_query": search_query, + "search_results": search_results, + "search_picks": search_picks, + }, + ) diff --git a/ietf/settings/base.py b/ietf/settings/base.py index 98f05030..2f745a35 100644 --- a/ietf/settings/base.py +++ b/ietf/settings/base.py @@ -54,10 +54,9 @@ "wagtail.search", "wagtail.contrib.search_promotions", "wagtail.admin", - "wagtail.core", + "wagtail", "wagtail.contrib.settings", "wagtail.contrib.table_block", - "wagtail.contrib.postgres_search", "wagtail.contrib.routable_page", "wagtail.contrib.modeladmin", "modelcluster", @@ -96,7 +95,9 @@ TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", - "DIRS": [os.path.join(PROJECT_DIR, "templates"),], + "DIRS": [ + os.path.join(PROJECT_DIR, "templates"), + ], "APP_DIRS": True, "OPTIONS": { "context_processors": [ @@ -118,7 +119,10 @@ # https://docs.djangoproject.com/en/1.8/ref/settings/#databases DATABASES = { - "default": {"ENGINE": "django.db.backends.postgresql_psycopg2", "NAME": "ietf",} + "default": { + "ENGINE": "django.db.backends.postgresql_psycopg2", + "NAME": "ietf", + } } @@ -175,7 +179,9 @@ WAGTAILSEARCH_BACKENDS = { - "default": {"BACKEND": "wagtail.contrib.postgres_search.backend",}, + "default": { + "BACKEND": "wagtail.search.backends.database", + }, } @@ -192,20 +198,35 @@ WAGTAILIMAGES_IMAGE_MODEL = "images.IETFImage" WAGTAILDOCS_DOCUMENT_MODEL = "documents.IetfDocument" WAGTAILADMIN_RICH_TEXT_EDITORS = { - 'default': { - 'WIDGET': 'wagtail.admin.rich_text.DraftailRichTextArea', - 'OPTIONS': { - 'features': [ - 'h2', 'h3', 'h4', 'h5', 'h6', - 'ol', 'ul', - 'bold', 'italic', - 'superscript', 'subscript', 'strikethrough', - 'hr', 'link', 'document-link', - 'image', 'embed', - 'code', 'blockquote'] - } + "default": { + "WIDGET": "wagtail.admin.rich_text.DraftailRichTextArea", + "OPTIONS": { + "features": [ + "h2", + "h3", + "h4", + "h5", + "h6", + "ol", + "ul", + "bold", + "italic", + "superscript", + "subscript", + "strikethrough", + "hr", + "link", + "document-link", + "image", + "embed", + "code", + "blockquote", + ] + }, } } # Application-wide settings DATATRACKER_URI = "https://datatracker.ietf.org" + +DEFAULT_AUTO_FIELD = "django.db.models.AutoField" diff --git a/ietf/settings/production.py b/ietf/settings/production.py index 1aa30161..21af3828 100644 --- a/ietf/settings/production.py +++ b/ietf/settings/production.py @@ -42,7 +42,7 @@ ALLOWED_HOSTS = env['ALLOWED_HOSTS'].split(',') if 'PRIMARY_HOST' in env: - BASE_URL = 'http://%s/' % env['PRIMARY_HOST'] + WAGTAILADMIN_BASE_URL = 'http://%s/' % env['PRIMARY_HOST'] if 'SERVER_EMAIL' in env: SERVER_EMAIL = env['SERVER_EMAIL'] diff --git a/ietf/snippets/migrations/0001_initial.py b/ietf/snippets/migrations/0001_initial.py index e90d6c10..a5a4beb5 100644 --- a/ietf/snippets/migrations/0001_initial.py +++ b/ietf/snippets/migrations/0001_initial.py @@ -2,11 +2,12 @@ # Generated by Django 1.11.29 on 2020-04-10 18:46 from __future__ import unicode_literals -from django.db import migrations, models import django.db.models.deletion -import ietf.snippets.models -import wagtail.core.fields +import wagtail.fields import wagtail.search.index +from django.db import migrations, models + +import ietf.snippets.models class Migration(migrations.Migration): @@ -14,183 +15,435 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('images', '0001_initial'), - ('wagtailcore', '0040_page_draft_title'), - ('wagtaildocs', '0008_document_file_size'), + ("images", "0001_initial"), + ("wagtailcore", "0040_page_draft_title"), + ("wagtaildocs", "0008_document_file_size"), ] operations = [ migrations.CreateModel( - name='CallToAction', + name="CallToAction", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('link_external', models.URLField(blank=True, verbose_name='External link')), - ('title', models.CharField(help_text='Link title', max_length=255)), - ('blurb', models.CharField(blank=True, help_text='An explanation of the call to action.', max_length=255)), - ('button_text', models.CharField(help_text='Text that appears on the call to action link.', max_length=255)), - ('link_document', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='wagtaildocs.Document')), - ('link_page', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='wagtailcore.Page')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "link_external", + models.URLField(blank=True, verbose_name="External link"), + ), + ("title", models.CharField(help_text="Link title", max_length=255)), + ( + "blurb", + models.CharField( + blank=True, + help_text="An explanation of the call to action.", + max_length=255, + ), + ), + ( + "button_text", + models.CharField( + help_text="Text that appears on the call to action link.", + max_length=255, + ), + ), + ( + "link_document", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to="wagtaildocs.Document", + ), + ), + ( + "link_page", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to="wagtailcore.Page", + ), + ), ], options={ - 'verbose_name_plural': 'Calls to action', - 'ordering': ['title'], + "verbose_name_plural": "Calls to action", + "ordering": ["title"], }, - bases=(wagtail.search.index.Indexed, models.Model, ietf.snippets.models.RenderableSnippetMixin), + bases=( + wagtail.search.index.Indexed, + models.Model, + ietf.snippets.models.RenderableSnippetMixin, + ), ), migrations.CreateModel( - name='Charter', + name="Charter", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=511, unique=True)), - ('title', models.TextField(blank=True)), - ('abstract', models.TextField(blank=True)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=511, unique=True)), + ("title", models.TextField(blank=True)), + ("abstract", models.TextField(blank=True)), ], options={ - 'verbose_name': 'Charter', - 'ordering': ['title'], + "verbose_name": "Charter", + "ordering": ["title"], }, bases=(models.Model, wagtail.search.index.Indexed), ), migrations.CreateModel( - name='GlossaryItem', + name="GlossaryItem", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('title', models.CharField(help_text='The glossary term.', max_length=255)), - ('body', wagtail.core.fields.RichTextField(help_text='Explanation of the glossary term.')), - ('link', models.URLField(blank=True)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "title", + models.CharField(help_text="The glossary term.", max_length=255), + ), + ( + "body", + wagtail.fields.RichTextField( + help_text="Explanation of the glossary term." + ), + ), + ("link", models.URLField(blank=True)), ], options={ - 'verbose_name': 'Glossary item', - 'ordering': ['title'], + "verbose_name": "Glossary item", + "ordering": ["title"], }, bases=(models.Model, wagtail.search.index.Indexed), ), migrations.CreateModel( - name='Group', + name="Group", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(help_text="This group's name.", max_length=255)), - ('summary', models.CharField(blank=True, help_text='More information about this group.', max_length=511)), - ('email', models.EmailField(blank=True, help_text="This group's email address.", max_length=254)), - ('image', models.ForeignKey(blank=True, help_text='An image to represent this group.', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='images.IETFImage')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "name", + models.CharField(help_text="This group's name.", max_length=255), + ), + ( + "summary", + models.CharField( + blank=True, + help_text="More information about this group.", + max_length=511, + ), + ), + ( + "email", + models.EmailField( + blank=True, + help_text="This group's email address.", + max_length=254, + ), + ), + ( + "image", + models.ForeignKey( + blank=True, + help_text="An image to represent this group.", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to="images.IETFImage", + ), + ), ], options={ - 'ordering': ['name'], + "ordering": ["name"], }, - bases=(models.Model, wagtail.search.index.Indexed, ietf.snippets.models.RenderableSnippetMixin), + bases=( + models.Model, + wagtail.search.index.Indexed, + ietf.snippets.models.RenderableSnippetMixin, + ), ), migrations.CreateModel( - name='MailingListSignup', + name="MailingListSignup", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('title', models.CharField(help_text='The header text for this content.', max_length=255)), - ('blurb', models.CharField(blank=True, help_text='An explanation and call to action for this content.', max_length=255)), - ('button_text', models.CharField(help_text='Text that appears on the mailing list link.', max_length=255)), - ('sign_up', models.CharField(blank=True, help_text='The URL or email address where the user should sign up. If the working group is set then this does not need to be set.', max_length=255)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "title", + models.CharField( + help_text="The header text for this content.", max_length=255 + ), + ), + ( + "blurb", + models.CharField( + blank=True, + help_text="An explanation and call to action for this content.", + max_length=255, + ), + ), + ( + "button_text", + models.CharField( + help_text="Text that appears on the mailing list link.", + max_length=255, + ), + ), + ( + "sign_up", + models.CharField( + blank=True, + help_text="The URL or email address where the user should sign up. If the working group is set then this does not need to be set.", + max_length=255, + ), + ), ], options={ - 'ordering': ['title'], + "ordering": ["title"], }, - bases=(models.Model, wagtail.search.index.Indexed, ietf.snippets.models.RenderableSnippetMixin), + bases=( + models.Model, + wagtail.search.index.Indexed, + ietf.snippets.models.RenderableSnippetMixin, + ), ), migrations.CreateModel( - name='Person', + name="Person", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=511)), - ('link', models.URLField()), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=511)), + ("link", models.URLField()), ], options={ - 'ordering': ['name'], + "ordering": ["name"], }, bases=(models.Model, wagtail.search.index.Indexed), ), migrations.CreateModel( - name='RFC', + name="RFC", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=511)), - ('title', models.TextField(blank=True)), - ('rfc', models.CharField(help_text="The RFC's number (without any letters)", max_length=511, unique=True)), - ('abstract', models.TextField(blank=True)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=511)), + ("title", models.TextField(blank=True)), + ( + "rfc", + models.CharField( + help_text="The RFC's number (without any letters)", + max_length=511, + unique=True, + ), + ), + ("abstract", models.TextField(blank=True)), ], options={ - 'verbose_name': 'RFC', - 'ordering': ['title'], + "verbose_name": "RFC", + "ordering": ["title"], }, bases=(models.Model, wagtail.search.index.Indexed), ), migrations.CreateModel( - name='Role', + name="Role", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(help_text='A role within the IETF.', max_length=255)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "name", + models.CharField( + help_text="A role within the IETF.", max_length=255 + ), + ), ], options={ - 'verbose_name': 'Role Override', - 'ordering': ['name'], + "verbose_name": "Role Override", + "ordering": ["name"], }, bases=(models.Model, wagtail.search.index.Indexed), ), migrations.CreateModel( - name='Sponsor', + name="Sponsor", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('title', models.CharField(help_text='The name of the organisation.', max_length=255)), - ('link', models.URLField(blank=True)), - ('logo', models.ForeignKey(help_text="The organisation's logo.", on_delete=django.db.models.deletion.CASCADE, related_name='+', to='images.IETFImage')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "title", + models.CharField( + help_text="The name of the organisation.", max_length=255 + ), + ), + ("link", models.URLField(blank=True)), + ( + "logo", + models.ForeignKey( + help_text="The organisation's logo.", + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to="images.IETFImage", + ), + ), ], options={ - 'ordering': ['title'], + "ordering": ["title"], }, bases=(models.Model, wagtail.search.index.Indexed), ), migrations.CreateModel( - name='Topic', + name="Topic", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('title', models.CharField(help_text='The name of this topic.', max_length=255)), - ('slug', models.CharField(max_length=511, unique=True)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "title", + models.CharField( + help_text="The name of this topic.", max_length=255 + ), + ), + ("slug", models.CharField(max_length=511, unique=True)), ], options={ - 'ordering': ['title'], + "ordering": ["title"], }, bases=(models.Model, wagtail.search.index.Indexed), ), migrations.CreateModel( - name='WorkingGroup', + name="WorkingGroup", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=511)), - ('acronym', models.CharField(blank=True, max_length=511)), - ('description', models.CharField(blank=True, max_length=4096)), - ('list_email', models.EmailField(blank=True, max_length=254)), - ('list_subscribe', models.EmailField(blank=True, max_length=254)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=511)), + ("acronym", models.CharField(blank=True, max_length=511)), + ("description", models.CharField(blank=True, max_length=4096)), + ("list_email", models.EmailField(blank=True, max_length=254)), + ("list_subscribe", models.EmailField(blank=True, max_length=254)), ], options={ - 'verbose_name': 'Working Group', - 'ordering': ['name'], + "verbose_name": "Working Group", + "ordering": ["name"], }, bases=(models.Model, wagtail.search.index.Indexed), ), migrations.AddField( - model_name='rfc', - name='working_group', - field=models.ForeignKey(blank=True, help_text='The working group that produced this RFC', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='snippets.WorkingGroup'), + model_name="rfc", + name="working_group", + field=models.ForeignKey( + blank=True, + help_text="The working group that produced this RFC", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to="snippets.WorkingGroup", + ), ), migrations.AddField( - model_name='mailinglistsignup', - name='working_group', - field=models.ForeignKey(blank=True, help_text='The group whose mailing list sign up address should be used. If sign up is set then this does not need to be set.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='snippets.WorkingGroup'), + model_name="mailinglistsignup", + name="working_group", + field=models.ForeignKey( + blank=True, + help_text="The group whose mailing list sign up address should be used. If sign up is set then this does not need to be set.", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to="snippets.WorkingGroup", + ), ), migrations.AddField( - model_name='group', - name='role', - field=models.ForeignKey(blank=True, help_text="This group's role within the IETF.", null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='snippets.Role'), + model_name="group", + name="role", + field=models.ForeignKey( + blank=True, + help_text="This group's role within the IETF.", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to="snippets.Role", + ), ), migrations.AddField( - model_name='charter', - name='working_group', - field=models.ForeignKey(blank=True, help_text="This charter's working group", null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='snippets.WorkingGroup'), + model_name="charter", + name="working_group", + field=models.ForeignKey( + blank=True, + help_text="This charter's working group", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to="snippets.WorkingGroup", + ), ), ] diff --git a/ietf/snippets/models.py b/ietf/snippets/models.py index d3bf5b4e..358983c0 100644 --- a/ietf/snippets/models.py +++ b/ietf/snippets/models.py @@ -1,25 +1,20 @@ +from django.conf import settings from django.db import models from django.template.loader import get_template -from django.conf import settings - -from wagtail.snippets.models import register_snippet -from wagtail.admin.edit_handlers import FieldPanel -from wagtail.images.edit_handlers import ImageChooserPanel -from wagtail.snippets.edit_handlers import SnippetChooserPanel -from wagtail.search.index import Indexed +from wagtail.admin.panels import FieldPanel +from wagtail.fields import RichTextField from wagtail.search import index -from wagtail.core.fields import RichTextField +from wagtail.search.index import Indexed +from wagtail.snippets.models import register_snippet from ..utils.models import RelatedLink -class RenderableSnippetMixin(): - +class RenderableSnippetMixin: def render(self): template = get_template(self.TEMPLATE_NAME) - return template.render( - {'snippet': self} - ) + return template.render({"snippet": self}) + @register_snippet class Charter(models.Model, index.Indexed): @@ -27,16 +22,17 @@ class Charter(models.Model, index.Indexed): title = models.TextField(blank=True) abstract = models.TextField(blank=True) working_group = models.ForeignKey( - 'snippets.WorkingGroup', - blank=True, null=True, - related_name='+', + "snippets.WorkingGroup", + blank=True, + null=True, + related_name="+", help_text="This charter's working group", on_delete=models.CASCADE, ) search_fields = [ - index.SearchField('title', partial_match=True, boost=10), - index.SearchField('abstract'), + index.SearchField("title", partial_match=True, boost=10), + index.SearchField("abstract"), ] def __str__(self): @@ -50,9 +46,10 @@ def url(self): return "" class Meta: - ordering = ['title'] + ordering = ["title"] verbose_name = "Charter" + @register_snippet class WorkingGroup(models.Model, index.Indexed): @@ -64,9 +61,9 @@ class WorkingGroup(models.Model, index.Indexed): # There is no field currently to capture area/parent search_fields = [ - index.SearchField('name', partial_match=True, boost=10), - index.SearchField('acronym'), - index.SearchField('description'), + index.SearchField("name", partial_match=True, boost=10), + index.SearchField("acronym"), + index.SearchField("description"), ] @property @@ -81,30 +78,34 @@ def __str__(self): return self.name class Meta: - ordering = ['name'] + ordering = ["name"] verbose_name = "Working Group" + @register_snippet class RFC(models.Model, index.Indexed): name = models.CharField(max_length=511) title = models.TextField(blank=True) - rfc = models.CharField(max_length=511, unique=True, help_text="The RFC's number (without any letters)") + rfc = models.CharField( + max_length=511, unique=True, help_text="The RFC's number (without any letters)" + ) abstract = models.TextField(blank=True) # There is currently no field for authors working_group = models.ForeignKey( - 'snippets.WorkingGroup', - blank=True, null=True, - related_name='+', + "snippets.WorkingGroup", + blank=True, + null=True, + related_name="+", help_text="The working group that produced this RFC", on_delete=models.SET_NULL, ) search_fields = [ - index.SearchField('title', partial_match=True, boost=10), - index.SearchField('rfc', boost=10), - index.SearchField('authors'), - index.SearchField('abstract'), + index.SearchField("title", partial_match=True, boost=10), + index.SearchField("rfc", boost=10), + index.SearchField("authors"), + index.SearchField("abstract"), ] def __str__(self): @@ -119,43 +120,38 @@ def url(self): return settings.DATATRACKER_URI + "/doc/rfc" + self.rfc class Meta: - ordering = ['title'] + ordering = ["title"] verbose_name = "RFC" + @register_snippet class Person(models.Model, Indexed): name = models.CharField(max_length=511) link = models.URLField() - search_fields = [ index.SearchField('name')] - panels = [ FieldPanel('name') ] + search_fields = [index.SearchField("name")] + panels = [FieldPanel("name")] def __str__(self): return self.name class Meta: - ordering = ['name'] + ordering = ["name"] + @register_snippet class Role(models.Model, Indexed): - name = models.CharField( - max_length=255, - help_text="A role within the IETF." - ) + name = models.CharField(max_length=255, help_text="A role within the IETF.") - search_fields = [ - index.SearchField('name') - ] + search_fields = [index.SearchField("name")] - panels = [ - FieldPanel('name') - ] + panels = [FieldPanel("name")] def __str__(self): return self.name class Meta: - ordering = ['name'] + ordering = ["name"] verbose_name = "Role Override" @@ -165,55 +161,50 @@ class Group(models.Model, Indexed, RenderableSnippetMixin): A group of people within the IETF. Groups may appear on the site as :model:`blog.BlogPage` author groups. """ - name = models.CharField( - max_length=255, - help_text="This group's name." - ) + + name = models.CharField(max_length=255, help_text="This group's name.") role = models.ForeignKey( - 'snippets.Role', - blank=True, null=True, - related_name='+', + "snippets.Role", + blank=True, + null=True, + related_name="+", help_text="This group's role within the IETF.", on_delete=models.SET_NULL, ) summary = models.CharField( - blank=True, - max_length=511, - help_text="More information about this group." - ) - email = models.EmailField( - blank=True, - help_text="This group's email address." + blank=True, max_length=511, help_text="More information about this group." ) + email = models.EmailField(blank=True, help_text="This group's email address.") image = models.ForeignKey( - 'images.IETFImage', - blank=True, null=True, - related_name='+', + "images.IETFImage", + blank=True, + null=True, + related_name="+", help_text="An image to represent this group.", on_delete=models.SET_NULL, ) search_fields = [ - index.SearchField('name'), - index.SearchField('summary'), - index.SearchField('email'), + index.SearchField("name"), + index.SearchField("summary"), + index.SearchField("email"), ] panels = [ - FieldPanel('name'), - SnippetChooserPanel('role'), - FieldPanel('summary'), - FieldPanel('email'), - ImageChooserPanel('image') + FieldPanel("name"), + FieldPanel("role"), + FieldPanel("summary"), + FieldPanel("email"), + FieldPanel("image"), ] def __str__(self): return self.name - TEMPLATE_NAME = 'snippets/group.html' + TEMPLATE_NAME = "snippets/group.html" class Meta: - ordering = ['name'] + ordering = ["name"] @register_snippet @@ -222,35 +213,33 @@ class CallToAction(Indexed, RelatedLink, RenderableSnippetMixin): Content that guides the user to the next step after having read a page. """ + blurb = models.CharField( - max_length=255, - blank=True, - help_text="An explanation of the call to action." + max_length=255, blank=True, help_text="An explanation of the call to action." ) button_text = models.CharField( - max_length=255, - help_text="Text that appears on the call to action link." + max_length=255, help_text="Text that appears on the call to action link." ) search_fields = [ - index.SearchField('title'), - index.SearchField('blurb'), - index.SearchField('button_text'), + index.SearchField("title"), + index.SearchField("blurb"), + index.SearchField("button_text"), ] panels = RelatedLink.panels + [ - FieldPanel('blurb'), - FieldPanel('button_text'), + FieldPanel("blurb"), + FieldPanel("button_text"), ] def __str__(self): return self.title - TEMPLATE_NAME = 'snippets/call_to_action.html' + TEMPLATE_NAME = "snippets/call_to_action.html" class Meta: verbose_name_plural = "Calls to action" - ordering = ['title'] + ordering = ["title"] @register_snippet @@ -259,47 +248,47 @@ class MailingListSignup(models.Model, Indexed, RenderableSnippetMixin): Page content that directs users to a mailing list sign up link or address. """ + title = models.CharField( - max_length=255, - help_text="The header text for this content." + max_length=255, help_text="The header text for this content." ) blurb = models.CharField( max_length=255, blank=True, - help_text="An explanation and call to action for this content." + help_text="An explanation and call to action for this content.", ) button_text = models.CharField( - max_length=255, - help_text="Text that appears on the mailing list link." + max_length=255, help_text="Text that appears on the mailing list link." ) sign_up = models.CharField( max_length=255, blank=True, help_text="The URL or email address where the user should sign up. " - "If the working group is set then this does not need to be set." + "If the working group is set then this does not need to be set.", ) working_group = models.ForeignKey( - 'snippets.WorkingGroup', - null=True, blank=True, + "snippets.WorkingGroup", + null=True, + blank=True, on_delete=models.SET_NULL, - related_name='+', + related_name="+", help_text="The group whose mailing list sign up address should be " - "used. If sign up is set then this does not need to be set." + "used. If sign up is set then this does not need to be set.", ) search_fields = [ - index.SearchField('title'), - index.SearchField('blurb'), - index.SearchField('button_text'), - index.SearchField('sign_up'), + index.SearchField("title"), + index.SearchField("blurb"), + index.SearchField("button_text"), + index.SearchField("sign_up"), ] panels = [ - FieldPanel('title'), - FieldPanel('blurb'), - FieldPanel('button_text'), - FieldPanel('sign_up'), - FieldPanel('working_group'), + FieldPanel("title"), + FieldPanel("blurb"), + FieldPanel("button_text"), + FieldPanel("sign_up"), + FieldPanel("working_group"), ] @property @@ -309,47 +298,44 @@ def link(self): else: link = self.working_group.list_subscribe - if '@' in link: - return 'mailto:{}'.format(link) + if "@" in link: + return "mailto:{}".format(link) else: return link - TEMPLATE_NAME = 'snippets/mailing_list_signup.html' + TEMPLATE_NAME = "snippets/mailing_list_signup.html" def __str__(self): return self.title class Meta: - ordering = ['title'] + ordering = ["title"] @register_snippet class Topic(models.Model, Indexed): """ - These snippets categorise blog posts. + These snippets categorise blog posts. """ - title = models.CharField( - max_length=255, - help_text="The name of this topic." - ) - slug = models.CharField(max_length=511, unique=True) + title = models.CharField(max_length=255, help_text="The name of this topic.") + slug = models.CharField(max_length=511, unique=True) search_fields = [ - index.SearchField('title'), - index.SearchField('slug'), + index.SearchField("title"), + index.SearchField("slug"), ] panels = [ - FieldPanel('title'), - FieldPanel('slug'), + FieldPanel("title"), + FieldPanel("slug"), ] def __str__(self): return self.title class Meta: - ordering = ['title'] + ordering = ["title"] @register_snippet @@ -357,33 +343,27 @@ class Sponsor(models.Model, Indexed): """ An organisation that sponsors IETF events. """ - title = models.CharField( - max_length=255, - help_text="The name of the organisation." - ) + + title = models.CharField(max_length=255, help_text="The name of the organisation.") logo = models.ForeignKey( - 'images.IETFImage', - related_name='+', + "images.IETFImage", + related_name="+", help_text="The organisation's logo.", - on_delete = models.CASCADE, + on_delete=models.CASCADE, ) link = models.URLField(blank=True) search_fields = [ - index.SearchField('title'), + index.SearchField("title"), ] - panels = [ - FieldPanel('title'), - ImageChooserPanel('logo'), - FieldPanel('link') - ] + panels = [FieldPanel("title"), FieldPanel("logo"), FieldPanel("link")] def __str__(self): return self.title class Meta: - ordering = ['title'] + ordering = ["title"] @register_snippet @@ -392,24 +372,20 @@ class GlossaryItem(models.Model, Indexed): A short explanation of a technical term. Appears on the :models:`glossary.GlossaryPage`. """ - title = models.CharField( - max_length=255, - help_text="The glossary term." - ) - body = RichTextField( - help_text="Explanation of the glossary term." - ) + + title = models.CharField(max_length=255, help_text="The glossary term.") + body = RichTextField(help_text="Explanation of the glossary term.") link = models.URLField(blank=True) search_fields = [ - index.SearchField('title'), - index.SearchField('body'), + index.SearchField("title"), + index.SearchField("body"), ] panels = [ - FieldPanel('title'), - FieldPanel('body'), - FieldPanel('link'), + FieldPanel("title"), + FieldPanel("body"), + FieldPanel("link"), ] def __str__(self): @@ -418,9 +394,9 @@ def __str__(self): @property def url(self): from ietf.glossary.models import GlossaryPage - return "{}?query={}".format(GlossaryPage.objects.first().url, - self.title) + + return "{}?query={}".format(GlossaryPage.objects.first().url, self.title) class Meta: - ordering = ['title'] + ordering = ["title"] verbose_name = "Glossary item" diff --git a/ietf/standard/migrations/0001_initial.py b/ietf/standard/migrations/0001_initial.py index cfd1c51c..d276a7ee 100644 --- a/ietf/standard/migrations/0001_initial.py +++ b/ietf/standard/migrations/0001_initial.py @@ -2,14 +2,14 @@ # Generated by Django 1.11.29 on 2020-04-10 18:46 from __future__ import unicode_literals -from django.db import migrations, models import django.db.models.deletion import modelcluster.fields +import wagtail.blocks import wagtail.contrib.table_block.blocks -import wagtail.core.blocks -import wagtail.core.fields import wagtail.embeds.blocks +import wagtail.fields import wagtail.images.blocks +from django.db import migrations, models class Migration(migrations.Migration): @@ -17,93 +17,414 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('wagtaildocs', '0008_document_file_size'), - ('wagtailcore', '0040_page_draft_title'), - ('images', '0001_initial'), - ('snippets', '0001_initial'), + ("wagtaildocs", "0008_document_file_size"), + ("wagtailcore", "0040_page_draft_title"), + ("images", "0001_initial"), + ("snippets", "0001_initial"), ] operations = [ migrations.CreateModel( - name='StandardIndexPage', + name="StandardIndexPage", fields=[ - ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')), - ('social_text', models.CharField(blank=True, help_text='Description of this page as it should appear when shared on social networks, or in Google results', max_length=255)), - ('introduction', models.CharField(blank=True, help_text='Enter the title to display on the page, you can use only 255 characters.', max_length=255)), - ('key_info', wagtail.core.fields.StreamField([('heading', wagtail.core.blocks.CharBlock(icon='title')), ('paragraph', wagtail.core.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.images.blocks.ImageChooserBlock(icon='image', template='includes/imageblock.html')), ('embed', wagtail.embeds.blocks.EmbedBlock(icon='code')), ('raw_html', wagtail.core.blocks.RawHTMLBlock(icon='placeholder')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}))], blank=True)), - ('in_depth', wagtail.core.fields.StreamField([('heading', wagtail.core.blocks.CharBlock(icon='title')), ('paragraph', wagtail.core.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.images.blocks.ImageChooserBlock(icon='image', template='includes/imageblock.html')), ('embed', wagtail.embeds.blocks.EmbedBlock(icon='code')), ('raw_html', wagtail.core.blocks.RawHTMLBlock(icon='placeholder')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}))], blank=True)), - ('feed_image', models.ForeignKey(blank=True, help_text='This image will be used in listings and indexes across the site, if no feed image is added, the social image will be used.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.IETFImage')), - ('social_image', models.ForeignKey(blank=True, help_text="Image to appear alongside 'social text', particularly for sharing on social networks", null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.IETFImage')), + ( + "page_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="wagtailcore.Page", + ), + ), + ( + "social_text", + models.CharField( + blank=True, + help_text="Description of this page as it should appear when shared on social networks, or in Google results", + max_length=255, + ), + ), + ( + "introduction", + models.CharField( + blank=True, + help_text="Enter the title to display on the page, you can use only 255 characters.", + max_length=255, + ), + ), + ( + "key_info", + wagtail.fields.StreamField( + [ + ("heading", wagtail.blocks.CharBlock(icon="title")), + ("paragraph", wagtail.blocks.RichTextBlock(icon="pilcrow")), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + icon="image", template="includes/imageblock.html" + ), + ), + ("embed", wagtail.embeds.blocks.EmbedBlock(icon="code")), + ( + "raw_html", + wagtail.blocks.RawHTMLBlock(icon="placeholder"), + ), + ( + "table", + wagtail.contrib.table_block.blocks.TableBlock( + table_options={"renderer": "html"} + ), + ), + ], + blank=True, + ), + ), + ( + "in_depth", + wagtail.fields.StreamField( + [ + ("heading", wagtail.blocks.CharBlock(icon="title")), + ("paragraph", wagtail.blocks.RichTextBlock(icon="pilcrow")), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + icon="image", template="includes/imageblock.html" + ), + ), + ("embed", wagtail.embeds.blocks.EmbedBlock(icon="code")), + ( + "raw_html", + wagtail.blocks.RawHTMLBlock(icon="placeholder"), + ), + ( + "table", + wagtail.contrib.table_block.blocks.TableBlock( + table_options={"renderer": "html"} + ), + ), + ], + blank=True, + ), + ), + ( + "feed_image", + models.ForeignKey( + blank=True, + help_text="This image will be used in listings and indexes across the site, if no feed image is added, the social image will be used.", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to="images.IETFImage", + ), + ), + ( + "social_image", + models.ForeignKey( + blank=True, + help_text="Image to appear alongside 'social text', particularly for sharing on social networks", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to="images.IETFImage", + ), + ), ], options={ - 'verbose_name': 'Index Page', + "verbose_name": "Index Page", }, - bases=('wagtailcore.page', models.Model), + bases=("wagtailcore.page", models.Model), ), migrations.CreateModel( - name='StandardPage', + name="StandardPage", fields=[ - ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')), - ('social_text', models.CharField(blank=True, help_text='Description of this page as it should appear when shared on social networks, or in Google results', max_length=255)), - ('introduction', models.CharField(blank=True, help_text='Enter the title to display on the page, you can use only 255 characters.', max_length=255)), - ('key_info', wagtail.core.fields.StreamField([('heading', wagtail.core.blocks.CharBlock(icon='title')), ('paragraph', wagtail.core.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.images.blocks.ImageChooserBlock(icon='image', template='includes/imageblock.html')), ('embed', wagtail.embeds.blocks.EmbedBlock(icon='code')), ('raw_html', wagtail.core.blocks.RawHTMLBlock(icon='placeholder')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}))], blank=True)), - ('in_depth', wagtail.core.fields.StreamField([('heading', wagtail.core.blocks.CharBlock(icon='title')), ('paragraph', wagtail.core.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.images.blocks.ImageChooserBlock(icon='image', template='includes/imageblock.html')), ('embed', wagtail.embeds.blocks.EmbedBlock(icon='code')), ('raw_html', wagtail.core.blocks.RawHTMLBlock(icon='placeholder')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}))], blank=True)), - ('prepared_key_info', models.TextField(blank=True, help_text='The prepared key info field after bibliography styling has been applied. Auto-generated on each save.', null=True)), - ('prepared_in_depth', models.TextField(blank=True, help_text='The prepared in depth field after bibliography styling has been applied. Auto-generated on each save.', null=True)), - ('call_to_action', models.ForeignKey(blank=True, help_text='Specify the page you would like visitors to go to next.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='snippets.CallToAction')), - ('feed_image', models.ForeignKey(blank=True, help_text='This image will be used in listings and indexes across the site, if no feed image is added, the social image will be used.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.IETFImage')), - ('mailing_list_signup', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='snippets.MailingListSignup')), - ('social_image', models.ForeignKey(blank=True, help_text="Image to appear alongside 'social text', particularly for sharing on social networks", null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.IETFImage')), + ( + "page_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="wagtailcore.Page", + ), + ), + ( + "social_text", + models.CharField( + blank=True, + help_text="Description of this page as it should appear when shared on social networks, or in Google results", + max_length=255, + ), + ), + ( + "introduction", + models.CharField( + blank=True, + help_text="Enter the title to display on the page, you can use only 255 characters.", + max_length=255, + ), + ), + ( + "key_info", + wagtail.fields.StreamField( + [ + ("heading", wagtail.blocks.CharBlock(icon="title")), + ("paragraph", wagtail.blocks.RichTextBlock(icon="pilcrow")), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + icon="image", template="includes/imageblock.html" + ), + ), + ("embed", wagtail.embeds.blocks.EmbedBlock(icon="code")), + ( + "raw_html", + wagtail.blocks.RawHTMLBlock(icon="placeholder"), + ), + ( + "table", + wagtail.contrib.table_block.blocks.TableBlock( + table_options={"renderer": "html"} + ), + ), + ], + blank=True, + ), + ), + ( + "in_depth", + wagtail.fields.StreamField( + [ + ("heading", wagtail.blocks.CharBlock(icon="title")), + ("paragraph", wagtail.blocks.RichTextBlock(icon="pilcrow")), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + icon="image", template="includes/imageblock.html" + ), + ), + ("embed", wagtail.embeds.blocks.EmbedBlock(icon="code")), + ( + "raw_html", + wagtail.blocks.RawHTMLBlock(icon="placeholder"), + ), + ( + "table", + wagtail.contrib.table_block.blocks.TableBlock( + table_options={"renderer": "html"} + ), + ), + ], + blank=True, + ), + ), + ( + "prepared_key_info", + models.TextField( + blank=True, + help_text="The prepared key info field after bibliography styling has been applied. Auto-generated on each save.", + null=True, + ), + ), + ( + "prepared_in_depth", + models.TextField( + blank=True, + help_text="The prepared in depth field after bibliography styling has been applied. Auto-generated on each save.", + null=True, + ), + ), + ( + "call_to_action", + models.ForeignKey( + blank=True, + help_text="Specify the page you would like visitors to go to next.", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to="snippets.CallToAction", + ), + ), + ( + "feed_image", + models.ForeignKey( + blank=True, + help_text="This image will be used in listings and indexes across the site, if no feed image is added, the social image will be used.", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to="images.IETFImage", + ), + ), + ( + "mailing_list_signup", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to="snippets.MailingListSignup", + ), + ), + ( + "social_image", + models.ForeignKey( + blank=True, + help_text="Image to appear alongside 'social text', particularly for sharing on social networks", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to="images.IETFImage", + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, - bases=('wagtailcore.page', models.Model), + bases=("wagtailcore.page", models.Model), ), migrations.CreateModel( - name='StandardPageFAQItem', + name="StandardPageFAQItem", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('sort_order', models.IntegerField(blank=True, editable=False, null=True)), - ('question', models.TextField()), - ('answer', models.TextField()), - ('page', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='faq_items', to='standard.StandardPage')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "sort_order", + models.IntegerField(blank=True, editable=False, null=True), + ), + ("question", models.TextField()), + ("answer", models.TextField()), + ( + "page", + modelcluster.fields.ParentalKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="faq_items", + to="standard.StandardPage", + ), + ), ], options={ - 'ordering': ['sort_order'], - 'abstract': False, + "ordering": ["sort_order"], + "abstract": False, }, ), migrations.CreateModel( - name='StandardPageFeedRelatedLink', + name="StandardPageFeedRelatedLink", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('sort_order', models.IntegerField(blank=True, editable=False, null=True)), - ('link_external', models.URLField(blank=True, verbose_name='External link')), - ('title', models.CharField(help_text='Link title', max_length=255)), - ('link_document', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='wagtaildocs.Document')), - ('link_page', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='wagtailcore.Page')), - ('page', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='feed_related_links', to='standard.StandardPage')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "sort_order", + models.IntegerField(blank=True, editable=False, null=True), + ), + ( + "link_external", + models.URLField(blank=True, verbose_name="External link"), + ), + ("title", models.CharField(help_text="Link title", max_length=255)), + ( + "link_document", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to="wagtaildocs.Document", + ), + ), + ( + "link_page", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to="wagtailcore.Page", + ), + ), + ( + "page", + modelcluster.fields.ParentalKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="feed_related_links", + to="standard.StandardPage", + ), + ), ], options={ - 'ordering': ['sort_order'], - 'abstract': False, + "ordering": ["sort_order"], + "abstract": False, }, ), migrations.CreateModel( - name='StandardPageRelatedLink', + name="StandardPageRelatedLink", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('sort_order', models.IntegerField(blank=True, editable=False, null=True)), - ('link_external', models.URLField(blank=True, verbose_name='External link')), - ('title', models.CharField(help_text='Link title', max_length=255)), - ('link_document', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='wagtaildocs.Document')), - ('link_page', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='wagtailcore.Page')), - ('page', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='related_links', to='standard.StandardPage')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "sort_order", + models.IntegerField(blank=True, editable=False, null=True), + ), + ( + "link_external", + models.URLField(blank=True, verbose_name="External link"), + ), + ("title", models.CharField(help_text="Link title", max_length=255)), + ( + "link_document", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to="wagtaildocs.Document", + ), + ), + ( + "link_page", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to="wagtailcore.Page", + ), + ), + ( + "page", + modelcluster.fields.ParentalKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="related_links", + to="standard.StandardPage", + ), + ), ], options={ - 'ordering': ['sort_order'], - 'abstract': False, + "ordering": ["sort_order"], + "abstract": False, }, ), ] diff --git a/ietf/standard/migrations/0002_auto_20210325_0442.py b/ietf/standard/migrations/0002_auto_20210325_0442.py index b9f52b52..c36270e4 100644 --- a/ietf/standard/migrations/0002_auto_20210325_0442.py +++ b/ietf/standard/migrations/0002_auto_20210325_0442.py @@ -1,38 +1,122 @@ # Generated by Django 2.2.16 on 2021-03-25 04:42 -from django.db import migrations +import wagtail.blocks import wagtail.contrib.table_block.blocks -import wagtail.core.blocks -import wagtail.core.fields import wagtail.embeds.blocks +import wagtail.fields import wagtail.images.blocks +from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('standard', '0001_initial'), + ("standard", "0001_initial"), ] operations = [ migrations.AlterField( - model_name='standardindexpage', - name='in_depth', - field=wagtail.core.fields.StreamField([('heading', wagtail.core.blocks.CharBlock(icon='title')), ('paragraph', wagtail.core.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.images.blocks.ImageChooserBlock(icon='image', template='includes/imageblock.html')), ('embed', wagtail.embeds.blocks.EmbedBlock(icon='code')), ('raw_html', wagtail.core.blocks.RawHTMLBlock(icon='placeholder')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}, template='includes/tableblock.html'))], blank=True), + model_name="standardindexpage", + name="in_depth", + field=wagtail.fields.StreamField( + [ + ("heading", wagtail.blocks.CharBlock(icon="title")), + ("paragraph", wagtail.blocks.RichTextBlock(icon="pilcrow")), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + icon="image", template="includes/imageblock.html" + ), + ), + ("embed", wagtail.embeds.blocks.EmbedBlock(icon="code")), + ("raw_html", wagtail.blocks.RawHTMLBlock(icon="placeholder")), + ( + "table", + wagtail.contrib.table_block.blocks.TableBlock( + table_options={"renderer": "html"}, + template="includes/tableblock.html", + ), + ), + ], + blank=True, + ), ), migrations.AlterField( - model_name='standardindexpage', - name='key_info', - field=wagtail.core.fields.StreamField([('heading', wagtail.core.blocks.CharBlock(icon='title')), ('paragraph', wagtail.core.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.images.blocks.ImageChooserBlock(icon='image', template='includes/imageblock.html')), ('embed', wagtail.embeds.blocks.EmbedBlock(icon='code')), ('raw_html', wagtail.core.blocks.RawHTMLBlock(icon='placeholder')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}, template='includes/tableblock.html'))], blank=True), + model_name="standardindexpage", + name="key_info", + field=wagtail.fields.StreamField( + [ + ("heading", wagtail.blocks.CharBlock(icon="title")), + ("paragraph", wagtail.blocks.RichTextBlock(icon="pilcrow")), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + icon="image", template="includes/imageblock.html" + ), + ), + ("embed", wagtail.embeds.blocks.EmbedBlock(icon="code")), + ("raw_html", wagtail.blocks.RawHTMLBlock(icon="placeholder")), + ( + "table", + wagtail.contrib.table_block.blocks.TableBlock( + table_options={"renderer": "html"}, + template="includes/tableblock.html", + ), + ), + ], + blank=True, + ), ), migrations.AlterField( - model_name='standardpage', - name='in_depth', - field=wagtail.core.fields.StreamField([('heading', wagtail.core.blocks.CharBlock(icon='title')), ('paragraph', wagtail.core.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.images.blocks.ImageChooserBlock(icon='image', template='includes/imageblock.html')), ('embed', wagtail.embeds.blocks.EmbedBlock(icon='code')), ('raw_html', wagtail.core.blocks.RawHTMLBlock(icon='placeholder')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}, template='includes/tableblock.html'))], blank=True), + model_name="standardpage", + name="in_depth", + field=wagtail.fields.StreamField( + [ + ("heading", wagtail.blocks.CharBlock(icon="title")), + ("paragraph", wagtail.blocks.RichTextBlock(icon="pilcrow")), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + icon="image", template="includes/imageblock.html" + ), + ), + ("embed", wagtail.embeds.blocks.EmbedBlock(icon="code")), + ("raw_html", wagtail.blocks.RawHTMLBlock(icon="placeholder")), + ( + "table", + wagtail.contrib.table_block.blocks.TableBlock( + table_options={"renderer": "html"}, + template="includes/tableblock.html", + ), + ), + ], + blank=True, + ), ), migrations.AlterField( - model_name='standardpage', - name='key_info', - field=wagtail.core.fields.StreamField([('heading', wagtail.core.blocks.CharBlock(icon='title')), ('paragraph', wagtail.core.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.images.blocks.ImageChooserBlock(icon='image', template='includes/imageblock.html')), ('embed', wagtail.embeds.blocks.EmbedBlock(icon='code')), ('raw_html', wagtail.core.blocks.RawHTMLBlock(icon='placeholder')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}, template='includes/tableblock.html'))], blank=True), + model_name="standardpage", + name="key_info", + field=wagtail.fields.StreamField( + [ + ("heading", wagtail.blocks.CharBlock(icon="title")), + ("paragraph", wagtail.blocks.RichTextBlock(icon="pilcrow")), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + icon="image", template="includes/imageblock.html" + ), + ), + ("embed", wagtail.embeds.blocks.EmbedBlock(icon="code")), + ("raw_html", wagtail.blocks.RawHTMLBlock(icon="placeholder")), + ( + "table", + wagtail.contrib.table_block.blocks.TableBlock( + table_options={"renderer": "html"}, + template="includes/tableblock.html", + ), + ), + ], + blank=True, + ), ), ] diff --git a/ietf/standard/migrations/0003_auto_20211101_0113.py b/ietf/standard/migrations/0003_auto_20211101_0113.py index 0d5d9876..d110b881 100644 --- a/ietf/standard/migrations/0003_auto_20211101_0113.py +++ b/ietf/standard/migrations/0003_auto_20211101_0113.py @@ -1,39 +1,127 @@ # Generated by Django 2.2.19 on 2021-11-01 01:13 -from django.db import migrations +import wagtail.blocks import wagtail.contrib.table_block.blocks -import wagtail.core.blocks -import wagtail.core.fields import wagtail.embeds.blocks +import wagtail.fields import wagtail.images.blocks import wagtailmarkdown.blocks +from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('standard', '0002_auto_20210325_0442'), + ("standard", "0002_auto_20210325_0442"), ] operations = [ migrations.AlterField( - model_name='standardindexpage', - name='in_depth', - field=wagtail.core.fields.StreamField([('heading', wagtail.core.blocks.CharBlock(icon='title')), ('paragraph', wagtail.core.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.images.blocks.ImageChooserBlock(icon='image', template='includes/imageblock.html')), ('markdown', wagtailmarkdown.blocks.MarkdownBlock(icon='code')), ('embed', wagtail.embeds.blocks.EmbedBlock(icon='code')), ('raw_html', wagtail.core.blocks.RawHTMLBlock(icon='placeholder')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}, template='includes/tableblock.html'))], blank=True), + model_name="standardindexpage", + name="in_depth", + field=wagtail.fields.StreamField( + [ + ("heading", wagtail.blocks.CharBlock(icon="title")), + ("paragraph", wagtail.blocks.RichTextBlock(icon="pilcrow")), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + icon="image", template="includes/imageblock.html" + ), + ), + ("markdown", wagtailmarkdown.blocks.MarkdownBlock(icon="code")), + ("embed", wagtail.embeds.blocks.EmbedBlock(icon="code")), + ("raw_html", wagtail.blocks.RawHTMLBlock(icon="placeholder")), + ( + "table", + wagtail.contrib.table_block.blocks.TableBlock( + table_options={"renderer": "html"}, + template="includes/tableblock.html", + ), + ), + ], + blank=True, + ), ), migrations.AlterField( - model_name='standardindexpage', - name='key_info', - field=wagtail.core.fields.StreamField([('heading', wagtail.core.blocks.CharBlock(icon='title')), ('paragraph', wagtail.core.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.images.blocks.ImageChooserBlock(icon='image', template='includes/imageblock.html')), ('markdown', wagtailmarkdown.blocks.MarkdownBlock(icon='code')), ('embed', wagtail.embeds.blocks.EmbedBlock(icon='code')), ('raw_html', wagtail.core.blocks.RawHTMLBlock(icon='placeholder')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}, template='includes/tableblock.html'))], blank=True), + model_name="standardindexpage", + name="key_info", + field=wagtail.fields.StreamField( + [ + ("heading", wagtail.blocks.CharBlock(icon="title")), + ("paragraph", wagtail.blocks.RichTextBlock(icon="pilcrow")), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + icon="image", template="includes/imageblock.html" + ), + ), + ("markdown", wagtailmarkdown.blocks.MarkdownBlock(icon="code")), + ("embed", wagtail.embeds.blocks.EmbedBlock(icon="code")), + ("raw_html", wagtail.blocks.RawHTMLBlock(icon="placeholder")), + ( + "table", + wagtail.contrib.table_block.blocks.TableBlock( + table_options={"renderer": "html"}, + template="includes/tableblock.html", + ), + ), + ], + blank=True, + ), ), migrations.AlterField( - model_name='standardpage', - name='in_depth', - field=wagtail.core.fields.StreamField([('heading', wagtail.core.blocks.CharBlock(icon='title')), ('paragraph', wagtail.core.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.images.blocks.ImageChooserBlock(icon='image', template='includes/imageblock.html')), ('markdown', wagtailmarkdown.blocks.MarkdownBlock(icon='code')), ('embed', wagtail.embeds.blocks.EmbedBlock(icon='code')), ('raw_html', wagtail.core.blocks.RawHTMLBlock(icon='placeholder')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}, template='includes/tableblock.html'))], blank=True), + model_name="standardpage", + name="in_depth", + field=wagtail.fields.StreamField( + [ + ("heading", wagtail.blocks.CharBlock(icon="title")), + ("paragraph", wagtail.blocks.RichTextBlock(icon="pilcrow")), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + icon="image", template="includes/imageblock.html" + ), + ), + ("markdown", wagtailmarkdown.blocks.MarkdownBlock(icon="code")), + ("embed", wagtail.embeds.blocks.EmbedBlock(icon="code")), + ("raw_html", wagtail.blocks.RawHTMLBlock(icon="placeholder")), + ( + "table", + wagtail.contrib.table_block.blocks.TableBlock( + table_options={"renderer": "html"}, + template="includes/tableblock.html", + ), + ), + ], + blank=True, + ), ), migrations.AlterField( - model_name='standardpage', - name='key_info', - field=wagtail.core.fields.StreamField([('heading', wagtail.core.blocks.CharBlock(icon='title')), ('paragraph', wagtail.core.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.images.blocks.ImageChooserBlock(icon='image', template='includes/imageblock.html')), ('markdown', wagtailmarkdown.blocks.MarkdownBlock(icon='code')), ('embed', wagtail.embeds.blocks.EmbedBlock(icon='code')), ('raw_html', wagtail.core.blocks.RawHTMLBlock(icon='placeholder')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}, template='includes/tableblock.html'))], blank=True), + model_name="standardpage", + name="key_info", + field=wagtail.fields.StreamField( + [ + ("heading", wagtail.blocks.CharBlock(icon="title")), + ("paragraph", wagtail.blocks.RichTextBlock(icon="pilcrow")), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + icon="image", template="includes/imageblock.html" + ), + ), + ("markdown", wagtailmarkdown.blocks.MarkdownBlock(icon="code")), + ("embed", wagtail.embeds.blocks.EmbedBlock(icon="code")), + ("raw_html", wagtail.blocks.RawHTMLBlock(icon="placeholder")), + ( + "table", + wagtail.contrib.table_block.blocks.TableBlock( + table_options={"renderer": "html"}, + template="includes/tableblock.html", + ), + ), + ], + blank=True, + ), ), ] diff --git a/ietf/standard/migrations/0004_auto_20220902_0524.py b/ietf/standard/migrations/0004_auto_20220902_0524.py new file mode 100644 index 00000000..bfc31686 --- /dev/null +++ b/ietf/standard/migrations/0004_auto_20220902_0524.py @@ -0,0 +1,39 @@ +# Generated by Django 3.2.13 on 2022-09-02 04:24 + +from django.db import migrations +import wagtail.blocks +import wagtail.contrib.table_block.blocks +import wagtail.embeds.blocks +import wagtail.fields +import wagtail.images.blocks +import wagtailmarkdown.blocks + + +class Migration(migrations.Migration): + + dependencies = [ + ('standard', '0003_auto_20211101_0113'), + ] + + operations = [ + migrations.AlterField( + model_name='standardindexpage', + name='in_depth', + field=wagtail.fields.StreamField([('heading', wagtail.blocks.CharBlock(icon='title')), ('paragraph', wagtail.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.images.blocks.ImageChooserBlock(icon='image', template='includes/imageblock.html')), ('markdown', wagtailmarkdown.blocks.MarkdownBlock(icon='code')), ('embed', wagtail.embeds.blocks.EmbedBlock(icon='code')), ('raw_html', wagtail.blocks.RawHTMLBlock(icon='placeholder')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}, template='includes/tableblock.html'))], blank=True, use_json_field=True), + ), + migrations.AlterField( + model_name='standardindexpage', + name='key_info', + field=wagtail.fields.StreamField([('heading', wagtail.blocks.CharBlock(icon='title')), ('paragraph', wagtail.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.images.blocks.ImageChooserBlock(icon='image', template='includes/imageblock.html')), ('markdown', wagtailmarkdown.blocks.MarkdownBlock(icon='code')), ('embed', wagtail.embeds.blocks.EmbedBlock(icon='code')), ('raw_html', wagtail.blocks.RawHTMLBlock(icon='placeholder')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}, template='includes/tableblock.html'))], blank=True, use_json_field=True), + ), + migrations.AlterField( + model_name='standardpage', + name='in_depth', + field=wagtail.fields.StreamField([('heading', wagtail.blocks.CharBlock(icon='title')), ('paragraph', wagtail.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.images.blocks.ImageChooserBlock(icon='image', template='includes/imageblock.html')), ('markdown', wagtailmarkdown.blocks.MarkdownBlock(icon='code')), ('embed', wagtail.embeds.blocks.EmbedBlock(icon='code')), ('raw_html', wagtail.blocks.RawHTMLBlock(icon='placeholder')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}, template='includes/tableblock.html'))], blank=True, use_json_field=True), + ), + migrations.AlterField( + model_name='standardpage', + name='key_info', + field=wagtail.fields.StreamField([('heading', wagtail.blocks.CharBlock(icon='title')), ('paragraph', wagtail.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.images.blocks.ImageChooserBlock(icon='image', template='includes/imageblock.html')), ('markdown', wagtailmarkdown.blocks.MarkdownBlock(icon='code')), ('embed', wagtail.embeds.blocks.EmbedBlock(icon='code')), ('raw_html', wagtail.blocks.RawHTMLBlock(icon='placeholder')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}, template='includes/tableblock.html'))], blank=True, use_json_field=True), + ), + ] diff --git a/ietf/standard/models.py b/ietf/standard/models.py index a9f47161..f352ee47 100644 --- a/ietf/standard/models.py +++ b/ietf/standard/models.py @@ -1,22 +1,15 @@ from collections import OrderedDict from django.db import models - -from wagtail.core.models import Page, Orderable -from wagtail.core.fields import StreamField -from wagtail.admin.edit_handlers import ( - FieldPanel, StreamFieldPanel, InlinePanel -) -from wagtail.snippets.edit_handlers import ( - SnippetChooserPanel, -) -from wagtail.search import index - from modelcluster.fields import ParentalKey +from wagtail.admin.panels import FieldPanel, InlinePanel +from wagtail.fields import StreamField +from wagtail.models import Orderable, Page +from wagtail.search import index -from ..utils.models import PromoteMixin, RelatedLink -from ..utils.blocks import StandardBlock from ..bibliography.models import BibliographyMixin +from ..utils.blocks import StandardBlock +from ..utils.models import PromoteMixin, RelatedLink class StandardPageFAQItem(Orderable, models.Model): @@ -24,24 +17,20 @@ class StandardPageFAQItem(Orderable, models.Model): A question and answer pair that can be added to a Standard Page. """ - page = ParentalKey('standard.StandardPage', - related_name='faq_items') + + page = ParentalKey("standard.StandardPage", related_name="faq_items") question = models.TextField() answer = models.TextField() - panels = [ - FieldPanel('question'), - FieldPanel('answer') - ] + panels = [FieldPanel("question"), FieldPanel("answer")] class StandardPageFeedRelatedLink(Orderable, RelatedLink): - page = ParentalKey('standard.StandardPage', - related_name='feed_related_links') + page = ParentalKey("standard.StandardPage", related_name="feed_related_links") class StandardPageRelatedLink(Orderable, RelatedLink): - page = ParentalKey('standard.StandardPage', related_name='related_links') + page = ParentalKey("standard.StandardPage", related_name="related_links") class StandardPage(Page, BibliographyMixin, PromoteMixin): @@ -49,43 +38,49 @@ class StandardPage(Page, BibliographyMixin, PromoteMixin): blank=True, max_length=255, help_text="Enter the title to display on the page, " - "you can use only 255 characters." + "you can use only 255 characters.", ) - key_info = StreamField(StandardBlock(required=False), blank=True) - in_depth = StreamField(StandardBlock(required=False), blank=True) + key_info = StreamField(StandardBlock(required=False), blank=True, use_json_field=True) + in_depth = StreamField(StandardBlock(required=False), blank=True, use_json_field=True) call_to_action = models.ForeignKey( - 'snippets.CallToAction', - null=True, blank=True, + "snippets.CallToAction", + null=True, + blank=True, on_delete=models.SET_NULL, - related_name='+', - help_text="Specify the page you would like visitors to go to next." + related_name="+", + help_text="Specify the page you would like visitors to go to next.", ) mailing_list_signup = models.ForeignKey( - 'snippets.MailingListSignup', + "snippets.MailingListSignup", null=True, - blank=True, on_delete=models.SET_NULL, - related_name='+' + blank=True, + on_delete=models.SET_NULL, + related_name="+", ) search_fields = Page.search_fields + [ - index.SearchField('introduction'), - index.SearchField('key_info'), - index.SearchField('in_depth'), + index.SearchField("introduction"), + index.SearchField("key_info"), + index.SearchField("in_depth"), ] # for bibliography prepared_key_info = models.TextField( - blank=True, null=True, + blank=True, + null=True, help_text="The prepared key info field after bibliography styling has been applied. Auto-generated on each save.", ) prepared_in_depth = models.TextField( - blank=True, null=True, + blank=True, + null=True, help_text="The prepared in depth field after bibliography styling has been applied. Auto-generated on each save.", ) - CONTENT_FIELD_MAP = OrderedDict([ - ('key_info', 'prepared_key_info'), - ('in_depth', 'prepared_in_depth'), - ]) + CONTENT_FIELD_MAP = OrderedDict( + [ + ("key_info", "prepared_key_info"), + ("in_depth", "prepared_in_depth"), + ] + ) @property def feed_text(self): @@ -96,42 +91,47 @@ def siblings(self): return self.get_siblings().live().public().filter(show_in_menus=True).specific() def serve_preview(self, request, mode_name): - """ This is another hack to overcome the MRO issue we were seeing """ + """This is another hack to overcome the MRO issue we were seeing""" return BibliographyMixin.serve_preview(self, request, mode_name) StandardPage.content_panels = Page.content_panels + [ - FieldPanel('introduction'), - StreamFieldPanel('key_info'), - StreamFieldPanel('in_depth'), - SnippetChooserPanel('call_to_action'), - SnippetChooserPanel('mailing_list_signup'), - InlinePanel('related_links', label="Related Links"), - InlinePanel('faq_items', label="FAQ Items"), + FieldPanel("introduction"), + FieldPanel("key_info"), + FieldPanel("in_depth"), + FieldPanel("call_to_action"), + FieldPanel("mailing_list_signup"), + InlinePanel("related_links", label="Related Links"), + InlinePanel("faq_items", label="FAQ Items"), ] -StandardPage.promote_panels = Page.promote_panels + PromoteMixin.panels + [ - InlinePanel('feed_related_links', label="Feed related Links"), -] +StandardPage.promote_panels = ( + Page.promote_panels + + PromoteMixin.panels + + [ + InlinePanel("feed_related_links", label="Feed related Links"), + ] +) class StandardIndexPage(Page, PromoteMixin): """ An index for standard pages. """ + introduction = models.CharField( blank=True, max_length=255, help_text="Enter the title to display on the page, " - "you can use only 255 characters." + "you can use only 255 characters.", ) - key_info = StreamField(StandardBlock(required=False), blank=True) - in_depth = StreamField(StandardBlock(required=False), blank=True) + key_info = StreamField(StandardBlock(required=False), blank=True, use_json_field=True) + in_depth = StreamField(StandardBlock(required=False), blank=True, use_json_field=True) search_fields = Page.search_fields + [ - index.SearchField('introduction'), - index.SearchField('key_info'), - index.SearchField('in_depth'), + index.SearchField("introduction"), + index.SearchField("key_info"), + index.SearchField("in_depth"), ] @property @@ -143,9 +143,9 @@ class Meta: StandardIndexPage.content_panels = Page.content_panels + [ - FieldPanel('introduction'), - StreamFieldPanel('key_info'), - StreamFieldPanel('in_depth'), + FieldPanel("introduction"), + FieldPanel("key_info"), + FieldPanel("in_depth"), ] StandardIndexPage.promote_panels = Page.promote_panels + PromoteMixin.panels diff --git a/ietf/standard/tests.py b/ietf/standard/tests.py index 846bd249..6ac4bae1 100644 --- a/ietf/standard/tests.py +++ b/ietf/standard/tests.py @@ -1,23 +1,22 @@ from django.test import TestCase +from wagtail.models import Page, Site -from .models import StandardPage, StandardIndexPage from ..home.models import HomePage +from .models import StandardIndexPage, StandardPage -from wagtail.core.models import Page, Site class StandardPageTests(TestCase): - def test_standard_page(self): root = Page.get_first_root_node() home = HomePage( - slug = 'homepageslug', - title = 'home page title', - heading = 'home page heading', - introduction = 'home page introduction', - request_for_comments_section_body = 'rfc section body', - working_groups_section_body = 'wg section body', + slug="homepageslug", + title="home page title", + heading="home page heading", + introduction="home page introduction", + request_for_comments_section_body="rfc section body", + working_groups_section_body="wg section body", ) root.add_child(instance=home) @@ -25,25 +24,25 @@ def test_standard_page(self): Site.objects.all().delete() Site.objects.create( - hostname='localhost', - root_page = home, + hostname="localhost", + root_page=home, is_default_site=True, - site_name='testingsitename', + site_name="testingsitename", ) standardindex = StandardIndexPage( - slug = 'standardindex', - title = 'standard index page title', - introduction = 'standard index page introduction' + slug="standardindex", + title="standard index page title", + introduction="standard index page introduction", ) - home.add_child(instance = standardindex) + home.add_child(instance=standardindex) standardpage = StandardPage( - slug = 'standard', - title = 'standard title', - introduction = 'standard introduction', + slug="standard", + title="standard title", + introduction="standard introduction", ) - standardindex.add_child(instance = standardpage) + standardindex.add_child(instance=standardpage) rindex = self.client.get(path=standardindex.url) self.assertEqual(rindex.status_code, 200) diff --git a/ietf/topics/migrations/0001_initial.py b/ietf/topics/migrations/0001_initial.py index cab5b311..e67fd6fc 100644 --- a/ietf/topics/migrations/0001_initial.py +++ b/ietf/topics/migrations/0001_initial.py @@ -2,14 +2,14 @@ # Generated by Django 1.11.29 on 2020-04-10 18:46 from __future__ import unicode_literals -from django.db import migrations, models import django.db.models.deletion import modelcluster.fields +import wagtail.blocks import wagtail.contrib.table_block.blocks -import wagtail.core.blocks -import wagtail.core.fields import wagtail.embeds.blocks +import wagtail.fields import wagtail.images.blocks +from django.db import migrations, models class Migration(migrations.Migration): @@ -17,67 +17,258 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('images', '0001_initial'), - ('snippets', '0001_initial'), - ('wagtaildocs', '0008_document_file_size'), - ('wagtailcore', '0040_page_draft_title'), + ("images", "0001_initial"), + ("snippets", "0001_initial"), + ("wagtaildocs", "0008_document_file_size"), + ("wagtailcore", "0040_page_draft_title"), ] operations = [ migrations.CreateModel( - name='PrimaryTopicPage', + name="PrimaryTopicPage", fields=[ - ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')), - ('social_text', models.CharField(blank=True, help_text='Description of this page as it should appear when shared on social networks, or in Google results', max_length=255)), - ('introduction', models.CharField(blank=True, help_text='Enter the title to display on the page, you can use only 255 characters.', max_length=255)), - ('key_info', wagtail.core.fields.StreamField([('heading', wagtail.core.blocks.CharBlock(icon='title')), ('paragraph', wagtail.core.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.images.blocks.ImageChooserBlock(icon='image', template='includes/imageblock.html')), ('embed', wagtail.embeds.blocks.EmbedBlock(icon='code')), ('raw_html', wagtail.core.blocks.RawHTMLBlock(icon='placeholder')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}))], blank=True)), - ('in_depth', wagtail.core.fields.StreamField([('heading', wagtail.core.blocks.CharBlock(icon='title')), ('paragraph', wagtail.core.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.images.blocks.ImageChooserBlock(icon='image', template='includes/imageblock.html')), ('embed', wagtail.embeds.blocks.EmbedBlock(icon='code')), ('raw_html', wagtail.core.blocks.RawHTMLBlock(icon='placeholder')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}))], blank=True)), - ('call_to_action', models.ForeignKey(blank=True, help_text='Will only be displayed if no mailing list signup is selected.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='snippets.CallToAction')), - ('feed_image', models.ForeignKey(blank=True, help_text='This image will be used in listings and indexes across the site, if no feed image is added, the social image will be used.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.IETFImage')), - ('mailing_list_signup', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='snippets.MailingListSignup')), - ('social_image', models.ForeignKey(blank=True, help_text="Image to appear alongside 'social text', particularly for sharing on social networks", null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.IETFImage')), + ( + "page_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="wagtailcore.Page", + ), + ), + ( + "social_text", + models.CharField( + blank=True, + help_text="Description of this page as it should appear when shared on social networks, or in Google results", + max_length=255, + ), + ), + ( + "introduction", + models.CharField( + blank=True, + help_text="Enter the title to display on the page, you can use only 255 characters.", + max_length=255, + ), + ), + ( + "key_info", + wagtail.fields.StreamField( + [ + ("heading", wagtail.blocks.CharBlock(icon="title")), + ("paragraph", wagtail.blocks.RichTextBlock(icon="pilcrow")), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + icon="image", template="includes/imageblock.html" + ), + ), + ("embed", wagtail.embeds.blocks.EmbedBlock(icon="code")), + ( + "raw_html", + wagtail.blocks.RawHTMLBlock(icon="placeholder"), + ), + ( + "table", + wagtail.contrib.table_block.blocks.TableBlock( + table_options={"renderer": "html"} + ), + ), + ], + blank=True, + ), + ), + ( + "in_depth", + wagtail.fields.StreamField( + [ + ("heading", wagtail.blocks.CharBlock(icon="title")), + ("paragraph", wagtail.blocks.RichTextBlock(icon="pilcrow")), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + icon="image", template="includes/imageblock.html" + ), + ), + ("embed", wagtail.embeds.blocks.EmbedBlock(icon="code")), + ( + "raw_html", + wagtail.blocks.RawHTMLBlock(icon="placeholder"), + ), + ( + "table", + wagtail.contrib.table_block.blocks.TableBlock( + table_options={"renderer": "html"} + ), + ), + ], + blank=True, + ), + ), + ( + "call_to_action", + models.ForeignKey( + blank=True, + help_text="Will only be displayed if no mailing list signup is selected.", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to="snippets.CallToAction", + ), + ), + ( + "feed_image", + models.ForeignKey( + blank=True, + help_text="This image will be used in listings and indexes across the site, if no feed image is added, the social image will be used.", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to="images.IETFImage", + ), + ), + ( + "mailing_list_signup", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to="snippets.MailingListSignup", + ), + ), + ( + "social_image", + models.ForeignKey( + blank=True, + help_text="Image to appear alongside 'social text', particularly for sharing on social networks", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to="images.IETFImage", + ), + ), ], options={ - 'verbose_name': 'Topic Page', + "verbose_name": "Topic Page", }, - bases=('wagtailcore.page', models.Model), + bases=("wagtailcore.page", models.Model), ), migrations.CreateModel( - name='PrimaryTopicPageRelatedLink', + name="PrimaryTopicPageRelatedLink", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('sort_order', models.IntegerField(blank=True, editable=False, null=True)), - ('link_external', models.URLField(blank=True, verbose_name='External link')), - ('title', models.CharField(help_text='Link title', max_length=255)), - ('link_document', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='wagtaildocs.Document')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "sort_order", + models.IntegerField(blank=True, editable=False, null=True), + ), + ( + "link_external", + models.URLField(blank=True, verbose_name="External link"), + ), + ("title", models.CharField(help_text="Link title", max_length=255)), + ( + "link_document", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to="wagtaildocs.Document", + ), + ), ], options={ - 'ordering': ['sort_order'], - 'abstract': False, + "ordering": ["sort_order"], + "abstract": False, }, ), migrations.CreateModel( - name='TopicIndexPage', + name="TopicIndexPage", fields=[ - ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')), - ('social_text', models.CharField(blank=True, help_text='Description of this page as it should appear when shared on social networks, or in Google results', max_length=255)), - ('introduction', models.CharField(help_text='Enter the introduction to display on the page, you can use only 255 characters.', max_length=255)), - ('feed_image', models.ForeignKey(blank=True, help_text='This image will be used in listings and indexes across the site, if no feed image is added, the social image will be used.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.IETFImage')), - ('social_image', models.ForeignKey(blank=True, help_text="Image to appear alongside 'social text', particularly for sharing on social networks", null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.IETFImage')), + ( + "page_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="wagtailcore.Page", + ), + ), + ( + "social_text", + models.CharField( + blank=True, + help_text="Description of this page as it should appear when shared on social networks, or in Google results", + max_length=255, + ), + ), + ( + "introduction", + models.CharField( + help_text="Enter the introduction to display on the page, you can use only 255 characters.", + max_length=255, + ), + ), + ( + "feed_image", + models.ForeignKey( + blank=True, + help_text="This image will be used in listings and indexes across the site, if no feed image is added, the social image will be used.", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to="images.IETFImage", + ), + ), + ( + "social_image", + models.ForeignKey( + blank=True, + help_text="Image to appear alongside 'social text', particularly for sharing on social networks", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to="images.IETFImage", + ), + ), ], options={ - 'verbose_name': 'Topic Page List', + "verbose_name": "Topic Page List", }, - bases=('wagtailcore.page', models.Model), + bases=("wagtailcore.page", models.Model), ), migrations.AddField( - model_name='primarytopicpagerelatedlink', - name='link_page', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='wagtailcore.Page'), + model_name="primarytopicpagerelatedlink", + name="link_page", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to="wagtailcore.Page", + ), ), migrations.AddField( - model_name='primarytopicpagerelatedlink', - name='page', - field=modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='related_links', to='topics.PrimaryTopicPage'), + model_name="primarytopicpagerelatedlink", + name="page", + field=modelcluster.fields.ParentalKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="related_links", + to="topics.PrimaryTopicPage", + ), ), ] diff --git a/ietf/topics/migrations/0002_auto_20210325_0442.py b/ietf/topics/migrations/0002_auto_20210325_0442.py index 45feea65..13da7d46 100644 --- a/ietf/topics/migrations/0002_auto_20210325_0442.py +++ b/ietf/topics/migrations/0002_auto_20210325_0442.py @@ -1,28 +1,70 @@ # Generated by Django 2.2.16 on 2021-03-25 04:42 -from django.db import migrations +import wagtail.blocks import wagtail.contrib.table_block.blocks -import wagtail.core.blocks -import wagtail.core.fields import wagtail.embeds.blocks +import wagtail.fields import wagtail.images.blocks +from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('topics', '0001_initial'), + ("topics", "0001_initial"), ] operations = [ migrations.AlterField( - model_name='primarytopicpage', - name='in_depth', - field=wagtail.core.fields.StreamField([('heading', wagtail.core.blocks.CharBlock(icon='title')), ('paragraph', wagtail.core.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.images.blocks.ImageChooserBlock(icon='image', template='includes/imageblock.html')), ('embed', wagtail.embeds.blocks.EmbedBlock(icon='code')), ('raw_html', wagtail.core.blocks.RawHTMLBlock(icon='placeholder')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}, template='includes/tableblock.html'))], blank=True), + model_name="primarytopicpage", + name="in_depth", + field=wagtail.fields.StreamField( + [ + ("heading", wagtail.blocks.CharBlock(icon="title")), + ("paragraph", wagtail.blocks.RichTextBlock(icon="pilcrow")), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + icon="image", template="includes/imageblock.html" + ), + ), + ("embed", wagtail.embeds.blocks.EmbedBlock(icon="code")), + ("raw_html", wagtail.blocks.RawHTMLBlock(icon="placeholder")), + ( + "table", + wagtail.contrib.table_block.blocks.TableBlock( + table_options={"renderer": "html"}, + template="includes/tableblock.html", + ), + ), + ], + blank=True, + ), ), migrations.AlterField( - model_name='primarytopicpage', - name='key_info', - field=wagtail.core.fields.StreamField([('heading', wagtail.core.blocks.CharBlock(icon='title')), ('paragraph', wagtail.core.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.images.blocks.ImageChooserBlock(icon='image', template='includes/imageblock.html')), ('embed', wagtail.embeds.blocks.EmbedBlock(icon='code')), ('raw_html', wagtail.core.blocks.RawHTMLBlock(icon='placeholder')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}, template='includes/tableblock.html'))], blank=True), + model_name="primarytopicpage", + name="key_info", + field=wagtail.fields.StreamField( + [ + ("heading", wagtail.blocks.CharBlock(icon="title")), + ("paragraph", wagtail.blocks.RichTextBlock(icon="pilcrow")), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + icon="image", template="includes/imageblock.html" + ), + ), + ("embed", wagtail.embeds.blocks.EmbedBlock(icon="code")), + ("raw_html", wagtail.blocks.RawHTMLBlock(icon="placeholder")), + ( + "table", + wagtail.contrib.table_block.blocks.TableBlock( + table_options={"renderer": "html"}, + template="includes/tableblock.html", + ), + ), + ], + blank=True, + ), ), ] diff --git a/ietf/topics/migrations/0003_auto_20211101_0113.py b/ietf/topics/migrations/0003_auto_20211101_0113.py index 4148d8fc..09b97a6b 100644 --- a/ietf/topics/migrations/0003_auto_20211101_0113.py +++ b/ietf/topics/migrations/0003_auto_20211101_0113.py @@ -1,29 +1,73 @@ # Generated by Django 2.2.19 on 2021-11-01 01:13 -from django.db import migrations +import wagtail.blocks import wagtail.contrib.table_block.blocks -import wagtail.core.blocks -import wagtail.core.fields import wagtail.embeds.blocks +import wagtail.fields import wagtail.images.blocks import wagtailmarkdown.blocks +from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('topics', '0002_auto_20210325_0442'), + ("topics", "0002_auto_20210325_0442"), ] operations = [ migrations.AlterField( - model_name='primarytopicpage', - name='in_depth', - field=wagtail.core.fields.StreamField([('heading', wagtail.core.blocks.CharBlock(icon='title')), ('paragraph', wagtail.core.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.images.blocks.ImageChooserBlock(icon='image', template='includes/imageblock.html')), ('markdown', wagtailmarkdown.blocks.MarkdownBlock(icon='code')), ('embed', wagtail.embeds.blocks.EmbedBlock(icon='code')), ('raw_html', wagtail.core.blocks.RawHTMLBlock(icon='placeholder')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}, template='includes/tableblock.html'))], blank=True), + model_name="primarytopicpage", + name="in_depth", + field=wagtail.fields.StreamField( + [ + ("heading", wagtail.blocks.CharBlock(icon="title")), + ("paragraph", wagtail.blocks.RichTextBlock(icon="pilcrow")), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + icon="image", template="includes/imageblock.html" + ), + ), + ("markdown", wagtailmarkdown.blocks.MarkdownBlock(icon="code")), + ("embed", wagtail.embeds.blocks.EmbedBlock(icon="code")), + ("raw_html", wagtail.blocks.RawHTMLBlock(icon="placeholder")), + ( + "table", + wagtail.contrib.table_block.blocks.TableBlock( + table_options={"renderer": "html"}, + template="includes/tableblock.html", + ), + ), + ], + blank=True, + ), ), migrations.AlterField( - model_name='primarytopicpage', - name='key_info', - field=wagtail.core.fields.StreamField([('heading', wagtail.core.blocks.CharBlock(icon='title')), ('paragraph', wagtail.core.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.images.blocks.ImageChooserBlock(icon='image', template='includes/imageblock.html')), ('markdown', wagtailmarkdown.blocks.MarkdownBlock(icon='code')), ('embed', wagtail.embeds.blocks.EmbedBlock(icon='code')), ('raw_html', wagtail.core.blocks.RawHTMLBlock(icon='placeholder')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}, template='includes/tableblock.html'))], blank=True), + model_name="primarytopicpage", + name="key_info", + field=wagtail.fields.StreamField( + [ + ("heading", wagtail.blocks.CharBlock(icon="title")), + ("paragraph", wagtail.blocks.RichTextBlock(icon="pilcrow")), + ( + "image", + wagtail.images.blocks.ImageChooserBlock( + icon="image", template="includes/imageblock.html" + ), + ), + ("markdown", wagtailmarkdown.blocks.MarkdownBlock(icon="code")), + ("embed", wagtail.embeds.blocks.EmbedBlock(icon="code")), + ("raw_html", wagtail.blocks.RawHTMLBlock(icon="placeholder")), + ( + "table", + wagtail.contrib.table_block.blocks.TableBlock( + table_options={"renderer": "html"}, + template="includes/tableblock.html", + ), + ), + ], + blank=True, + ), ), ] diff --git a/ietf/topics/migrations/0004_auto_20220902_0524.py b/ietf/topics/migrations/0004_auto_20220902_0524.py new file mode 100644 index 00000000..4e792178 --- /dev/null +++ b/ietf/topics/migrations/0004_auto_20220902_0524.py @@ -0,0 +1,29 @@ +# Generated by Django 3.2.13 on 2022-09-02 04:24 + +from django.db import migrations +import wagtail.blocks +import wagtail.contrib.table_block.blocks +import wagtail.embeds.blocks +import wagtail.fields +import wagtail.images.blocks +import wagtailmarkdown.blocks + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0003_auto_20211101_0113'), + ] + + operations = [ + migrations.AlterField( + model_name='primarytopicpage', + name='in_depth', + field=wagtail.fields.StreamField([('heading', wagtail.blocks.CharBlock(icon='title')), ('paragraph', wagtail.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.images.blocks.ImageChooserBlock(icon='image', template='includes/imageblock.html')), ('markdown', wagtailmarkdown.blocks.MarkdownBlock(icon='code')), ('embed', wagtail.embeds.blocks.EmbedBlock(icon='code')), ('raw_html', wagtail.blocks.RawHTMLBlock(icon='placeholder')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}, template='includes/tableblock.html'))], blank=True, use_json_field=True), + ), + migrations.AlterField( + model_name='primarytopicpage', + name='key_info', + field=wagtail.fields.StreamField([('heading', wagtail.blocks.CharBlock(icon='title')), ('paragraph', wagtail.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.images.blocks.ImageChooserBlock(icon='image', template='includes/imageblock.html')), ('markdown', wagtailmarkdown.blocks.MarkdownBlock(icon='code')), ('embed', wagtail.embeds.blocks.EmbedBlock(icon='code')), ('raw_html', wagtail.blocks.RawHTMLBlock(icon='placeholder')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}, template='includes/tableblock.html'))], blank=True, use_json_field=True), + ), + ] diff --git a/ietf/topics/models.py b/ietf/topics/models.py index 18bd8edd..12e90c2a 100644 --- a/ietf/topics/models.py +++ b/ietf/topics/models.py @@ -1,19 +1,12 @@ from django.db import models - from modelcluster.fields import ParentalKey - -from wagtail.core.models import Page, Orderable -from wagtail.core.fields import StreamField -from wagtail.admin.edit_handlers import ( - FieldPanel, StreamFieldPanel, InlinePanel -) +from wagtail.admin.panels import FieldPanel, InlinePanel +from wagtail.fields import StreamField +from wagtail.models import Orderable, Page from wagtail.search import index -from wagtail.snippets.edit_handlers import ( - SnippetChooserPanel -) -from ..utils.models import PromoteMixin, RelatedLink from ..utils.blocks import StandardBlock +from ..utils.models import PromoteMixin, RelatedLink class TopicIndexPage(Page, PromoteMixin): @@ -21,18 +14,20 @@ class TopicIndexPage(Page, PromoteMixin): This page organises topics. The :model:`topics.PrimaryTopicPage` page should be placed beneath it in the page heirarchy. """ + introduction = models.CharField( max_length=255, help_text="Enter the introduction to display on the page, " - "you can use only 255 characters." + "you can use only 255 characters.", ) search_fields = Page.search_fields + [ - index.SearchField('introduction'), + index.SearchField("introduction"), ] - subpage_types = ['topics.PrimaryTopicPage', - ] + subpage_types = [ + "topics.PrimaryTopicPage", + ] @property def primary_topics(self): @@ -41,18 +36,16 @@ def primary_topics(self): class Meta: verbose_name = "Topic Page List" + TopicIndexPage.content_panels = Page.content_panels + [ - FieldPanel('introduction'), + FieldPanel("introduction"), ] TopicIndexPage.promote_panels = Page.promote_panels + PromoteMixin.panels - - class PrimaryTopicPageRelatedLink(Orderable, RelatedLink): - page = ParentalKey('topics.PrimaryTopicPage', - related_name='related_links') + page = ParentalKey("topics.PrimaryTopicPage", related_name="related_links") class PrimaryTopicPage(Page, PromoteMixin): @@ -60,31 +53,34 @@ class PrimaryTopicPage(Page, PromoteMixin): When this page is saved a :model:`snippets.PrimaryTopic` snippet is created. The snippet is used for organising blog posts. """ + introduction = models.CharField( blank=True, max_length=255, - help_text="Enter the title to display on the page, you can use only 255 characters." + help_text="Enter the title to display on the page, you can use only 255 characters.", ) - key_info = StreamField(StandardBlock(required=False), blank=True) - in_depth = StreamField(StandardBlock(required=False), blank=True) + key_info = StreamField(StandardBlock(required=False), blank=True, use_json_field=True) + in_depth = StreamField(StandardBlock(required=False), blank=True, use_json_field=True) call_to_action = models.ForeignKey( - 'snippets.CallToAction', - null=True, blank=True, + "snippets.CallToAction", + null=True, + blank=True, on_delete=models.SET_NULL, - related_name='+', - help_text="Will only be displayed if no mailing list signup is selected." + related_name="+", + help_text="Will only be displayed if no mailing list signup is selected.", ) mailing_list_signup = models.ForeignKey( - 'snippets.MailingListSignup', + "snippets.MailingListSignup", null=True, - blank=True, on_delete=models.SET_NULL, - related_name='+' + blank=True, + on_delete=models.SET_NULL, + related_name="+", ) search_fields = Page.search_fields + [ - index.SearchField('introduction'), - index.SearchField('key_info'), - index.SearchField('in_depth'), + index.SearchField("introduction"), + index.SearchField("key_info"), + index.SearchField("in_depth"), ] @property @@ -100,14 +96,12 @@ class Meta: PrimaryTopicPage.content_panels = Page.content_panels + [ - FieldPanel('introduction'), - StreamFieldPanel('key_info'), - StreamFieldPanel('in_depth'), - SnippetChooserPanel('call_to_action'), - SnippetChooserPanel('mailing_list_signup'), - InlinePanel('related_links', label="Related Links"), + FieldPanel("introduction"), + FieldPanel("key_info"), + FieldPanel("in_depth"), + FieldPanel("call_to_action"), + FieldPanel("mailing_list_signup"), + InlinePanel("related_links", label="Related Links"), ] PrimaryTopicPage.promote_panels = Page.promote_panels + PromoteMixin.panels - - diff --git a/ietf/topics/test.py b/ietf/topics/test.py index b49d53be..01e239bf 100644 --- a/ietf/topics/test.py +++ b/ietf/topics/test.py @@ -1,23 +1,22 @@ from django.test import TestCase +from wagtail.models import Page, Site -from .models import TopicIndexPage, PrimaryTopicPage from ..home.models import HomePage +from .models import PrimaryTopicPage, TopicIndexPage -from wagtail.core.models import Page, Site class StandardPageTests(TestCase): - def test_standard_page(self): root = Page.get_first_root_node() home = HomePage( - slug = 'homepageslug', - title = 'home page title', - heading = 'home page heading', - introduction = 'home page introduction', - request_for_comments_section_body = 'rfc section body', - working_groups_section_body = 'wg section body', + slug="homepageslug", + title="home page title", + heading="home page heading", + introduction="home page introduction", + request_for_comments_section_body="rfc section body", + working_groups_section_body="wg section body", ) root.add_child(instance=home) @@ -25,25 +24,25 @@ def test_standard_page(self): Site.objects.all().delete() Site.objects.create( - hostname='localhost', - root_page = home, + hostname="localhost", + root_page=home, is_default_site=True, - site_name='testingsitename', + site_name="testingsitename", ) topicindex = TopicIndexPage( - slug = 'topicindex', - title = 'topic index page title', - introduction = 'topic index page introduction' + slug="topicindex", + title="topic index page title", + introduction="topic index page introduction", ) - home.add_child(instance = topicindex) + home.add_child(instance=topicindex) topicpage = PrimaryTopicPage( - slug = 'topic', - title = 'topic title', - introduction = 'topic introduction', + slug="topic", + title="topic title", + introduction="topic introduction", ) - topicindex.add_child(instance = topicpage) + topicindex.add_child(instance=topicpage) rindex = self.client.get(path=topicindex.url) self.assertEqual(rindex.status_code, 200) diff --git a/ietf/urls.py b/ietf/urls.py index c96d1508..d1621e1f 100644 --- a/ietf/urls.py +++ b/ietf/urls.py @@ -2,9 +2,9 @@ from django.conf.urls import include, url from django.contrib import admin from django.urls import path +from wagtail import urls as wagtail_urls from wagtail.admin import urls as wagtailadmin_urls from wagtail.contrib.sitemaps.views import sitemap -from wagtail.core import urls as wagtail_urls from wagtail.documents import urls as wagtaildocs_urls from ietf.bibliography import urls as bibliography_urls @@ -16,7 +16,7 @@ urlpatterns = [ - path('sitemap.xml', sitemap), + path("sitemap.xml", sitemap), url(r"^admin/doc/", include("django.contrib.admindocs.urls")), url(r"^bibliography/", include(bibliography_urls)), url(r"^django-admin/", admin.site.urls), diff --git a/ietf/utils/blocks.py b/ietf/utils/blocks.py index f5acc325..2253fd79 100644 --- a/ietf/utils/blocks.py +++ b/ietf/utils/blocks.py @@ -1,17 +1,17 @@ -from wagtail.core.blocks import ( - CharBlock, RichTextBlock, StreamBlock, RawHTMLBlock -) +from wagtail.blocks import CharBlock, RawHTMLBlock, RichTextBlock, StreamBlock from wagtail.contrib.table_block.blocks import TableBlock -from wagtail.images.blocks import ImageChooserBlock from wagtail.embeds.blocks import EmbedBlock +from wagtail.images.blocks import ImageChooserBlock from wagtailmarkdown.blocks import MarkdownBlock class StandardBlock(StreamBlock): heading = CharBlock(icon="title") paragraph = RichTextBlock(icon="pilcrow") - image = ImageChooserBlock(icon="image", template='includes/imageblock.html') + image = ImageChooserBlock(icon="image", template="includes/imageblock.html") markdown = MarkdownBlock(icon="code") embed = EmbedBlock(icon="code") raw_html = RawHTMLBlock(icon="placeholder") - table = TableBlock(table_options={'renderer': 'html'}, template="includes/tableblock.html") + table = TableBlock( + table_options={"renderer": "html"}, template="includes/tableblock.html" + ) diff --git a/ietf/utils/models.py b/ietf/utils/models.py index c88eb35b..0b5e9cfc 100644 --- a/ietf/utils/models.py +++ b/ietf/utils/models.py @@ -3,16 +3,13 @@ from django.utils.translation import gettext_lazy as _ from modelcluster.fields import ParentalKey from modelcluster.models import ClusterableModel -from wagtail.admin.edit_handlers import ( +from wagtail.admin.panels import ( FieldPanel, InlinePanel, MultiFieldPanel, - PageChooserPanel, ) from wagtail.contrib.settings.models import BaseSetting, register_setting -from wagtail.core.models import Orderable -from wagtail.documents.edit_handlers import DocumentChooserPanel -from wagtail.images.edit_handlers import ImageChooserPanel +from wagtail.models import Orderable from wagtailorderable.models import Orderable as WagtailOrderable @@ -44,8 +41,8 @@ def link(self): panels = [ FieldPanel("link_external"), - PageChooserPanel("link_page"), - DocumentChooserPanel("link_document"), + FieldPanel("link_page"), + FieldPanel("link_document"), ] class Meta: @@ -91,8 +88,8 @@ class PromoteMixin(models.Model): MultiFieldPanel( [ FieldPanel("social_text"), - ImageChooserPanel("social_image"), - ImageChooserPanel("feed_image"), + FieldPanel("social_image"), + FieldPanel("feed_image"), ], "Social/Meta descriptions", ) @@ -122,7 +119,7 @@ def url(self): def title(self): return self.text or getattr(self.page, "title", "") - panels = [PageChooserPanel("page"), FieldPanel("link"), FieldPanel("text")] + panels = [FieldPanel("page"), FieldPanel("link"), FieldPanel("text")] class MenuItem(ClusterableModel, WagtailOrderable): @@ -136,7 +133,7 @@ class MenuItem(ClusterableModel, WagtailOrderable): text = models.CharField(max_length=40, blank=True) panels = [ FieldPanel("text"), - PageChooserPanel("page"), + FieldPanel("page"), InlinePanel( "sub_menu_items", label="Sub Menu Items", @@ -150,7 +147,7 @@ def is_dropdown(self): @property def title(self): return self.text or getattr(self.page, "title", "") - + class Meta: verbose_name_plural = "Secondary Menu" @@ -191,7 +188,7 @@ class SocialMediaSettings(BaseSetting): FieldPanel("twitter_handle"), FieldPanel("facebook_app_id"), FieldPanel("default_sharing_text"), - ImageChooserPanel("default_sharing_image"), + FieldPanel("default_sharing_image"), FieldPanel("site_name"), ] diff --git a/ietf/utils/templatetags/ietf_tags.py b/ietf/utils/templatetags/ietf_tags.py index 8af42f86..394379a9 100644 --- a/ietf/utils/templatetags/ietf_tags.py +++ b/ietf/utils/templatetags/ietf_tags.py @@ -1,12 +1,10 @@ from urllib.parse import quote from django.template import Library - -from wagtail.core.models import Site +from wagtail.models import Site from ..models import SocialMediaSettings - register = Library() @@ -26,19 +24,20 @@ def social_text(page, site, encode=False): return text - @register.simple_tag(takes_context=False) def social_image(page, site): image = "" if page and site: try: - image = page.social_image.get_rendition('original').url + image = page.social_image.get_rendition("original").url except (AttributeError, ValueError): try: - image = SocialMediaSettings.for_site( - site - ).default_sharing_image.get_rendition('original').url + image = ( + SocialMediaSettings.for_site(site) + .default_sharing_image.get_rendition("original") + .url + ) except (AttributeError, ValueError): pass diff --git a/ietf/utils/tests.py b/ietf/utils/tests.py index 40d45c8b..940f4d48 100644 --- a/ietf/utils/tests.py +++ b/ietf/utils/tests.py @@ -1,6 +1,6 @@ from django.test import TestCase -from wagtail.core.models import Page, Site -from wagtail.tests.utils import WagtailTestUtils +from wagtail.models import Page, Site +from wagtail.test.utils import WagtailTestUtils from ietf.events.models import EventListingPage, EventPage from ietf.utils.models import MenuItem diff --git a/ietf/utils/wagtail_hooks.py b/ietf/utils/wagtail_hooks.py index e6452f00..fe81ea0d 100644 --- a/ietf/utils/wagtail_hooks.py +++ b/ietf/utils/wagtail_hooks.py @@ -1,7 +1,7 @@ from django.conf import settings from django.utils.html import format_html +from wagtail import hooks from wagtail.contrib.modeladmin.options import ModelAdmin, modeladmin_register -from wagtail.core import hooks from wagtailorderable.modeladmin.mixins import OrderableMixin from ietf.utils.models import MenuItem diff --git a/requirements.txt b/requirements.txt index 082027f0..75a121ad 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ Django>=3.0,<3.3 djangorestframework>=3.8 psycopg2-binary>=2.7.5 -wagtail>=2.15.1,<2.16 +wagtail>=3.0,<3.1 django-libsass>=0.8 libsass>=0.8.3 future>=0.15.2