diff --git a/ov_collections/__init__.py b/ov_collections/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ov_collections/admin.py b/ov_collections/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/ov_collections/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/ov_collections/apps.py b/ov_collections/apps.py new file mode 100644 index 0000000..736d46e --- /dev/null +++ b/ov_collections/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class CollectionsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'ov_collections' diff --git a/ov_collections/blocks.py b/ov_collections/blocks.py new file mode 100644 index 0000000..c5c8f17 --- /dev/null +++ b/ov_collections/blocks.py @@ -0,0 +1,29 @@ +from wagtail.core.blocks import StructBlock, CharBlock, URLBlock +from wagtail.images.blocks import ImageChooserBlock + + +class ContentBlock(StructBlock): + """Generic content block + - title + - link + + All fields are required + """ + + title = CharBlock( + required=True, max_length=1024, help_text='The title of this content' + ) + link = URLBlock(required=True) + + +class ContentImageBlock(ContentBlock): + """Generic content block with image + - image: required + """ + + image = ImageChooserBlock(required=True) + + def get_api_representation(self, value, context=None): + results = super().get_api_representation(value, context) + results['image'] = value.get('image').get_rendition('width-400').attrs_dict + return results diff --git a/ov_collections/migrations/0001_initial.py b/ov_collections/migrations/0001_initial.py new file mode 100644 index 0000000..f3f937e --- /dev/null +++ b/ov_collections/migrations/0001_initial.py @@ -0,0 +1,33 @@ +# Generated by Django 4.1.2 on 2022-10-06 20:24 + +from django.db import migrations, models +import django.db.models.deletion +import wagtail.blocks +import wagtail.fields +import wagtail.images.blocks + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('wagtailimages', '0024_index_image_file_hash'), + ('wagtailcore', '0077_alter_revision_user'), + ] + + operations = [ + migrations.CreateModel( + name='Collection', + 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')), + ('about', wagtail.fields.RichTextField(blank=True)), + ('content', wagtail.fields.StreamField([('heading', wagtail.blocks.CharBlock(form_classname='title')), ('text', wagtail.blocks.TextBlock()), ('image', wagtail.images.blocks.ImageChooserBlock())], use_json_field=True)), + ('cover_image', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.image')), + ], + options={ + 'abstract': False, + }, + bases=('wagtailcore.page',), + ), + ] diff --git a/ov_collections/migrations/0002_alter_collection_content.py b/ov_collections/migrations/0002_alter_collection_content.py new file mode 100644 index 0000000..c967e05 --- /dev/null +++ b/ov_collections/migrations/0002_alter_collection_content.py @@ -0,0 +1,21 @@ +# Generated by Django 4.1.2 on 2022-10-13 18:14 + +from django.db import migrations +import wagtail.blocks +import wagtail.fields +import wagtail.images.blocks + + +class Migration(migrations.Migration): + + dependencies = [ + ('ov_collections', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='collection', + name='content', + field=wagtail.fields.StreamField([('heading', wagtail.blocks.CharBlock(form_classname='title')), ('text', wagtail.blocks.TextBlock()), ('image', wagtail.images.blocks.ImageChooserBlock()), ('Interviews', wagtail.blocks.StructBlock([('interviews', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('interview', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(help_text='The title of this content', max_length=1024, required=True)), ('image', wagtail.images.blocks.ImageChooserBlock(required=True)), ('link', wagtail.blocks.URLBlock(required=True))]))])))]))], use_json_field=True), + ), + ] diff --git a/ov_collections/migrations/0003_alter_collection_content.py b/ov_collections/migrations/0003_alter_collection_content.py new file mode 100644 index 0000000..5a60812 --- /dev/null +++ b/ov_collections/migrations/0003_alter_collection_content.py @@ -0,0 +1,21 @@ +# Generated by Django 4.1.2 on 2022-10-14 16:05 + +from django.db import migrations +import wagtail.blocks +import wagtail.fields +import wagtail.images.blocks + + +class Migration(migrations.Migration): + + dependencies = [ + ('ov_collections', '0002_alter_collection_content'), + ] + + operations = [ + migrations.AlterField( + model_name='collection', + name='content', + field=wagtail.fields.StreamField([('heading', wagtail.blocks.CharBlock(form_classname='title')), ('text', wagtail.blocks.TextBlock()), ('image', wagtail.images.blocks.ImageChooserBlock()), ('interviews', wagtail.blocks.StructBlock([('interviews', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('interview', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(help_text='The title of this content', max_length=1024, required=True)), ('image', wagtail.images.blocks.ImageChooserBlock(required=True)), ('link', wagtail.blocks.URLBlock(required=True))]))])))])), ('archival_footage', wagtail.blocks.StructBlock([('footage', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('footage', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(help_text='The title of this content', max_length=1024, required=True)), ('image', wagtail.images.blocks.ImageChooserBlock(required=True)), ('link', wagtail.blocks.URLBlock(required=True))]))])))]))], use_json_field=True), + ), + ] diff --git a/ov_collections/migrations/0004_alter_collection_content.py b/ov_collections/migrations/0004_alter_collection_content.py new file mode 100644 index 0000000..11182f7 --- /dev/null +++ b/ov_collections/migrations/0004_alter_collection_content.py @@ -0,0 +1,21 @@ +# Generated by Django 4.1.2 on 2022-10-14 21:56 + +from django.db import migrations +import wagtail.blocks +import wagtail.fields +import wagtail.images.blocks + + +class Migration(migrations.Migration): + + dependencies = [ + ('ov_collections', '0003_alter_collection_content'), + ] + + operations = [ + migrations.AlterField( + model_name='collection', + name='content', + field=wagtail.fields.StreamField([('heading', wagtail.blocks.CharBlock(form_classname='title')), ('text', wagtail.blocks.TextBlock()), ('image', wagtail.images.blocks.ImageChooserBlock()), ('interviews', wagtail.blocks.StructBlock([('interviews', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('interview', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(help_text='The title of this content', max_length=1024, required=True)), ('image', wagtail.images.blocks.ImageChooserBlock(required=True)), ('link', wagtail.blocks.URLBlock(required=True))]))])))])), ('archival_footage', wagtail.blocks.StructBlock([('footage', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('footage', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(help_text='The title of this content', max_length=1024, required=True)), ('image', wagtail.images.blocks.ImageChooserBlock(required=True)), ('link', wagtail.blocks.URLBlock(required=True))]))])))])), ('photographs', wagtail.blocks.StructBlock([('photos', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('photos', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(help_text='The title of this content', max_length=1024, required=True)), ('image', wagtail.images.blocks.ImageChooserBlock(required=True)), ('link', wagtail.blocks.URLBlock(required=True))]))])))])), ('original_footage', wagtail.blocks.StructBlock([('footage', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('footage', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(help_text='The title of this content', max_length=1024, required=True)), ('image', wagtail.images.blocks.ImageChooserBlock(required=True)), ('link', wagtail.blocks.URLBlock(required=True))]))])))])), ('programs', wagtail.blocks.StructBlock([('programs', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('programs', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(help_text='The title of this content', max_length=1024, required=True)), ('image', wagtail.images.blocks.ImageChooserBlock(required=True)), ('link', wagtail.blocks.URLBlock(required=True))]))])))])), ('related_content', wagtail.blocks.StructBlock([('content', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('related_content', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(help_text='The title of this content', max_length=1024, required=True)), ('image', wagtail.images.blocks.ImageChooserBlock(required=True)), ('link', wagtail.blocks.URLBlock(required=True))]))])))])), ('credits', wagtail.blocks.StructBlock([('credits', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('credits', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(help_text='The title of this content', max_length=1024, required=True)), ('image', wagtail.images.blocks.ImageChooserBlock(required=True)), ('link', wagtail.blocks.URLBlock(required=True))]))])))]))], use_json_field=True), + ), + ] diff --git a/ov_collections/migrations/0005_alter_collection_content.py b/ov_collections/migrations/0005_alter_collection_content.py new file mode 100644 index 0000000..afc0f7e --- /dev/null +++ b/ov_collections/migrations/0005_alter_collection_content.py @@ -0,0 +1,21 @@ +# Generated by Django 4.1.2 on 2022-10-17 18:54 + +from django.db import migrations +import wagtail.blocks +import wagtail.fields +import wagtail.images.blocks + + +class Migration(migrations.Migration): + + dependencies = [ + ('ov_collections', '0004_alter_collection_content'), + ] + + operations = [ + migrations.AlterField( + model_name='collection', + name='content', + field=wagtail.fields.StreamField([('interviews', wagtail.blocks.StructBlock([('interviews', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('interview', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(help_text='The title of this content', max_length=1024, required=True)), ('link', wagtail.blocks.URLBlock(required=True)), ('image', wagtail.images.blocks.ImageChooserBlock(required=True))]))])))])), ('archival_footage', wagtail.blocks.StructBlock([('footage', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('footage', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(help_text='The title of this content', max_length=1024, required=True)), ('link', wagtail.blocks.URLBlock(required=True)), ('image', wagtail.images.blocks.ImageChooserBlock(required=True))]))])))])), ('photographs', wagtail.blocks.StructBlock([('photos', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('photos', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(help_text='The title of this content', max_length=1024, required=True)), ('link', wagtail.blocks.URLBlock(required=True)), ('image', wagtail.images.blocks.ImageChooserBlock(required=True))]))])))])), ('original_footage', wagtail.blocks.StructBlock([('footage', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('footage', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(help_text='The title of this content', max_length=1024, required=True)), ('link', wagtail.blocks.URLBlock(required=True)), ('image', wagtail.images.blocks.ImageChooserBlock(required=True))]))])))])), ('programs', wagtail.blocks.StructBlock([('programs', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('programs', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(help_text='The title of this content', max_length=1024, required=True)), ('link', wagtail.blocks.URLBlock(required=True))]))])))])), ('related_content', wagtail.blocks.StructBlock([('content', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('related_content', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(help_text='The title of this content', max_length=1024, required=True)), ('link', wagtail.blocks.URLBlock(required=True))]))])))])), ('credits', wagtail.blocks.RichTextBlock()), ('heading', wagtail.blocks.CharBlock(form_classname='title')), ('text', wagtail.blocks.TextBlock()), ('image', wagtail.images.blocks.ImageChooserBlock())], use_json_field=True), + ), + ] diff --git a/ov_collections/migrations/0006_alter_collection_content.py b/ov_collections/migrations/0006_alter_collection_content.py new file mode 100644 index 0000000..fa2af38 --- /dev/null +++ b/ov_collections/migrations/0006_alter_collection_content.py @@ -0,0 +1,21 @@ +# Generated by Django 4.1.2 on 2022-10-19 19:10 + +from django.db import migrations +import wagtail.blocks +import wagtail.fields +import wagtail.images.blocks + + +class Migration(migrations.Migration): + + dependencies = [ + ('ov_collections', '0005_alter_collection_content'), + ] + + operations = [ + migrations.AlterField( + model_name='collection', + name='content', + field=wagtail.fields.StreamField([('interviews', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(help_text='The title of this content', max_length=1024, required=True)), ('link', wagtail.blocks.URLBlock(required=True)), ('image', wagtail.images.blocks.ImageChooserBlock(required=True))]), icon='openquote')), ('archival_footage', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(help_text='The title of this content', max_length=1024, required=True)), ('link', wagtail.blocks.URLBlock(required=True)), ('image', wagtail.images.blocks.ImageChooserBlock(required=True))]), icon='form')), ('photographs', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(help_text='The title of this content', max_length=1024, required=True)), ('link', wagtail.blocks.URLBlock(required=True)), ('image', wagtail.images.blocks.ImageChooserBlock(required=True))]), icon='image')), ('original_footage', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(help_text='The title of this content', max_length=1024, required=True)), ('link', wagtail.blocks.URLBlock(required=True)), ('image', wagtail.images.blocks.ImageChooserBlock(required=True))]), icon='doc-full-inverse')), ('programs', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(help_text='The title of this content', max_length=1024, required=True)), ('link', wagtail.blocks.URLBlock(required=True))]), icon='clipboard-list')), ('related_content', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(help_text='The title of this content', max_length=1024, required=True)), ('link', wagtail.blocks.URLBlock(required=True))]), icon='list-ul')), ('credits', wagtail.blocks.RichTextBlock()), ('heading', wagtail.blocks.CharBlock(form_classname='title')), ('text', wagtail.blocks.TextBlock()), ('image', wagtail.images.blocks.ImageChooserBlock())], use_json_field=True), + ), + ] diff --git a/ov_collections/migrations/0007_rename_about_collection_introduction.py b/ov_collections/migrations/0007_rename_about_collection_introduction.py new file mode 100644 index 0000000..310a5bd --- /dev/null +++ b/ov_collections/migrations/0007_rename_about_collection_introduction.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.2 on 2022-10-19 21:01 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('ov_collections', '0006_alter_collection_content'), + ] + + operations = [ + migrations.RenameField( + model_name='collection', + old_name='about', + new_name='introduction', + ), + ] diff --git a/ov_collections/migrations/0008_alter_collection_content.py b/ov_collections/migrations/0008_alter_collection_content.py new file mode 100644 index 0000000..62b7bea --- /dev/null +++ b/ov_collections/migrations/0008_alter_collection_content.py @@ -0,0 +1,21 @@ +# Generated by Django 4.1.12 on 2023-10-30 19:42 + +from django.db import migrations +import wagtail.blocks +import wagtail.fields +import wagtail.images.blocks + + +class Migration(migrations.Migration): + + dependencies = [ + ('ov_collections', '0007_rename_about_collection_introduction'), + ] + + operations = [ + migrations.AlterField( + model_name='collection', + name='content', + field=wagtail.fields.StreamField([('interviews', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(help_text='The title of this content', max_length=1024, required=True)), ('link', wagtail.blocks.URLBlock(required=True)), ('image', wagtail.images.blocks.ImageChooserBlock(required=True))], icon='openquote', label='Interview'), icon='openquote')), ('archival_footage', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(help_text='The title of this content', max_length=1024, required=True)), ('link', wagtail.blocks.URLBlock(required=True)), ('image', wagtail.images.blocks.ImageChooserBlock(required=True))], icon='form', label='Footage'), icon='form')), ('photographs', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(help_text='The title of this content', max_length=1024, required=True)), ('link', wagtail.blocks.URLBlock(required=True)), ('image', wagtail.images.blocks.ImageChooserBlock(required=True))], icon='image', label='Photograph'), icon='image')), ('original_footage', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(help_text='The title of this content', max_length=1024, required=True)), ('link', wagtail.blocks.URLBlock(required=True)), ('image', wagtail.images.blocks.ImageChooserBlock(required=True))], icon='doc-full-inverse', label='Footage'), icon='doc-full-inverse')), ('programs', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(help_text='The title of this content', max_length=1024, required=True)), ('link', wagtail.blocks.URLBlock(required=True))], icon='clipboard-list', label='Program'), icon='clipboard-list')), ('related_content', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(help_text='The title of this content', max_length=1024, required=True)), ('link', wagtail.blocks.URLBlock(required=True))], icon='list-ul', label='Content'), icon='list-ul')), ('credits', wagtail.blocks.RichTextBlock()), ('heading', wagtail.blocks.CharBlock(form_classname='title')), ('text', wagtail.blocks.TextBlock()), ('image', wagtail.images.blocks.ImageChooserBlock())], use_json_field=True), + ), + ] diff --git a/ov_collections/migrations/__init__.py b/ov_collections/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ov_collections/models.py b/ov_collections/models.py new file mode 100644 index 0000000..a39d4ac --- /dev/null +++ b/ov_collections/models.py @@ -0,0 +1,89 @@ +from django.db import models +from wagtail.blocks import ListBlock, RichTextBlock, TextBlock, CharBlock +from wagtail.core.models import Page +from wagtail.core.fields import RichTextField, StreamField +from wagtail.search import index +from wagtail.admin.edit_handlers import FieldPanel +from wagtail.images.api.fields import ImageRenditionField +from wagtail.images.blocks import ImageChooserBlock +from wagtail.api import APIField +from .blocks import ContentImageBlock, ContentBlock + + +class Collection(Page): + introduction = RichTextField(blank=True) + + content = StreamField( + [ + ( + 'interviews', + ListBlock( + ContentImageBlock(label='Interview', icon='openquote'), + icon='openquote', + ), + ), + ( + 'archival_footage', + ListBlock(ContentImageBlock(label='Footage', icon='form'), icon='form'), + ), + ( + 'photographs', + ListBlock( + ContentImageBlock(label='Photograph', icon='image'), icon='image' + ), + ), + ( + 'original_footage', + ListBlock( + ContentImageBlock(label='Footage', icon='doc-full-inverse'), + icon='doc-full-inverse', + ), + ), + ( + 'programs', + ListBlock( + ContentBlock(label='Program', icon='clipboard-list'), + icon='clipboard-list', + ), + ), + ( + 'related_content', + ListBlock( + ContentBlock(label='Content', icon='list-ul'), icon='list-ul' + ), + ), + ('credits', RichTextBlock()), + ('heading', CharBlock(form_classname='title')), + ('text', TextBlock()), + ('image', ImageChooserBlock()), + ], + use_json_field=True, + ) + + cover_image = models.ForeignKey( + 'wagtailimages.Image', + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name='+', + ) + + search_fields = Page.search_fields + [ + index.SearchField('introduction'), + ] + + content_panels = Page.content_panels + [ + FieldPanel('introduction'), + FieldPanel('cover_image'), + FieldPanel('content'), + ] + + api_fields = [ + APIField('title'), + APIField('introduction'), + APIField( + 'cover_image', + serializer=ImageRenditionField('fill-1600x500'), + ), + APIField('content'), + ] diff --git a/ov_collections/tests.py b/ov_collections/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/ov_collections/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/ov_collections/views.py b/ov_collections/views.py new file mode 100644 index 0000000..4af84f0 --- /dev/null +++ b/ov_collections/views.py @@ -0,0 +1,12 @@ +from wagtail.api.v2.views import BaseAPIViewSet +from .models import Collection + + +class CollectionAPIViewSet(BaseAPIViewSet): + model = Collection + + listing_default_fields = BaseAPIViewSet.listing_default_fields + [ + 'title', + 'introduction', + 'cover_image', + ] diff --git a/ov_wag/api.py b/ov_wag/api.py index 124d91b..ca72a92 100644 --- a/ov_wag/api.py +++ b/ov_wag/api.py @@ -4,6 +4,7 @@ from wagtail.documents.api.v2.views import DocumentsAPIViewSet from authors.views import AuthorsAPIViewSet from exhibits.views import ExhibitsAPIViewSet +from ov_collections.views import CollectionAPIViewSet # Create the router. "wagtailapi" is the URL namespace api_router = WagtailAPIRouter('wagtailapi') @@ -17,3 +18,4 @@ api_router.register_endpoint('documents', DocumentsAPIViewSet) api_router.register_endpoint('authors', AuthorsAPIViewSet) api_router.register_endpoint('exhibits', ExhibitsAPIViewSet) +api_router.register_endpoint('collections', CollectionAPIViewSet) diff --git a/ov_wag/settings/base.py b/ov_wag/settings/base.py index a38e664..9df4b2f 100644 --- a/ov_wag/settings/base.py +++ b/ov_wag/settings/base.py @@ -27,6 +27,7 @@ 'home', 'search', 'exhibits', + 'ov_collections', 'authors', 'wagtail.contrib.forms', 'wagtail.contrib.modeladmin',