dnscms: add studio page

This commit is contained in:
2026-05-19 06:09:54 +02:00
parent 843062bb13
commit 509a50c321
8 changed files with 189 additions and 0 deletions
+2
View File
@@ -37,6 +37,7 @@ INSTALLED_APPS = [
"news", "news",
"openinghours", "openinghours",
"sponsors", "sponsors",
"studio",
# end cms apps # end cms apps
"grapple", "grapple",
"graphene_django", "graphene_django",
@@ -206,6 +207,7 @@ GRAPPLE = {
"news", "news",
"openinghours", "openinghours",
"sponsors", "sponsors",
"studio",
], ],
"EXPOSE_GRAPHIQL": True, "EXPOSE_GRAPHIQL": True,
"PAGE_SIZE": 100, "PAGE_SIZE": 100,
View File
+6
View File
@@ -0,0 +1,6 @@
from django.apps import AppConfig
class StudioConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "studio"
+32
View File
@@ -0,0 +1,32 @@
# Generated by Django 6.0.5 on 2026-05-19 02:59
import django.db.models.deletion
import wagtail.fields
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('images', '0005_customimage_description'),
('wagtailcore', '0097_baselogentry_uuid_action_timestamp_indexes'),
]
operations = [
migrations.CreateModel(
name='StudioPage',
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')),
('lead', wagtail.fields.RichTextField(blank=True)),
('body', wagtail.fields.StreamField([('paragraph', 0), ('image', 4), ('image_slider', 8), ('horizontal_rule', 10), ('featured', 18), ('page_section_navigation', 19), ('accordion', 23), ('fact_box', 26), ('embed', 27), ('raw_html', 28), ('page_section', 33)], block_lookup={0: ('wagtail.blocks.RichTextBlock', (), {'label': 'Rik tekst'}), 1: ('wagtail.images.blocks.ImageChooserBlock', (), {'label': 'Bilde'}), 2: ('wagtail.blocks.ChoiceBlock', [], {'choices': [('fullwidth', 'Fullbredde'), ('bleed', 'Utfallende'), ('original', 'Uendret størrelse')], 'icon': 'cup', 'label': 'Bildeformat'}), 3: ('wagtail.blocks.CharBlock', (), {'label': 'Bildetekst', 'max_length': 512, 'required': False}), 4: ('wagtail.blocks.StructBlock', [[('image', 1), ('image_format', 2), ('text', 3)]], {}), 5: ('wagtail.blocks.CharBlock', (), {'label': 'Tekst', 'max_length': 512, 'required': False}), 6: ('wagtail.blocks.StructBlock', [[('image', 1), ('text', 5)]], {}), 7: ('wagtail.blocks.ListBlock', (6,), {'label': 'Bilder', 'min_num': 1}), 8: ('wagtail.blocks.StructBlock', [[('images', 7)]], {}), 9: ('wagtail.blocks.ChoiceBlock', [], {'choices': [('deepBrick', 'Dyp tegl'), ('neufPink', 'Griserosa'), ('goldenOrange', 'Gyllen oransje'), ('goldenBeige', 'Gyllen beige'), ('chateauBlue', 'Slottsblå')], 'label': 'Farge', 'required': False}), 10: ('wagtail.blocks.StructBlock', [[('color', 9)]], {}), 11: ('wagtail.blocks.CharBlock', (), {'label': 'Tittel', 'max_length': 64, 'required': True}), 12: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link'], 'label': 'Tekst', 'required': True}), 13: ('wagtail.blocks.PageChooserBlock', (), {'header': 'Fremhevet side', 'required': True}), 14: ('wagtail.blocks.CharBlock', (), {'default': 'Les mer', 'help_text': 'Lenketeksten som tar deg videre til siden. Tips: Ikke start med "Trykk her"', 'label': 'Lenketekst', 'max_length': 64, 'required': True}), 15: ('wagtail.blocks.ChoiceBlock', [], {'choices': [('betongGray', 'Betonggrå'), ('deepBrick', 'Dyp tegl'), ('neufPink', 'Griserosa'), ('goldenOrange', 'Gyllen oransje'), ('goldenBeige', 'Gyllen beige'), ('chateauBlue', 'Slottsblå')], 'label': 'Bakgrunnsfarge'}), 16: ('wagtail.blocks.ChoiceBlock', [], {'choices': [('left', 'Venstre'), ('right', 'Høyre')], 'label': 'Bildeplassering'}), 17: ('wagtail.images.blocks.ImageChooserBlock', (), {'header': 'Overstyr bilde', 'help_text': 'Bildet som er tilknyttet undersiden du vil fremheve, vil automatisk brukes. Om det mangler eller du vil overstyre hvilket bilde som et brukes, kan du velge et her.', 'required': False}), 18: ('wagtail.blocks.StructBlock', [[('title', 11), ('text', 12), ('featured_page', 13), ('link_text', 14), ('background_color', 15), ('image_position', 16), ('featured_image_override', 17)]], {}), 19: ('dnscms.blocks.PageSectionNavigationBlock', (), {}), 20: ('wagtail.blocks.CharBlock', (), {'label': 'Overskrift', 'max_length': 64, 'required': True}), 21: ('wagtail.blocks.StructBlock', [[('image', 1), ('image_format', 2), ('text', 3)]], {'label': 'Bilde'}), 22: ('wagtail.blocks.StreamBlock', [[('paragraph', 0), ('image', 21)]], {}), 23: ('wagtail.blocks.StructBlock', [[('heading', 20), ('body', 22)]], {}), 24: ('wagtail.blocks.ChoiceBlock', [], {'choices': [('betongGray', 'Betonggrå'), ('deepBrick', 'Dyp tegl'), ('neufPink', 'Griserosa'), ('goldenOrange', 'Gyllen oransje'), ('goldenBeige', 'Gyllen beige'), ('chateauBlue', 'Slottsblå')], 'label': 'Bakgrunnsfarge', 'required': False}), 25: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link', 'ol', 'ul', 'h2', 'h3'], 'label': 'Innhold'}), 26: ('wagtail.blocks.StructBlock', [[('background_color', 24), ('body', 25)]], {}), 27: ('wagtail.embeds.blocks.EmbedBlock', (), {}), 28: ('wagtail.blocks.RawHTMLBlock', (), {}), 29: ('wagtail.blocks.ChoiceBlock', [], {'choices': [('pigHeadLogo', 'Grisehodelogo'), ('key', 'Nøkkel'), ('ticket', 'Billett'), ('shield', 'Skjold'), ('bottle', 'Flaske'), ('lostProperty', 'Hittegods'), ('pigsty', 'Grisebinge'), ('wheelchair', 'Rullestol'), ('clock', 'Klokke'), ('parking', 'Parkering'), ('coins', 'Mynter')], 'label': 'Ikon', 'required': False}), 30: ('dnscms.blocks.NeufAddressSectionBlock', (), {}), 31: ('dnscms.blocks.OpeningHoursSectionBlock', (), {}), 32: ('wagtail.blocks.StreamBlock', [[('paragraph', 0), ('image', 4), ('image_slider', 8), ('horizontal_rule', 10), ('featured', 18), ('accordion', 23), ('fact_box', 26), ('embed', 27), ('raw_html', 28), ('neuf_address', 30), ('opening_hours', 31)]], {}), 33: ('wagtail.blocks.StructBlock', [[('title', 11), ('background_color', 24), ('icon', 29), ('body', 32)]], {})})),
('pig', models.CharField(blank=True, choices=[('', 'Ingen'), ('logo', 'Logogrisen'), ('music', 'Musikergrisen'), ('drink', 'Drikkegrisen'), ('dance', 'Dansegrisen'), ('point', 'Pekegrisen'), ('student', 'Studentgrisen'), ('listen', 'Lyttegrisen'), ('guard', 'Vaktgrisen'), ('key', 'Nøkkelgrisen'), ('chill', 'Liggegrisen'), ('peek', 'Tittegrisen')], default='', help_text='Grisen nedi hjørnet.', max_length=32)),
('logo', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.customimage')),
],
options={
'abstract': False,
},
bases=('wagtailcore.page',),
),
]
+63
View File
@@ -0,0 +1,63 @@
from django.db import models
from grapple.helpers import register_singular_query_field
from grapple.models import (
GraphQLImage,
GraphQLRichText,
GraphQLStreamfield,
GraphQLString,
)
from wagtail.admin.panels import FieldPanel
from wagtail.fields import RichTextField, StreamField
from wagtail.models import Page
from wagtail.search import index
from dnscms.blocks import BASE_BLOCKS, PageSectionBlock
from dnscms.options import ALL_PIGS
@register_singular_query_field("studioPage")
class StudioPage(Page):
max_count = 1
subpage_types = []
show_in_menus = True
logo = models.ForeignKey(
"images.CustomImage",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="+",
)
lead = RichTextField(features=["link"], blank=True)
body = StreamField(BASE_BLOCKS + [("page_section", PageSectionBlock())])
PIG_CHOICES = [
("", "Ingen"),
] + ALL_PIGS
pig = models.CharField(
max_length=32,
choices=PIG_CHOICES,
default="",
blank=True,
help_text="Grisen nedi hjørnet.",
)
content_panels = Page.content_panels + [
FieldPanel("logo"),
FieldPanel("lead", heading="Ingress"),
FieldPanel("body", heading="Innhold"),
FieldPanel("pig", heading="Gris"),
]
graphql_fields = [
GraphQLImage("logo"),
GraphQLRichText("lead"),
GraphQLStreamfield("body"),
GraphQLString("pig", required=True),
]
search_fields = Page.search_fields + [
index.SearchField("lead"),
index.SearchField("body"),
]
+8
View File
@@ -10,6 +10,7 @@ from events.models import EventIndex, EventPage
from generic.models import GenericPage from generic.models import GenericPage
from images.models import CustomImage from images.models import CustomImage
from news.models import NewsIndex, NewsPage from news.models import NewsIndex, NewsPage
from studio.models import StudioPage
from venues.models import VenueIndex, VenuePage from venues.models import VenueIndex, VenuePage
@@ -56,6 +57,13 @@ class GenericPageFactory(wagtail_factories.PageFactory):
model = GenericPage model = GenericPage
class StudioPageFactory(wagtail_factories.PageFactory):
title = factory.Sequence(lambda n: f"Studio {n}")
class Meta:
model = StudioPage
class NewsIndexFactory(wagtail_factories.PageFactory): class NewsIndexFactory(wagtail_factories.PageFactory):
title = factory.Sequence(lambda n: f"News {n}") title = factory.Sequence(lambda n: f"News {n}")
+78
View File
@@ -0,0 +1,78 @@
from studio.models import StudioPage
from tests.conftest import CustomImageFactory, StudioPageFactory
def test_studio_page_persists_via_factory(home_page):
logo = CustomImageFactory()
page = StudioPageFactory(
parent=home_page,
title="STUDiO",
slug="studio",
lead="<p>Ingress.</p>",
body=[("paragraph", "<p>Body content.</p>")],
pig="drink",
logo=logo,
)
reloaded = StudioPage.objects.get(pk=page.pk)
assert reloaded.title == "STUDiO"
assert reloaded.slug == "studio"
assert "Ingress." in reloaded.lead
assert reloaded.pig == "drink"
assert reloaded.body[0].block_type == "paragraph"
assert reloaded.logo == logo
def test_studio_page_is_singleton(home_page):
StudioPageFactory(parent=home_page, slug="studio")
assert StudioPage.can_create_at(home_page) is False
def test_graphql_studio_page_query(home_page, graphql_post):
logo = CustomImageFactory(alt="STUDiO-logo")
StudioPageFactory(
parent=home_page,
title="STUDiO",
slug="studio",
lead="<p>Ingress text.</p>",
body=[("paragraph", "<p>Body content.</p>")],
pig="drink",
logo=logo,
)
response, body = graphql_post(
"""
query {
page: studioPage {
... on StudioPage {
title
slug
lead
pig
logo {
alt
}
body {
blockType
field
... on RichTextBlock {
value
}
}
}
}
}
"""
)
assert response.status_code == 200
assert "errors" not in body, body
data = body["data"]["page"]
assert data["title"] == "STUDiO"
assert data["slug"] == "studio"
assert "Ingress text." in data["lead"]
assert data["pig"] == "drink"
assert data["logo"]["alt"] == "STUDiO-logo"
assert data["body"][0]["blockType"] == "RichTextBlock"
assert "Body content." in data["body"][0]["value"]