dnscms: add studio page
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class StudioConfig(AppConfig):
|
||||||
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
|
name = "studio"
|
||||||
@@ -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',),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -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"),
|
||||||
|
]
|
||||||
@@ -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}")
|
||||||
|
|
||||||
|
|||||||
@@ -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"]
|
||||||
Reference in New Issue
Block a user