diff --git a/exhibits/migrations/0012_alter_exhibitpage_body.py b/exhibits/migrations/0012_alter_exhibitpage_body.py new file mode 100644 index 0000000..0bab7e6 --- /dev/null +++ b/exhibits/migrations/0012_alter_exhibitpage_body.py @@ -0,0 +1,371 @@ +# Generated by Django 5.0.6 on 2024-05-21 22:05 + +import exhibits.models +import ov_collections.blocks +import wagtail.blocks +import wagtail.fields +import wagtail.images.blocks +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("exhibits", "0011_alter_exhibitpage_body"), + ] + + operations = [ + migrations.AlterField( + model_name="exhibitpage", + name="body", + field=wagtail.fields.StreamField( + [ + ( + "interviews", + wagtail.blocks.StructBlock( + [ + ( + "guids", + wagtail.blocks.TextBlock( + help_text="AAPB record IDs, separated by whitespace", + required=True, + ), + ), + ( + "show_title", + wagtail.blocks.BooleanBlock( + default=True, + help_text="Show asset title(s)", + required=False, + ), + ), + ( + "show_thumbnail", + wagtail.blocks.BooleanBlock( + default=True, + help_text="Show asset thumbnail(s)", + required=False, + ), + ), + ( + "title", + wagtail.blocks.RichTextBlock( + features=["italic"], + help_text="The title of this group", + max_length=1024, + required=False, + ), + ), + ( + "start_time", + ov_collections.blocks.DurationBlock( + help_text="Start time for the group", + required=False, + ), + ), + ( + "end_time", + ov_collections.blocks.DurationBlock( + help_text="End time for the group", + required=False, + ), + ), + ], + icon="openquote", + label="Interviews", + ), + ), + ( + "archival_footage", + wagtail.blocks.StructBlock( + [ + ( + "guids", + wagtail.blocks.TextBlock( + help_text="AAPB record IDs, separated by whitespace", + required=True, + ), + ), + ( + "show_title", + wagtail.blocks.BooleanBlock( + default=True, + help_text="Show asset title(s)", + required=False, + ), + ), + ( + "show_thumbnail", + wagtail.blocks.BooleanBlock( + default=True, + help_text="Show asset thumbnail(s)", + required=False, + ), + ), + ( + "title", + wagtail.blocks.RichTextBlock( + features=["italic"], + help_text="The title of this group", + max_length=1024, + required=False, + ), + ), + ( + "start_time", + ov_collections.blocks.DurationBlock( + help_text="Start time for the group", + required=False, + ), + ), + ( + "end_time", + ov_collections.blocks.DurationBlock( + help_text="End time for the group", + required=False, + ), + ), + ], + icon="clipboard-list", + label="Archival Footage", + ), + ), + ( + "photographs", + wagtail.blocks.StructBlock( + [ + ( + "guids", + wagtail.blocks.TextBlock( + help_text="AAPB record IDs, separated by whitespace", + required=True, + ), + ), + ( + "show_title", + wagtail.blocks.BooleanBlock( + default=True, + help_text="Show asset title(s)", + required=False, + ), + ), + ( + "show_thumbnail", + wagtail.blocks.BooleanBlock( + default=True, + help_text="Show asset thumbnail(s)", + required=False, + ), + ), + ( + "title", + wagtail.blocks.RichTextBlock( + features=["italic"], + help_text="The title of this group", + max_length=1024, + required=False, + ), + ), + ( + "start_time", + ov_collections.blocks.DurationBlock( + help_text="Start time for the group", + required=False, + ), + ), + ( + "end_time", + ov_collections.blocks.DurationBlock( + help_text="End time for the group", + required=False, + ), + ), + ], + icon="copy", + label="Photographs", + ), + ), + ( + "original_footage", + wagtail.blocks.StructBlock( + [ + ( + "guids", + wagtail.blocks.TextBlock( + help_text="AAPB record IDs, separated by whitespace", + required=True, + ), + ), + ( + "show_title", + wagtail.blocks.BooleanBlock( + default=True, + help_text="Show asset title(s)", + required=False, + ), + ), + ( + "show_thumbnail", + wagtail.blocks.BooleanBlock( + default=True, + help_text="Show asset thumbnail(s)", + required=False, + ), + ), + ( + "title", + wagtail.blocks.RichTextBlock( + features=["italic"], + help_text="The title of this group", + max_length=1024, + required=False, + ), + ), + ( + "start_time", + ov_collections.blocks.DurationBlock( + help_text="Start time for the group", + required=False, + ), + ), + ( + "end_time", + ov_collections.blocks.DurationBlock( + help_text="End time for the group", + required=False, + ), + ), + ], + icon="doc-full-inverse", + label="Original Footage", + ), + ), + ( + "programs", + wagtail.blocks.StructBlock( + [ + ( + "guids", + wagtail.blocks.TextBlock( + help_text="AAPB record IDs, separated by whitespace", + required=True, + ), + ), + ( + "show_title", + wagtail.blocks.BooleanBlock( + default=True, + help_text="Show asset title(s)", + required=False, + ), + ), + ( + "show_thumbnail", + wagtail.blocks.BooleanBlock( + default=True, + help_text="Show asset thumbnail(s)", + required=False, + ), + ), + ( + "title", + wagtail.blocks.RichTextBlock( + features=["italic"], + help_text="The title of this group", + max_length=1024, + required=False, + ), + ), + ( + "start_time", + ov_collections.blocks.DurationBlock( + help_text="Start time for the group", + required=False, + ), + ), + ( + "end_time", + ov_collections.blocks.DurationBlock( + help_text="End time for the group", + required=False, + ), + ), + ], + icon="desktop", + label="Programs", + ), + ), + ( + "related_content", + wagtail.blocks.StructBlock( + [ + ( + "guids", + wagtail.blocks.TextBlock( + help_text="AAPB record IDs, separated by whitespace", + required=True, + ), + ), + ( + "show_title", + wagtail.blocks.BooleanBlock( + default=True, + help_text="Show asset title(s)", + required=False, + ), + ), + ( + "show_thumbnail", + wagtail.blocks.BooleanBlock( + default=True, + help_text="Show asset thumbnail(s)", + required=False, + ), + ), + ( + "title", + wagtail.blocks.RichTextBlock( + features=["italic"], + help_text="The title of this group", + max_length=1024, + required=False, + ), + ), + ( + "start_time", + ov_collections.blocks.DurationBlock( + help_text="Start time for the group", + required=False, + ), + ), + ( + "end_time", + ov_collections.blocks.DurationBlock( + help_text="End time for the group", + required=False, + ), + ), + ], + icon="table", + label="Related Content", + ), + ), + ("credits", wagtail.blocks.RichTextBlock(icon="form")), + ( + "heading", + wagtail.blocks.RichTextBlock( + features=["italic"], form_classname="title", icon="title" + ), + ), + ("text", exhibits.models.RichTextFootnotesBlock()), + ("image", wagtail.images.blocks.ImageChooserBlock()), + ( + "subheading", + wagtail.blocks.RichTextBlock( + features=["italic"], form_classname="title", icon="title" + ), + ), + ("html", wagtail.blocks.RawHTMLBlock(label="HTML")), + ] + ), + ), + ] diff --git a/ov_collections/blocks.py b/ov_collections/blocks.py index 2e37453..98f3e39 100644 --- a/ov_collections/blocks.py +++ b/ov_collections/blocks.py @@ -1,5 +1,8 @@ +from django.utils.dateparse import parse_duration +from django.utils.functional import cached_property from wagtail.blocks import ( BooleanBlock, + FieldBlock, RichTextBlock, StructBlock, TextBlock, @@ -8,14 +11,45 @@ from wagtail.images.blocks import ImageChooserBlock -class ContentBlock(StructBlock): - """Generic External link block +class DurationBlock(FieldBlock): + def __init__( + self, required=True, help_text=None, format=None, validators=(), **kwargs + ): + self.field_options = { + "required": required, + "help_text": help_text, + "validators": validators, + } + self.format = format + super().__init__(**kwargs) - This is the base block for a generic external link. All fields are required + @cached_property + def field(self): + from django.forms import DurationField - Attributes: - title: RichTextBlock with italics only - link: URLBlock + field_kwargs = {} + # TODO: Add an AdminDurationInput widget + field_kwargs.update(self.field_options) + return DurationField(**field_kwargs) + + def to_python(self, value): + from datetime import timedelta + + if value is None or isinstance(value, timedelta): + return value + else: + return parse_duration(value) + + class Meta: + icon = "time" + + +class ContentBlock(StructBlock): + """Generic External link block + ) + from wagtail.images.blocks import ImageChooserBlock + title: RichTextBlock with italics only + link: URLBlock """ @@ -53,6 +87,8 @@ class AAPBRecordsBlock(StructBlock): show_title: Show the title of records on the page show_thumbnail: Show the thumbnail of records on the page title: Optional title of the group + start_time: Optional start time for the group + end_time: Optional end time for the group """ guids = TextBlock( @@ -74,3 +110,27 @@ class AAPBRecordsBlock(StructBlock): help_text='The title of this group', features=['italic'], ) + + start_time = DurationBlock( + required=False, + help_text='Start time for the group', + ) + + end_time = DurationBlock( + required=False, + help_text='End time for the group', + ) + + def clean(self, value): + data = super(AAPBRecordsBlock, self).clean(value) + + # Ensure that start_time is before end_time + if ( + data.get('start_time') + and data.get('end_time') + and data['start_time'] > data['end_time'] + ): + from django.core.exceptions import ValidationError + + raise ValidationError('Start time must be before end time') + return data diff --git a/ov_collections/factories.py b/ov_collections/factories.py new file mode 100644 index 0000000..2c0fcf2 --- /dev/null +++ b/ov_collections/factories.py @@ -0,0 +1,12 @@ +from factory import SubFactory +from wagtail_factories import ImageChooserBlockFactory, PageFactory + +from .models import Collection + + +class CollectionPageFactory(PageFactory): + cover_image = SubFactory(ImageChooserBlockFactory) + hero_image = SubFactory(ImageChooserBlockFactory) + + class Meta: + model = Collection diff --git a/ov_collections/migrations/0014_alter_collection_content.py b/ov_collections/migrations/0014_alter_collection_content.py new file mode 100644 index 0000000..0ea8734 --- /dev/null +++ b/ov_collections/migrations/0014_alter_collection_content.py @@ -0,0 +1,364 @@ +# Generated by Django 5.0.6 on 2024-05-21 22:05 + +import ov_collections.blocks +import wagtail.blocks +import wagtail.fields +import wagtail.images.blocks +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("ov_collections", "0013_alter_collection_content"), + ] + + operations = [ + migrations.AlterField( + model_name="collection", + name="content", + field=wagtail.fields.StreamField( + [ + ( + "interviews", + wagtail.blocks.StructBlock( + [ + ( + "guids", + wagtail.blocks.TextBlock( + help_text="AAPB record IDs, separated by whitespace", + required=True, + ), + ), + ( + "show_title", + wagtail.blocks.BooleanBlock( + default=True, + help_text="Show asset title(s)", + required=False, + ), + ), + ( + "show_thumbnail", + wagtail.blocks.BooleanBlock( + default=True, + help_text="Show asset thumbnail(s)", + required=False, + ), + ), + ( + "title", + wagtail.blocks.RichTextBlock( + features=["italic"], + help_text="The title of this group", + max_length=1024, + required=False, + ), + ), + ( + "start_time", + ov_collections.blocks.DurationBlock( + help_text="Start time for the group", + required=False, + ), + ), + ( + "end_time", + ov_collections.blocks.DurationBlock( + help_text="End time for the group", + required=False, + ), + ), + ], + icon="openquote", + label="Interviews", + ), + ), + ( + "archival_footage", + wagtail.blocks.StructBlock( + [ + ( + "guids", + wagtail.blocks.TextBlock( + help_text="AAPB record IDs, separated by whitespace", + required=True, + ), + ), + ( + "show_title", + wagtail.blocks.BooleanBlock( + default=True, + help_text="Show asset title(s)", + required=False, + ), + ), + ( + "show_thumbnail", + wagtail.blocks.BooleanBlock( + default=True, + help_text="Show asset thumbnail(s)", + required=False, + ), + ), + ( + "title", + wagtail.blocks.RichTextBlock( + features=["italic"], + help_text="The title of this group", + max_length=1024, + required=False, + ), + ), + ( + "start_time", + ov_collections.blocks.DurationBlock( + help_text="Start time for the group", + required=False, + ), + ), + ( + "end_time", + ov_collections.blocks.DurationBlock( + help_text="End time for the group", + required=False, + ), + ), + ], + icon="clipboard-list", + label="Archival Footage", + ), + ), + ( + "photographs", + wagtail.blocks.StructBlock( + [ + ( + "guids", + wagtail.blocks.TextBlock( + help_text="AAPB record IDs, separated by whitespace", + required=True, + ), + ), + ( + "show_title", + wagtail.blocks.BooleanBlock( + default=True, + help_text="Show asset title(s)", + required=False, + ), + ), + ( + "show_thumbnail", + wagtail.blocks.BooleanBlock( + default=True, + help_text="Show asset thumbnail(s)", + required=False, + ), + ), + ( + "title", + wagtail.blocks.RichTextBlock( + features=["italic"], + help_text="The title of this group", + max_length=1024, + required=False, + ), + ), + ( + "start_time", + ov_collections.blocks.DurationBlock( + help_text="Start time for the group", + required=False, + ), + ), + ( + "end_time", + ov_collections.blocks.DurationBlock( + help_text="End time for the group", + required=False, + ), + ), + ], + icon="copy", + label="Photographs", + ), + ), + ( + "original_footage", + wagtail.blocks.StructBlock( + [ + ( + "guids", + wagtail.blocks.TextBlock( + help_text="AAPB record IDs, separated by whitespace", + required=True, + ), + ), + ( + "show_title", + wagtail.blocks.BooleanBlock( + default=True, + help_text="Show asset title(s)", + required=False, + ), + ), + ( + "show_thumbnail", + wagtail.blocks.BooleanBlock( + default=True, + help_text="Show asset thumbnail(s)", + required=False, + ), + ), + ( + "title", + wagtail.blocks.RichTextBlock( + features=["italic"], + help_text="The title of this group", + max_length=1024, + required=False, + ), + ), + ( + "start_time", + ov_collections.blocks.DurationBlock( + help_text="Start time for the group", + required=False, + ), + ), + ( + "end_time", + ov_collections.blocks.DurationBlock( + help_text="End time for the group", + required=False, + ), + ), + ], + icon="doc-full-inverse", + label="Original Footage", + ), + ), + ( + "programs", + wagtail.blocks.StructBlock( + [ + ( + "guids", + wagtail.blocks.TextBlock( + help_text="AAPB record IDs, separated by whitespace", + required=True, + ), + ), + ( + "show_title", + wagtail.blocks.BooleanBlock( + default=True, + help_text="Show asset title(s)", + required=False, + ), + ), + ( + "show_thumbnail", + wagtail.blocks.BooleanBlock( + default=True, + help_text="Show asset thumbnail(s)", + required=False, + ), + ), + ( + "title", + wagtail.blocks.RichTextBlock( + features=["italic"], + help_text="The title of this group", + max_length=1024, + required=False, + ), + ), + ( + "start_time", + ov_collections.blocks.DurationBlock( + help_text="Start time for the group", + required=False, + ), + ), + ( + "end_time", + ov_collections.blocks.DurationBlock( + help_text="End time for the group", + required=False, + ), + ), + ], + icon="desktop", + label="Programs", + ), + ), + ( + "related_content", + wagtail.blocks.StructBlock( + [ + ( + "guids", + wagtail.blocks.TextBlock( + help_text="AAPB record IDs, separated by whitespace", + required=True, + ), + ), + ( + "show_title", + wagtail.blocks.BooleanBlock( + default=True, + help_text="Show asset title(s)", + required=False, + ), + ), + ( + "show_thumbnail", + wagtail.blocks.BooleanBlock( + default=True, + help_text="Show asset thumbnail(s)", + required=False, + ), + ), + ( + "title", + wagtail.blocks.RichTextBlock( + features=["italic"], + help_text="The title of this group", + max_length=1024, + required=False, + ), + ), + ( + "start_time", + ov_collections.blocks.DurationBlock( + help_text="Start time for the group", + required=False, + ), + ), + ( + "end_time", + ov_collections.blocks.DurationBlock( + help_text="End time for the group", + required=False, + ), + ), + ], + icon="table", + label="Related Content", + ), + ), + ("credits", wagtail.blocks.RichTextBlock(icon="form")), + ( + "heading", + wagtail.blocks.RichTextBlock( + features=["italic"], form_classname="title", icon="title" + ), + ), + ("text", wagtail.blocks.RichTextBlock()), + ("image", wagtail.images.blocks.ImageChooserBlock()), + ("html", wagtail.blocks.RawHTMLBlock(label="HTML")), + ] + ), + ), + ] diff --git a/ov_collections/tests.py b/ov_collections/tests.py index a39b155..f17b537 100644 --- a/ov_collections/tests.py +++ b/ov_collections/tests.py @@ -1 +1,13 @@ +from django.test import TestCase + +from ov_collections.factories import CollectionPageFactory +from ov_collections.models import Collection + + # Create your tests here. +class ExhibitPageTests(TestCase): + def test_exhibit_page_factory(self): + """ + ExhibitPageFactory creates ExhibitPage model instances + """ + self.assertIsInstance(CollectionPageFactory.create(), Collection)