Skip to content

Commit

Permalink
📮 collections (#71)
Browse files Browse the repository at this point in the history
* Initial collections app

* Initial model and migration

* Add basic CollectionAPIViewSet

* Basic ContentBlock nesting in InterviewsBlock

* Add migration for InterviewsBlock

* Add ArchivalFootage block

* Add other block types, per design

* Split ContentBlock.image to ContentImageBlock

* Include content in API response

* Migrate to 'content' for block content

* Add get_api_representation to ContentBlocks

* Remove unnecessary StructBlock nesting in content

* Restructure all ContentBlocks to match

* Better inheritance with ContentListBlock and ContentListImageBlock

* Refactor other classes to use inheritance

* Migrate custom block classes to generic ContentBlock and ContentImageBlock

* Rename about -> introduction to match design

* Add labels and icons to inner blocks

* Adds missing migration file
  • Loading branch information
mrharpo committed Oct 31, 2023
1 parent 134b752 commit 24512db
Show file tree
Hide file tree
Showing 18 changed files with 322 additions and 0 deletions.
Empty file added ov_collections/__init__.py
Empty file.
3 changes: 3 additions & 0 deletions ov_collections/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.contrib import admin

# Register your models here.
6 changes: 6 additions & 0 deletions ov_collections/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class CollectionsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'ov_collections'
29 changes: 29 additions & 0 deletions ov_collections/blocks.py
Original file line number Diff line number Diff line change
@@ -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
33 changes: 33 additions & 0 deletions ov_collections/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -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',),
),
]
21 changes: 21 additions & 0 deletions ov_collections/migrations/0002_alter_collection_content.py
Original file line number Diff line number Diff line change
@@ -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),
),
]
21 changes: 21 additions & 0 deletions ov_collections/migrations/0003_alter_collection_content.py
Original file line number Diff line number Diff line change
@@ -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),
),
]
21 changes: 21 additions & 0 deletions ov_collections/migrations/0004_alter_collection_content.py
Original file line number Diff line number Diff line change
@@ -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),
),
]
21 changes: 21 additions & 0 deletions ov_collections/migrations/0005_alter_collection_content.py
Original file line number Diff line number Diff line change
@@ -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),
),
]
21 changes: 21 additions & 0 deletions ov_collections/migrations/0006_alter_collection_content.py
Original file line number Diff line number Diff line change
@@ -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),
),
]
Original file line number Diff line number Diff line change
@@ -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',
),
]
21 changes: 21 additions & 0 deletions ov_collections/migrations/0008_alter_collection_content.py
Original file line number Diff line number Diff line change
@@ -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),
),
]
Empty file.
89 changes: 89 additions & 0 deletions ov_collections/models.py
Original file line number Diff line number Diff line change
@@ -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'),
]
3 changes: 3 additions & 0 deletions ov_collections/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.test import TestCase

# Create your tests here.
12 changes: 12 additions & 0 deletions ov_collections/views.py
Original file line number Diff line number Diff line change
@@ -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',
]
Loading

0 comments on commit 24512db

Please sign in to comment.