Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1073adacbb | |||
| 7e114adcc3 | |||
| 0e074b5f1f | |||
| e960da6f1c | |||
| a5ebb897f1 | |||
| f91c67f526 | |||
| 0c5a9876d6 | |||
| cf945d8647 | |||
| 0e5f9f7769 | |||
| 10c8ce194c | |||
| 509a50c321 | |||
| 843062bb13 | |||
| f7e0200a0a | |||
| b09ce9808d | |||
| bc8642b1fc | |||
| 2155a149e8 | |||
| 676e58c361 | |||
| a9e59b947a | |||
| 202cfe47f3 |
@@ -0,0 +1,36 @@
|
|||||||
|
# neuf-www
|
||||||
|
|
||||||
|
The neuf.no website. Wagtail CMS backend (`dnscms/`) feeding a Next.js frontend (`web/`) over GraphQL.
|
||||||
|
|
||||||
|
Tools are managed by [mise](https://mise.jdx.dev/). Run `mise install` to get python, uv, node, and prek.
|
||||||
|
|
||||||
|
## Backend (`dnscms/`)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd dnscms
|
||||||
|
uv sync
|
||||||
|
uv run ./manage.py migrate
|
||||||
|
uv run ./manage.py runserver
|
||||||
|
uv run pytest
|
||||||
|
```
|
||||||
|
|
||||||
|
GraphQL endpoint: <http://127.0.0.1:8000/api/graphql/>.
|
||||||
|
|
||||||
|
## Frontend (`web/`)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd web
|
||||||
|
npm install
|
||||||
|
npm run dev # http://localhost:3000
|
||||||
|
npm run codegen # regenerate GraphQL types (needs the backend running)
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pre-commit hooks
|
||||||
|
|
||||||
|
[prek](https://github.com/j178/prek) runs ruff lint + format on `dnscms/**/*.py` plus a few sanity hooks. Hooks are configured in [prek.toml](prek.toml).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
prek install # registers the git hook
|
||||||
|
prek run --all-files # run on everything
|
||||||
|
```
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
*.swp
|
*.swp
|
||||||
.vscode/
|
.vscode/
|
||||||
|
.coverage
|
||||||
/venv/
|
/venv/
|
||||||
/.venv/
|
/.venv/
|
||||||
/static/
|
/static/
|
||||||
|
|||||||
@@ -1,492 +0,0 @@
|
|||||||
# Generated by Django 6.0.3 on 2026-04-15 20:21
|
|
||||||
|
|
||||||
import wagtail.fields
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("associations", "0025_associationpage_lead"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="associationindex",
|
|
||||||
name="body",
|
|
||||||
field=wagtail.fields.StreamField(
|
|
||||||
[
|
|
||||||
("paragraph", 0),
|
|
||||||
("image", 4),
|
|
||||||
("image_slider", 8),
|
|
||||||
("horizontal_rule", 10),
|
|
||||||
("featured", 18),
|
|
||||||
("page_section_navigation", 19),
|
|
||||||
("accordion", 23),
|
|
||||||
("fact_box", 26),
|
|
||||||
("photo_sphere", 29),
|
|
||||||
("embed", 30),
|
|
||||||
("raw_html", 31),
|
|
||||||
],
|
|
||||||
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.images.blocks.ImageChooserBlock",
|
|
||||||
(),
|
|
||||||
{"label": "360°-bilde"},
|
|
||||||
),
|
|
||||||
28: (
|
|
||||||
"wagtail.blocks.CharBlock",
|
|
||||||
(),
|
|
||||||
{
|
|
||||||
"help_text": "Beskrivende tittel på bildet (vises også til skjermlesere)",
|
|
||||||
"label": "Tittel",
|
|
||||||
"max_length": 256,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
29: (
|
|
||||||
"wagtail.blocks.StructBlock",
|
|
||||||
[[("image", 27), ("title", 28)]],
|
|
||||||
{},
|
|
||||||
),
|
|
||||||
30: ("wagtail.embeds.blocks.EmbedBlock", (), {}),
|
|
||||||
31: ("wagtail.blocks.RawHTMLBlock", (), {}),
|
|
||||||
},
|
|
||||||
default=[("paragraph", "")],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="associationpage",
|
|
||||||
name="body",
|
|
||||||
field=wagtail.fields.StreamField(
|
|
||||||
[
|
|
||||||
("paragraph", 0),
|
|
||||||
("image", 4),
|
|
||||||
("image_slider", 8),
|
|
||||||
("horizontal_rule", 10),
|
|
||||||
("featured", 18),
|
|
||||||
("page_section_navigation", 19),
|
|
||||||
("accordion", 23),
|
|
||||||
("fact_box", 26),
|
|
||||||
("photo_sphere", 29),
|
|
||||||
("embed", 30),
|
|
||||||
("raw_html", 31),
|
|
||||||
],
|
|
||||||
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.images.blocks.ImageChooserBlock",
|
|
||||||
(),
|
|
||||||
{"label": "360°-bilde"},
|
|
||||||
),
|
|
||||||
28: (
|
|
||||||
"wagtail.blocks.CharBlock",
|
|
||||||
(),
|
|
||||||
{
|
|
||||||
"help_text": "Beskrivende tittel på bildet (vises også til skjermlesere)",
|
|
||||||
"label": "Tittel",
|
|
||||||
"max_length": 256,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
29: (
|
|
||||||
"wagtail.blocks.StructBlock",
|
|
||||||
[[("image", 27), ("title", 28)]],
|
|
||||||
{},
|
|
||||||
),
|
|
||||||
30: ("wagtail.embeds.blocks.EmbedBlock", (), {}),
|
|
||||||
31: ("wagtail.blocks.RawHTMLBlock", (), {}),
|
|
||||||
},
|
|
||||||
default=[("paragraph", "")],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -10,13 +10,14 @@ from wagtail.admin.panels import FieldPanel
|
|||||||
from wagtail.fields import RichTextField
|
from wagtail.fields import RichTextField
|
||||||
from wagtail.models import Page
|
from wagtail.models import Page
|
||||||
from wagtail.search import index
|
from wagtail.search import index
|
||||||
|
from wagtail_headless_preview.models import HeadlessMixin
|
||||||
|
|
||||||
from dnscms.fields import CommonStreamField
|
from dnscms.fields import CommonStreamField
|
||||||
from dnscms.wordpress.models import WPImportedPageMixin
|
from dnscms.wordpress.models import WPImportedPageMixin
|
||||||
|
|
||||||
|
|
||||||
@register_singular_query_field("associationIndex")
|
@register_singular_query_field("associationIndex")
|
||||||
class AssociationIndex(Page):
|
class AssociationIndex(HeadlessMixin, Page):
|
||||||
max_count = 1
|
max_count = 1
|
||||||
subpage_types = ["associations.AssociationPage"]
|
subpage_types = ["associations.AssociationPage"]
|
||||||
|
|
||||||
@@ -37,7 +38,7 @@ class AssociationIndex(Page):
|
|||||||
search_fields = Page.search_fields
|
search_fields = Page.search_fields
|
||||||
|
|
||||||
|
|
||||||
class AssociationPage(WPImportedPageMixin, Page):
|
class AssociationPage(HeadlessMixin, WPImportedPageMixin, Page):
|
||||||
subpage_types = []
|
subpage_types = []
|
||||||
parent_page_types = ["associations.AssociationIndex"]
|
parent_page_types = ["associations.AssociationIndex"]
|
||||||
show_in_menus = False
|
show_in_menus = False
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
|
|
||||||
# Create your tests here.
|
|
||||||
@@ -1,289 +0,0 @@
|
|||||||
# Generated by Django 6.0.3 on 2026-04-15 20:21
|
|
||||||
|
|
||||||
import wagtail.fields
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("contacts", "0017_alter_contactindex_body"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="contactindex",
|
|
||||||
name="body",
|
|
||||||
field=wagtail.fields.StreamField(
|
|
||||||
[
|
|
||||||
("paragraph", 0),
|
|
||||||
("image", 4),
|
|
||||||
("image_slider", 8),
|
|
||||||
("horizontal_rule", 10),
|
|
||||||
("featured", 18),
|
|
||||||
("page_section_navigation", 19),
|
|
||||||
("accordion", 23),
|
|
||||||
("fact_box", 26),
|
|
||||||
("photo_sphere", 29),
|
|
||||||
("embed", 30),
|
|
||||||
("raw_html", 31),
|
|
||||||
("contact_section", 39),
|
|
||||||
],
|
|
||||||
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.images.blocks.ImageChooserBlock",
|
|
||||||
(),
|
|
||||||
{"label": "360°-bilde"},
|
|
||||||
),
|
|
||||||
28: (
|
|
||||||
"wagtail.blocks.CharBlock",
|
|
||||||
(),
|
|
||||||
{
|
|
||||||
"help_text": "Beskrivende tittel på bildet (vises også til skjermlesere)",
|
|
||||||
"label": "Tittel",
|
|
||||||
"max_length": 256,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
29: (
|
|
||||||
"wagtail.blocks.StructBlock",
|
|
||||||
[[("image", 27), ("title", 28)]],
|
|
||||||
{},
|
|
||||||
),
|
|
||||||
30: ("wagtail.embeds.blocks.EmbedBlock", (), {}),
|
|
||||||
31: ("wagtail.blocks.RawHTMLBlock", (), {}),
|
|
||||||
32: (
|
|
||||||
"wagtail.blocks.RichTextBlock",
|
|
||||||
(),
|
|
||||||
{"features": ["bold", "italic", "link"]},
|
|
||||||
),
|
|
||||||
33: (
|
|
||||||
"wagtail.snippets.blocks.SnippetChooserBlock",
|
|
||||||
("contacts.ContactEntity",),
|
|
||||||
{},
|
|
||||||
),
|
|
||||||
34: ("wagtail.blocks.StructBlock", [[("contact_entity", 33)]], {}),
|
|
||||||
35: (
|
|
||||||
"contacts.blocks.ContactListBlock",
|
|
||||||
(34,),
|
|
||||||
{"label": "Liste med kontaktpunkter"},
|
|
||||||
),
|
|
||||||
36: (
|
|
||||||
"wagtail.blocks.StreamBlock",
|
|
||||||
[[("contact_entity_list", 35)]],
|
|
||||||
{"required": False},
|
|
||||||
),
|
|
||||||
37: (
|
|
||||||
"wagtail.blocks.StructBlock",
|
|
||||||
[[("title", 11), ("text", 32), ("blocks", 36)]],
|
|
||||||
{"label": "Kontaktunderseksjon"},
|
|
||||||
),
|
|
||||||
38: (
|
|
||||||
"wagtail.blocks.StreamBlock",
|
|
||||||
[[("contact_entity_list", 35), ("contact_subsection", 37)]],
|
|
||||||
{"required": False},
|
|
||||||
),
|
|
||||||
39: (
|
|
||||||
"wagtail.blocks.StructBlock",
|
|
||||||
[[("title", 11), ("text", 32), ("blocks", 38)]],
|
|
||||||
{"label": "Kontaktseksjon"},
|
|
||||||
),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -11,6 +11,7 @@ from wagtail.fields import RichTextField, StreamField
|
|||||||
from wagtail.models import Page
|
from wagtail.models import Page
|
||||||
from wagtail.search import index
|
from wagtail.search import index
|
||||||
from wagtail.snippets.models import register_snippet
|
from wagtail.snippets.models import register_snippet
|
||||||
|
from wagtail_headless_preview.models import HeadlessMixin
|
||||||
|
|
||||||
from contacts.blocks import ContactSectionBlock
|
from contacts.blocks import ContactSectionBlock
|
||||||
from dnscms.blocks import BASE_BLOCKS
|
from dnscms.blocks import BASE_BLOCKS
|
||||||
@@ -22,7 +23,7 @@ PHONE_REGEX_VALIDATOR = RegexValidator(
|
|||||||
|
|
||||||
|
|
||||||
@register_singular_query_field("contactIndex")
|
@register_singular_query_field("contactIndex")
|
||||||
class ContactIndex(Page):
|
class ContactIndex(HeadlessMixin, Page):
|
||||||
max_count = 1
|
max_count = 1
|
||||||
subpage_types = []
|
subpage_types = []
|
||||||
|
|
||||||
|
|||||||
@@ -203,25 +203,6 @@ class AccordionBlock(blocks.StructBlock):
|
|||||||
label = "Trekkspill"
|
label = "Trekkspill"
|
||||||
|
|
||||||
|
|
||||||
@register_streamfield_block
|
|
||||||
class PhotoSphereBlock(blocks.StructBlock):
|
|
||||||
image = ImageChooserBlock(label="360°-bilde")
|
|
||||||
title = blocks.CharBlock(
|
|
||||||
max_length=512,
|
|
||||||
label="Bildetekst",
|
|
||||||
required=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
graphql_fields = [
|
|
||||||
GraphQLImage("image", required=True),
|
|
||||||
GraphQLString("title", required=False),
|
|
||||||
]
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
icon = "globe"
|
|
||||||
label = "360°-bilde"
|
|
||||||
|
|
||||||
|
|
||||||
@register_streamfield_block
|
@register_streamfield_block
|
||||||
class FactBoxBlock(blocks.StructBlock):
|
class FactBoxBlock(blocks.StructBlock):
|
||||||
background_color = blocks.ChoiceBlock(
|
background_color = blocks.ChoiceBlock(
|
||||||
@@ -253,7 +234,6 @@ BASE_BLOCKS = [
|
|||||||
("page_section_navigation", PageSectionNavigationBlock()),
|
("page_section_navigation", PageSectionNavigationBlock()),
|
||||||
("accordion", AccordionBlock()),
|
("accordion", AccordionBlock()),
|
||||||
("fact_box", FactBoxBlock()),
|
("fact_box", FactBoxBlock()),
|
||||||
("photo_sphere", PhotoSphereBlock()),
|
|
||||||
("embed", EmbedBlock()),
|
("embed", EmbedBlock()),
|
||||||
("raw_html", blocks.RawHTMLBlock()),
|
("raw_html", blocks.RawHTMLBlock()),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -37,9 +37,11 @@ INSTALLED_APPS = [
|
|||||||
"news",
|
"news",
|
||||||
"openinghours",
|
"openinghours",
|
||||||
"sponsors",
|
"sponsors",
|
||||||
|
"studio",
|
||||||
# end cms apps
|
# end cms apps
|
||||||
"grapple",
|
"grapple",
|
||||||
"graphene_django",
|
"graphene_django",
|
||||||
|
"wagtail_headless_preview",
|
||||||
"wagtail.contrib.forms",
|
"wagtail.contrib.forms",
|
||||||
"wagtail.contrib.redirects",
|
"wagtail.contrib.redirects",
|
||||||
"wagtail.contrib.settings",
|
"wagtail.contrib.settings",
|
||||||
@@ -173,6 +175,7 @@ WAGTAIL_SITE_NAME = "dnscms"
|
|||||||
WAGTAIL_ALLOW_UNICODE_SLUGS = False
|
WAGTAIL_ALLOW_UNICODE_SLUGS = False
|
||||||
|
|
||||||
WAGTAILIMAGES_IMAGE_MODEL = "images.CustomImage"
|
WAGTAILIMAGES_IMAGE_MODEL = "images.CustomImage"
|
||||||
|
WAGTAILIMAGES_EXTENSIONS = ["avif", "gif", "jpg", "jpeg", "png", "webp", "svg"]
|
||||||
|
|
||||||
# Search
|
# Search
|
||||||
# https://docs.wagtail.org/en/stable/topics/search/backends.html
|
# https://docs.wagtail.org/en/stable/topics/search/backends.html
|
||||||
@@ -183,11 +186,22 @@ WAGTAILSEARCH_BACKENDS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Base URL to use when referring to full URLs within the Wagtail admin backend -
|
# Base URL to use when referring to full URLs within the Wagtail admin backend -
|
||||||
# e.g. in notification emails. Don't include '/admin' or a trailing slash
|
# e.g. in notification emails. Don't include '/admin' or a trailing slash.
|
||||||
WAGTAILADMIN_BASE_URL = "http://example.com"
|
# Also used by wagtail-grapple to make image URLs absolute.
|
||||||
|
WAGTAIL_BASE_URL = os.environ.get("WAGTAIL_BASE_URL", "http://127.0.0.1:8000").rstrip("/")
|
||||||
|
WAGTAILADMIN_BASE_URL = WAGTAIL_BASE_URL
|
||||||
|
BASE_URL = WAGTAIL_BASE_URL
|
||||||
|
|
||||||
# Required by wagtail-grapple to make image URLs absolute
|
# Public URL of the Next.js frontend. Used to direct preview iframes and to
|
||||||
BASE_URL = "http://example.com"
|
# redirect "View Live" clicks on the CMS host over to the headless frontend.
|
||||||
|
FRONTEND_BASE_URL = os.environ.get("FRONTEND_BASE_URL", "http://localhost:3000").rstrip("/")
|
||||||
|
|
||||||
|
WAGTAIL_HEADLESS_PREVIEW = {
|
||||||
|
"CLIENT_URLS": {"default": f"{FRONTEND_BASE_URL}/api/preview"},
|
||||||
|
"SERVE_BASE_URL": FRONTEND_BASE_URL,
|
||||||
|
"ENFORCE_TRAILING_SLASH": False,
|
||||||
|
"REDIRECT_ON_PREVIEW": False,
|
||||||
|
}
|
||||||
|
|
||||||
# https://docs.wagtail.org/en/latest/releases/6.4.html#data-upload-max-number-fields-update
|
# https://docs.wagtail.org/en/latest/releases/6.4.html#data-upload-max-number-fields-update
|
||||||
DATA_UPLOAD_MAX_NUMBER_FIELDS = 10_000
|
DATA_UPLOAD_MAX_NUMBER_FIELDS = 10_000
|
||||||
@@ -206,6 +220,7 @@ GRAPPLE = {
|
|||||||
"news",
|
"news",
|
||||||
"openinghours",
|
"openinghours",
|
||||||
"sponsors",
|
"sponsors",
|
||||||
|
"studio",
|
||||||
],
|
],
|
||||||
"EXPOSE_GRAPHIQL": True,
|
"EXPOSE_GRAPHIQL": True,
|
||||||
"PAGE_SIZE": 100,
|
"PAGE_SIZE": 100,
|
||||||
|
|||||||
@@ -11,13 +11,6 @@ ALLOWED_HOSTS = ["*"]
|
|||||||
|
|
||||||
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||||
|
|
||||||
# Base URL to use when referring to full URLs within the Wagtail admin backend -
|
|
||||||
# e.g. in notification emails. Don't include '/admin' or a trailing slash
|
|
||||||
WAGTAILADMIN_BASE_URL = "http://127.0.0.1:8000"
|
|
||||||
|
|
||||||
# Required by wagtail-grapple to make image URLs absolute
|
|
||||||
BASE_URL = "http://127.0.0.1:8000"
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from .local import *
|
from .local import *
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
from .base import * # noqa: F401, F403
|
||||||
|
|
||||||
|
SECRET_KEY = "test-secret-key"
|
||||||
|
DEBUG = False
|
||||||
|
ALLOWED_HOSTS = ["*"]
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
"default": {
|
||||||
|
"ENGINE": "django.db.backends.sqlite3",
|
||||||
|
"NAME": ":memory:",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
STORAGES = {
|
||||||
|
"default": {
|
||||||
|
"BACKEND": "django.core.files.storage.FileSystemStorage",
|
||||||
|
},
|
||||||
|
"staticfiles": {
|
||||||
|
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"]
|
||||||
@@ -1,253 +0,0 @@
|
|||||||
# Generated by Django 6.0.3 on 2026-04-15 20:21
|
|
||||||
|
|
||||||
import wagtail.fields
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("events", "0053_eventpage_lead"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="eventpage",
|
|
||||||
name="body",
|
|
||||||
field=wagtail.fields.StreamField(
|
|
||||||
[
|
|
||||||
("paragraph", 0),
|
|
||||||
("image", 4),
|
|
||||||
("image_slider", 8),
|
|
||||||
("horizontal_rule", 10),
|
|
||||||
("featured", 18),
|
|
||||||
("page_section_navigation", 19),
|
|
||||||
("accordion", 23),
|
|
||||||
("fact_box", 26),
|
|
||||||
("photo_sphere", 29),
|
|
||||||
("embed", 30),
|
|
||||||
("raw_html", 31),
|
|
||||||
],
|
|
||||||
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.images.blocks.ImageChooserBlock",
|
|
||||||
(),
|
|
||||||
{"label": "360°-bilde"},
|
|
||||||
),
|
|
||||||
28: (
|
|
||||||
"wagtail.blocks.CharBlock",
|
|
||||||
(),
|
|
||||||
{
|
|
||||||
"help_text": "Beskrivende tittel på bildet (vises også til skjermlesere)",
|
|
||||||
"label": "Tittel",
|
|
||||||
"max_length": 256,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
29: (
|
|
||||||
"wagtail.blocks.StructBlock",
|
|
||||||
[[("image", 27), ("title", 28)]],
|
|
||||||
{},
|
|
||||||
),
|
|
||||||
30: ("wagtail.embeds.blocks.EmbedBlock", (), {}),
|
|
||||||
31: ("wagtail.blocks.RawHTMLBlock", (), {}),
|
|
||||||
},
|
|
||||||
default=[("paragraph", "")],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
+10
-6
@@ -30,6 +30,7 @@ from wagtail.fields import RichTextField
|
|||||||
from wagtail.models import Orderable, Page, PageManager, PageQuerySet
|
from wagtail.models import Orderable, Page, PageManager, PageQuerySet
|
||||||
from wagtail.search import index
|
from wagtail.search import index
|
||||||
from wagtail.snippets.models import register_snippet
|
from wagtail.snippets.models import register_snippet
|
||||||
|
from wagtail_headless_preview.models import HeadlessMixin
|
||||||
|
|
||||||
from associations.widgets import AssociationChooserWidget
|
from associations.widgets import AssociationChooserWidget
|
||||||
from dnscms.fields import CommonStreamField
|
from dnscms.fields import CommonStreamField
|
||||||
@@ -39,7 +40,7 @@ from venues.models import VenuePage
|
|||||||
|
|
||||||
|
|
||||||
@register_singular_query_field("eventIndex")
|
@register_singular_query_field("eventIndex")
|
||||||
class EventIndex(Page):
|
class EventIndex(HeadlessMixin, Page):
|
||||||
max_count = 1
|
max_count = 1
|
||||||
subpage_types = ["events.EventPage"]
|
subpage_types = ["events.EventPage"]
|
||||||
|
|
||||||
@@ -209,15 +210,18 @@ class EventOrganizer(ClusterableModel):
|
|||||||
|
|
||||||
class EventPageQuerySet(PageQuerySet):
|
class EventPageQuerySet(PageQuerySet):
|
||||||
def future(self):
|
def future(self):
|
||||||
today = timezone.localtime(timezone.now()).date()
|
now = timezone.now()
|
||||||
next_occurrence = Min("occurrences__start", filter=Q(occurrences__start__gte=today))
|
today_start = timezone.localtime(now).replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
return self.filter(occurrences__start__gte=today).annotate(next_occurrence=next_occurrence)
|
next_occurrence = Min("occurrences__start", filter=Q(occurrences__start__gte=today_start))
|
||||||
|
return self.filter(occurrences__start__gte=today_start).annotate(
|
||||||
|
next_occurrence=next_occurrence
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
EventPageManager = PageManager.from_queryset(EventPageQuerySet)
|
EventPageManager = PageManager.from_queryset(EventPageQuerySet)
|
||||||
|
|
||||||
|
|
||||||
class EventPage(WPImportedPageMixin, Page):
|
class EventPage(HeadlessMixin, WPImportedPageMixin, Page):
|
||||||
subpage_types = []
|
subpage_types = []
|
||||||
parent_page_types = ["events.EventIndex"]
|
parent_page_types = ["events.EventIndex"]
|
||||||
show_in_menus = False
|
show_in_menus = False
|
||||||
@@ -355,7 +359,7 @@ class EventPage(WPImportedPageMixin, Page):
|
|||||||
GraphQLImage("featured_image"),
|
GraphQLImage("featured_image"),
|
||||||
GraphQLRichText("lead"),
|
GraphQLRichText("lead"),
|
||||||
GraphQLStreamfield("body"),
|
GraphQLStreamfield("body"),
|
||||||
GraphQLString("pig"),
|
GraphQLString("pig", required=True),
|
||||||
GraphQLString("ticket_url"),
|
GraphQLString("ticket_url"),
|
||||||
GraphQLString("facebook_url"),
|
GraphQLString("facebook_url"),
|
||||||
GraphQLBoolean("free"),
|
GraphQLBoolean("free"),
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
|
|
||||||
# Create your tests here.
|
|
||||||
@@ -1,308 +0,0 @@
|
|||||||
# Generated by Django 6.0.3 on 2026-04-15 20:21
|
|
||||||
|
|
||||||
import wagtail.fields
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("generic", "0026_alter_genericpage_body"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="genericpage",
|
|
||||||
name="body",
|
|
||||||
field=wagtail.fields.StreamField(
|
|
||||||
[
|
|
||||||
("paragraph", 0),
|
|
||||||
("image", 4),
|
|
||||||
("image_slider", 8),
|
|
||||||
("horizontal_rule", 10),
|
|
||||||
("featured", 18),
|
|
||||||
("page_section_navigation", 19),
|
|
||||||
("accordion", 23),
|
|
||||||
("fact_box", 26),
|
|
||||||
("photo_sphere", 29),
|
|
||||||
("embed", 30),
|
|
||||||
("raw_html", 31),
|
|
||||||
("page_section", 36),
|
|
||||||
],
|
|
||||||
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.images.blocks.ImageChooserBlock",
|
|
||||||
(),
|
|
||||||
{"label": "360°-bilde"},
|
|
||||||
),
|
|
||||||
28: (
|
|
||||||
"wagtail.blocks.CharBlock",
|
|
||||||
(),
|
|
||||||
{
|
|
||||||
"help_text": "Beskrivende tittel på bildet (vises også til skjermlesere)",
|
|
||||||
"label": "Tittel",
|
|
||||||
"max_length": 256,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
29: (
|
|
||||||
"wagtail.blocks.StructBlock",
|
|
||||||
[[("image", 27), ("title", 28)]],
|
|
||||||
{},
|
|
||||||
),
|
|
||||||
30: ("wagtail.embeds.blocks.EmbedBlock", (), {}),
|
|
||||||
31: ("wagtail.blocks.RawHTMLBlock", (), {}),
|
|
||||||
32: (
|
|
||||||
"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,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
33: ("dnscms.blocks.NeufAddressSectionBlock", (), {}),
|
|
||||||
34: ("dnscms.blocks.OpeningHoursSectionBlock", (), {}),
|
|
||||||
35: (
|
|
||||||
"wagtail.blocks.StreamBlock",
|
|
||||||
[
|
|
||||||
[
|
|
||||||
("paragraph", 0),
|
|
||||||
("image", 4),
|
|
||||||
("image_slider", 8),
|
|
||||||
("horizontal_rule", 10),
|
|
||||||
("featured", 18),
|
|
||||||
("accordion", 23),
|
|
||||||
("fact_box", 26),
|
|
||||||
("photo_sphere", 29),
|
|
||||||
("embed", 30),
|
|
||||||
("raw_html", 31),
|
|
||||||
("neuf_address", 33),
|
|
||||||
("opening_hours", 34),
|
|
||||||
]
|
|
||||||
],
|
|
||||||
{},
|
|
||||||
),
|
|
||||||
36: (
|
|
||||||
"wagtail.blocks.StructBlock",
|
|
||||||
[
|
|
||||||
[
|
|
||||||
("title", 11),
|
|
||||||
("background_color", 24),
|
|
||||||
("icon", 32),
|
|
||||||
("body", 35),
|
|
||||||
]
|
|
||||||
],
|
|
||||||
{},
|
|
||||||
),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -4,13 +4,14 @@ from wagtail.admin.panels import FieldPanel
|
|||||||
from wagtail.fields import RichTextField, StreamField
|
from wagtail.fields import RichTextField, StreamField
|
||||||
from wagtail.models import Page
|
from wagtail.models import Page
|
||||||
from wagtail.search import index
|
from wagtail.search import index
|
||||||
|
from wagtail_headless_preview.models import HeadlessMixin
|
||||||
|
|
||||||
from dnscms.blocks import PageSectionBlock
|
from dnscms.blocks import PageSectionBlock
|
||||||
from dnscms.fields import BASE_BLOCKS
|
from dnscms.fields import BASE_BLOCKS
|
||||||
from dnscms.options import ALL_PIGS
|
from dnscms.options import ALL_PIGS
|
||||||
|
|
||||||
|
|
||||||
class GenericPage(Page):
|
class GenericPage(HeadlessMixin, Page):
|
||||||
subpage_types = ["generic.GenericPage"]
|
subpage_types = ["generic.GenericPage"]
|
||||||
show_in_menus = True
|
show_in_menus = True
|
||||||
|
|
||||||
|
|||||||
@@ -10,9 +10,10 @@ from wagtail.admin.panels import (
|
|||||||
PageChooserPanel,
|
PageChooserPanel,
|
||||||
)
|
)
|
||||||
from wagtail.models import Orderable, Page
|
from wagtail.models import Orderable, Page
|
||||||
|
from wagtail_headless_preview.models import HeadlessMixin
|
||||||
|
|
||||||
|
|
||||||
class HomePage(Page):
|
class HomePage(HeadlessMixin, Page):
|
||||||
max_count = 1
|
max_count = 1
|
||||||
|
|
||||||
content_panels = Page.content_panels + [
|
content_panels = Page.content_panels + [
|
||||||
|
|||||||
@@ -1,253 +0,0 @@
|
|||||||
# Generated by Django 6.0.3 on 2026-04-15 20:21
|
|
||||||
|
|
||||||
import wagtail.fields
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("news", "0018_newspage_wp_block_json_newspage_wp_link_and_more"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="newspage",
|
|
||||||
name="body",
|
|
||||||
field=wagtail.fields.StreamField(
|
|
||||||
[
|
|
||||||
("paragraph", 0),
|
|
||||||
("image", 4),
|
|
||||||
("image_slider", 8),
|
|
||||||
("horizontal_rule", 10),
|
|
||||||
("featured", 18),
|
|
||||||
("page_section_navigation", 19),
|
|
||||||
("accordion", 23),
|
|
||||||
("fact_box", 26),
|
|
||||||
("photo_sphere", 29),
|
|
||||||
("embed", 30),
|
|
||||||
("raw_html", 31),
|
|
||||||
],
|
|
||||||
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.images.blocks.ImageChooserBlock",
|
|
||||||
(),
|
|
||||||
{"label": "360°-bilde"},
|
|
||||||
),
|
|
||||||
28: (
|
|
||||||
"wagtail.blocks.CharBlock",
|
|
||||||
(),
|
|
||||||
{
|
|
||||||
"help_text": "Beskrivende tittel på bildet (vises også til skjermlesere)",
|
|
||||||
"label": "Tittel",
|
|
||||||
"max_length": 256,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
29: (
|
|
||||||
"wagtail.blocks.StructBlock",
|
|
||||||
[[("image", 27), ("title", 28)]],
|
|
||||||
{},
|
|
||||||
),
|
|
||||||
30: ("wagtail.embeds.blocks.EmbedBlock", (), {}),
|
|
||||||
31: ("wagtail.blocks.RawHTMLBlock", (), {}),
|
|
||||||
},
|
|
||||||
default=[("paragraph", "")],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -5,13 +5,14 @@ from wagtail.admin.panels import FieldPanel
|
|||||||
from wagtail.fields import RichTextField
|
from wagtail.fields import RichTextField
|
||||||
from wagtail.models import Page
|
from wagtail.models import Page
|
||||||
from wagtail.search import index
|
from wagtail.search import index
|
||||||
|
from wagtail_headless_preview.models import HeadlessMixin
|
||||||
|
|
||||||
from dnscms.fields import CommonStreamField
|
from dnscms.fields import CommonStreamField
|
||||||
from dnscms.wordpress.models import WPImportedPageMixin
|
from dnscms.wordpress.models import WPImportedPageMixin
|
||||||
|
|
||||||
|
|
||||||
@register_singular_query_field("newsIndex")
|
@register_singular_query_field("newsIndex")
|
||||||
class NewsIndex(Page):
|
class NewsIndex(HeadlessMixin, Page):
|
||||||
max_count = 1
|
max_count = 1
|
||||||
subpage_types = ["news.NewsPage"]
|
subpage_types = ["news.NewsPage"]
|
||||||
|
|
||||||
@@ -28,7 +29,7 @@ class NewsIndex(Page):
|
|||||||
search_fields = []
|
search_fields = []
|
||||||
|
|
||||||
|
|
||||||
class NewsPage(WPImportedPageMixin, Page):
|
class NewsPage(HeadlessMixin, WPImportedPageMixin, Page):
|
||||||
subpage_types = []
|
subpage_types = []
|
||||||
parent_page_types = ["news.NewsIndex"]
|
parent_page_types = ["news.NewsIndex"]
|
||||||
show_in_menus = False
|
show_in_menus = False
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
# Create your tests here.
|
|
||||||
+29
-9
@@ -4,20 +4,24 @@ version = "0.1.0"
|
|||||||
description = ""
|
description = ""
|
||||||
authors = [{ name = "EDB", email = "edb@neuf.no" }]
|
authors = [{ name = "EDB", email = "edb@neuf.no" }]
|
||||||
requires-python = ">=3.14, <3.15"
|
requires-python = ">=3.14, <3.15"
|
||||||
readme = "README.md"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wagtail>=7.3.1",
|
"wagtail>=7.4.1,<8",
|
||||||
"wagtail-grapple>=0.29.0",
|
"wagtail-grapple>=0.31.0,<0.32",
|
||||||
"django>=6.0.3",
|
"wagtail-headless-preview>=0.8,<0.9",
|
||||||
"django-extensions>=4.1",
|
"django>=6.0.5,<7",
|
||||||
"psycopg2-binary>=2.9.11,<3",
|
"django-extensions>=4.1,<5",
|
||||||
"gunicorn>=25.1.0",
|
"psycopg2-binary>=2.9.12,<3",
|
||||||
"whitenoise>=6.12.0",
|
"gunicorn>=26.0.0,<27",
|
||||||
|
"whitenoise>=6.12.0,<7",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
dev = [
|
dev = [
|
||||||
"ruff>=0.15.1",
|
"ruff>=0.15.13,<0.16",
|
||||||
|
"pytest>=9.0.3,<10",
|
||||||
|
"pytest-cov>=7.0.0,<8",
|
||||||
|
"pytest-django>=4.12.0,<5",
|
||||||
|
"wagtail-factories>=4.4.0,<5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.uv]
|
[tool.uv]
|
||||||
@@ -34,3 +38,19 @@ line-length = 99
|
|||||||
select = ["F", "E", "W", "Q", "UP", "DJ"]
|
select = ["F", "E", "W", "Q", "UP", "DJ"]
|
||||||
ignore = []
|
ignore = []
|
||||||
exclude = ["**/migrations/*.py"]
|
exclude = ["**/migrations/*.py"]
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
DJANGO_SETTINGS_MODULE = "dnscms.settings.test"
|
||||||
|
python_files = ["test_*.py"]
|
||||||
|
testpaths = ["tests"]
|
||||||
|
addopts = "--cov=. --cov-report=term-missing"
|
||||||
|
|
||||||
|
[tool.coverage.run]
|
||||||
|
omit = [
|
||||||
|
"*/migrations/*",
|
||||||
|
"tests/*",
|
||||||
|
"manage.py",
|
||||||
|
"dnscms/settings/*",
|
||||||
|
"dnscms/wsgi.py",
|
||||||
|
"dnscms/asgi.py",
|
||||||
|
]
|
||||||
|
|||||||
@@ -1,252 +0,0 @@
|
|||||||
# Generated by Django 6.0.3 on 2026-04-15 20:21
|
|
||||||
|
|
||||||
import wagtail.fields
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("sponsors", "0001_initial"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="sponsorspage",
|
|
||||||
name="body",
|
|
||||||
field=wagtail.fields.StreamField(
|
|
||||||
[
|
|
||||||
("paragraph", 0),
|
|
||||||
("image", 4),
|
|
||||||
("image_slider", 8),
|
|
||||||
("horizontal_rule", 10),
|
|
||||||
("featured", 18),
|
|
||||||
("page_section_navigation", 19),
|
|
||||||
("accordion", 23),
|
|
||||||
("fact_box", 26),
|
|
||||||
("photo_sphere", 29),
|
|
||||||
("embed", 30),
|
|
||||||
("raw_html", 31),
|
|
||||||
],
|
|
||||||
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.images.blocks.ImageChooserBlock",
|
|
||||||
(),
|
|
||||||
{"label": "360°-bilde"},
|
|
||||||
),
|
|
||||||
28: (
|
|
||||||
"wagtail.blocks.CharBlock",
|
|
||||||
(),
|
|
||||||
{
|
|
||||||
"help_text": "Beskrivende tittel på bildet (vises også til skjermlesere)",
|
|
||||||
"label": "Tittel",
|
|
||||||
"max_length": 256,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
29: (
|
|
||||||
"wagtail.blocks.StructBlock",
|
|
||||||
[[("image", 27), ("title", 28)]],
|
|
||||||
{},
|
|
||||||
),
|
|
||||||
30: ("wagtail.embeds.blocks.EmbedBlock", (), {}),
|
|
||||||
31: ("wagtail.blocks.RawHTMLBlock", (), {}),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -8,6 +8,7 @@ from wagtail.fields import RichTextField, StreamField
|
|||||||
from wagtail.images.blocks import ImageChooserBlock
|
from wagtail.images.blocks import ImageChooserBlock
|
||||||
from wagtail.models import Page
|
from wagtail.models import Page
|
||||||
from wagtail.search import index
|
from wagtail.search import index
|
||||||
|
from wagtail_headless_preview.models import HeadlessMixin
|
||||||
|
|
||||||
from dnscms.blocks import BASE_BLOCKS
|
from dnscms.blocks import BASE_BLOCKS
|
||||||
|
|
||||||
@@ -34,7 +35,7 @@ class SponsorBlock(blocks.StructBlock):
|
|||||||
|
|
||||||
|
|
||||||
@register_singular_query_field("sponsorsPage")
|
@register_singular_query_field("sponsorsPage")
|
||||||
class SponsorsPage(Page):
|
class SponsorsPage(HeadlessMixin, Page):
|
||||||
max_count = 1
|
max_count = 1
|
||||||
subpage_types = []
|
subpage_types = []
|
||||||
|
|
||||||
|
|||||||
@@ -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,64 @@
|
|||||||
|
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 wagtail_headless_preview.models import HeadlessMixin
|
||||||
|
|
||||||
|
from dnscms.blocks import BASE_BLOCKS, PageSectionBlock
|
||||||
|
from dnscms.options import ALL_PIGS
|
||||||
|
|
||||||
|
|
||||||
|
@register_singular_query_field("studioPage")
|
||||||
|
class StudioPage(HeadlessMixin, 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"),
|
||||||
|
]
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
import factory
|
||||||
|
import pytest
|
||||||
|
import wagtail_factories
|
||||||
|
from wagtail.models import Page
|
||||||
|
|
||||||
|
from associations.models import AssociationIndex, AssociationPage
|
||||||
|
from events.models import EventIndex, EventPage
|
||||||
|
from generic.models import GenericPage
|
||||||
|
from images.models import CustomImage
|
||||||
|
from news.models import NewsIndex, NewsPage
|
||||||
|
from studio.models import StudioPage
|
||||||
|
from venues.models import VenueIndex, VenuePage
|
||||||
|
|
||||||
|
|
||||||
|
class CustomImageFactory(wagtail_factories.ImageFactory):
|
||||||
|
class Meta:
|
||||||
|
model = CustomImage
|
||||||
|
|
||||||
|
|
||||||
|
class AssociationIndexFactory(wagtail_factories.PageFactory):
|
||||||
|
title = factory.Sequence(lambda n: f"Associations {n}")
|
||||||
|
lead = "<p>Foreninger og utvalg.</p>"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = AssociationIndex
|
||||||
|
|
||||||
|
|
||||||
|
class AssociationPageFactory(wagtail_factories.PageFactory):
|
||||||
|
title = factory.Sequence(lambda n: f"Association {n}")
|
||||||
|
excerpt = "Et utdrag."
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = AssociationPage
|
||||||
|
|
||||||
|
|
||||||
|
class EventIndexFactory(wagtail_factories.PageFactory):
|
||||||
|
title = factory.Sequence(lambda n: f"Events {n}")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = EventIndex
|
||||||
|
|
||||||
|
|
||||||
|
class EventPageFactory(wagtail_factories.PageFactory):
|
||||||
|
title = factory.Sequence(lambda n: f"Event {n}")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = EventPage
|
||||||
|
|
||||||
|
|
||||||
|
class GenericPageFactory(wagtail_factories.PageFactory):
|
||||||
|
title = factory.Sequence(lambda n: f"Page {n}")
|
||||||
|
lead = "<p>Ingress.</p>"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = GenericPage
|
||||||
|
|
||||||
|
|
||||||
|
class StudioPageFactory(wagtail_factories.PageFactory):
|
||||||
|
title = factory.Sequence(lambda n: f"Studio {n}")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = StudioPage
|
||||||
|
|
||||||
|
|
||||||
|
class NewsIndexFactory(wagtail_factories.PageFactory):
|
||||||
|
title = factory.Sequence(lambda n: f"News {n}")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = NewsIndex
|
||||||
|
|
||||||
|
|
||||||
|
class NewsPageFactory(wagtail_factories.PageFactory):
|
||||||
|
title = factory.Sequence(lambda n: f"Article {n}")
|
||||||
|
excerpt = "Et utdrag."
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = NewsPage
|
||||||
|
|
||||||
|
|
||||||
|
class VenueIndexFactory(wagtail_factories.PageFactory):
|
||||||
|
title = factory.Sequence(lambda n: f"Venues {n}")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = VenueIndex
|
||||||
|
|
||||||
|
|
||||||
|
class VenuePageFactory(wagtail_factories.PageFactory):
|
||||||
|
title = factory.Sequence(lambda n: f"Venue {n}")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = VenuePage
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def root_page(db):
|
||||||
|
return Page.objects.get(depth=1)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def home_page(root_page):
|
||||||
|
# Wagtail's initial migration creates a default "Welcome" page at depth=2.
|
||||||
|
# Reuse it so we don't fight slug collisions across tests.
|
||||||
|
return root_page.get_children().first() or root_page.add_child(
|
||||||
|
instance=Page(title="Home", slug="home")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def event_index(home_page):
|
||||||
|
return EventIndexFactory(parent=home_page)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def news_index(home_page):
|
||||||
|
return NewsIndexFactory(parent=home_page)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def association_index(home_page):
|
||||||
|
return AssociationIndexFactory(parent=home_page)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def venue(home_page):
|
||||||
|
venue_index = VenueIndexFactory(parent=home_page)
|
||||||
|
return VenuePageFactory(parent=venue_index)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def graphql_post(client):
|
||||||
|
def _post(query, variables=None):
|
||||||
|
payload = {"query": query}
|
||||||
|
if variables is not None:
|
||||||
|
payload["variables"] = variables
|
||||||
|
response = client.post(
|
||||||
|
"/api/graphql/",
|
||||||
|
data=json.dumps(payload),
|
||||||
|
content_type="application/json",
|
||||||
|
)
|
||||||
|
return response, response.json()
|
||||||
|
|
||||||
|
return _post
|
||||||
@@ -0,0 +1,389 @@
|
|||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from events.models import (
|
||||||
|
EventCategory,
|
||||||
|
EventOccurrence,
|
||||||
|
EventOrganizer,
|
||||||
|
EventOrganizerLink,
|
||||||
|
EventPage,
|
||||||
|
)
|
||||||
|
from tests.conftest import (
|
||||||
|
AssociationPageFactory,
|
||||||
|
CustomImageFactory,
|
||||||
|
EventPageFactory,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_eventpage_clean_unsets_specific_pricing_when_free():
|
||||||
|
page = EventPage(
|
||||||
|
title="Free event",
|
||||||
|
slug="free-event",
|
||||||
|
free=True,
|
||||||
|
price_regular="100",
|
||||||
|
price_student="50",
|
||||||
|
price_member="25",
|
||||||
|
)
|
||||||
|
|
||||||
|
page.clean()
|
||||||
|
|
||||||
|
assert page.price_regular == ""
|
||||||
|
assert page.price_student == ""
|
||||||
|
assert page.price_member == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_eventpage_clean_keeps_specific_pricing_when_not_free():
|
||||||
|
page = EventPage(
|
||||||
|
title="Paid event",
|
||||||
|
slug="paid-event",
|
||||||
|
free=False,
|
||||||
|
price_regular="100",
|
||||||
|
price_student="50",
|
||||||
|
price_member="25",
|
||||||
|
)
|
||||||
|
|
||||||
|
page.clean()
|
||||||
|
|
||||||
|
assert page.price_regular == "100"
|
||||||
|
assert page.price_student == "50"
|
||||||
|
assert page.price_member == "25"
|
||||||
|
|
||||||
|
|
||||||
|
def test_eventpage_clean_dedupes_organizers_by_name(event_index):
|
||||||
|
org_a = EventOrganizer.objects.create(name="DNS", slug="dns-a")
|
||||||
|
org_b = EventOrganizer.objects.create(name="DNS", slug="dns-b")
|
||||||
|
|
||||||
|
event = EventPageFactory(parent=event_index)
|
||||||
|
EventOrganizerLink.objects.create(event=event, organizer=org_a)
|
||||||
|
EventOrganizerLink.objects.create(event=event, organizer=org_b)
|
||||||
|
|
||||||
|
event = EventPage.objects.get(pk=event.pk)
|
||||||
|
assert event.organizer_links.count() == 2
|
||||||
|
|
||||||
|
event.clean()
|
||||||
|
|
||||||
|
assert event.organizer_links.count() == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_eventpage_clean_dedupes_three_duplicates_and_keeps_distinct(event_index):
|
||||||
|
dup_1 = EventOrganizer.objects.create(name="DNS", slug="dns-1")
|
||||||
|
dup_2 = EventOrganizer.objects.create(name="DNS", slug="dns-2")
|
||||||
|
dup_3 = EventOrganizer.objects.create(name="DNS", slug="dns-3")
|
||||||
|
distinct = EventOrganizer.objects.create(name="Studentersamfundet", slug="ss")
|
||||||
|
|
||||||
|
event = EventPageFactory(parent=event_index)
|
||||||
|
for organizer in (dup_1, dup_2, dup_3, distinct):
|
||||||
|
EventOrganizerLink.objects.create(event=event, organizer=organizer)
|
||||||
|
|
||||||
|
event = EventPage.objects.get(pk=event.pk)
|
||||||
|
assert event.organizer_links.count() == 4
|
||||||
|
|
||||||
|
event.clean()
|
||||||
|
|
||||||
|
names = sorted(link.organizer.name for link in event.organizer_links.all())
|
||||||
|
assert names == ["DNS", "Studentersamfundet"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_eventoccurrence_clean_rejects_both_venue_and_venue_custom(event_index, venue):
|
||||||
|
event = EventPageFactory(parent=event_index)
|
||||||
|
occurrence = EventOccurrence(
|
||||||
|
event=event,
|
||||||
|
start=timezone.now(),
|
||||||
|
venue=venue,
|
||||||
|
venue_custom="Frederikkeplassen",
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(ValidationError) as exc:
|
||||||
|
occurrence.clean()
|
||||||
|
assert "venue_custom" in exc.value.message_dict
|
||||||
|
|
||||||
|
|
||||||
|
def test_eventoccurrence_clean_requires_venue_or_venue_custom(event_index):
|
||||||
|
event = EventPageFactory(parent=event_index)
|
||||||
|
occurrence = EventOccurrence(event=event, start=timezone.now())
|
||||||
|
|
||||||
|
with pytest.raises(ValidationError) as exc:
|
||||||
|
occurrence.clean()
|
||||||
|
assert "venue" in exc.value.message_dict
|
||||||
|
|
||||||
|
|
||||||
|
def test_eventpage_manager_future_filters_past_and_annotates(event_index):
|
||||||
|
now = timezone.now()
|
||||||
|
|
||||||
|
past = EventPageFactory(parent=event_index, title="Past")
|
||||||
|
EventOccurrence.objects.create(event=past, start=now - timedelta(days=7), venue_custom="Old")
|
||||||
|
|
||||||
|
future = EventPageFactory(parent=event_index, title="Future")
|
||||||
|
EventOccurrence.objects.create(event=future, start=now + timedelta(days=7), venue_custom="New")
|
||||||
|
|
||||||
|
results = list(EventPage.objects.live().future().order_by("next_occurrence"))
|
||||||
|
|
||||||
|
assert [p.pk for p in results] == [future.pk]
|
||||||
|
assert results[0].next_occurrence is not None
|
||||||
|
|
||||||
|
|
||||||
|
def test_future_includes_occurrence_late_today(event_index):
|
||||||
|
today_start = timezone.localtime(timezone.now()).replace(
|
||||||
|
hour=0, minute=0, second=0, microsecond=0
|
||||||
|
)
|
||||||
|
late_today = today_start + timedelta(hours=23, minutes=59)
|
||||||
|
|
||||||
|
event = EventPageFactory(parent=event_index, title="Late today")
|
||||||
|
EventOccurrence.objects.create(event=event, start=late_today, venue_custom="X")
|
||||||
|
|
||||||
|
assert event.pk in EventPage.objects.future().values_list("pk", flat=True)
|
||||||
|
|
||||||
|
|
||||||
|
def test_future_excludes_occurrence_just_before_today(event_index):
|
||||||
|
today_start = timezone.localtime(timezone.now()).replace(
|
||||||
|
hour=0, minute=0, second=0, microsecond=0
|
||||||
|
)
|
||||||
|
just_before_today = today_start - timedelta(seconds=1)
|
||||||
|
|
||||||
|
event = EventPageFactory(parent=event_index, title="Just past")
|
||||||
|
EventOccurrence.objects.create(event=event, start=just_before_today, venue_custom="X")
|
||||||
|
|
||||||
|
assert event.pk not in EventPage.objects.future().values_list("pk", flat=True)
|
||||||
|
|
||||||
|
|
||||||
|
def test_future_next_occurrence_picks_earliest_future_ignoring_past(event_index):
|
||||||
|
now = timezone.now()
|
||||||
|
soonest_future = now + timedelta(days=3)
|
||||||
|
|
||||||
|
event = EventPageFactory(parent=event_index, title="With history")
|
||||||
|
EventOccurrence.objects.create(event=event, start=now - timedelta(days=30), venue_custom="X")
|
||||||
|
EventOccurrence.objects.create(event=event, start=soonest_future, venue_custom="X")
|
||||||
|
EventOccurrence.objects.create(event=event, start=now + timedelta(days=10), venue_custom="X")
|
||||||
|
|
||||||
|
annotated = EventPage.objects.future().filter(pk=event.pk).first()
|
||||||
|
assert annotated is not None
|
||||||
|
assert abs((annotated.next_occurrence - soonest_future).total_seconds()) < 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_graphql_event_index_future_events_query(event_index, graphql_post):
|
||||||
|
upcoming = EventPageFactory(parent=event_index, title="Upcoming gig")
|
||||||
|
EventOccurrence.objects.create(
|
||||||
|
event=upcoming,
|
||||||
|
start=timezone.now() + timedelta(days=3),
|
||||||
|
venue_custom="Storsalen",
|
||||||
|
)
|
||||||
|
|
||||||
|
response, body = graphql_post(
|
||||||
|
"""
|
||||||
|
query {
|
||||||
|
eventIndex {
|
||||||
|
futureEvents { title }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert "errors" not in body, body
|
||||||
|
titles = [e["title"] for e in body["data"]["eventIndex"]["futureEvents"]]
|
||||||
|
assert "Upcoming gig" in titles
|
||||||
|
|
||||||
|
|
||||||
|
def test_graphql_event_index_future_events_ordered_by_next_occurrence(event_index, graphql_post):
|
||||||
|
now = timezone.now()
|
||||||
|
|
||||||
|
later = EventPageFactory(parent=event_index, title="Later gig")
|
||||||
|
EventOccurrence.objects.create(event=later, start=now + timedelta(days=10), venue_custom="X")
|
||||||
|
|
||||||
|
sooner = EventPageFactory(parent=event_index, title="Sooner gig")
|
||||||
|
EventOccurrence.objects.create(event=sooner, start=now + timedelta(days=3), venue_custom="X")
|
||||||
|
|
||||||
|
response, body = graphql_post(
|
||||||
|
"""
|
||||||
|
query {
|
||||||
|
eventIndex {
|
||||||
|
futureEvents { title }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert "errors" not in body, body
|
||||||
|
titles = [e["title"] for e in body["data"]["eventIndex"]["futureEvents"]]
|
||||||
|
assert titles.index("Sooner gig") < titles.index("Later gig")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def comprehensive_event(event_index, venue, association_index):
|
||||||
|
"""A fully-populated paid EventPage exercising every field exposed via GraphQL."""
|
||||||
|
image = CustomImageFactory(
|
||||||
|
title="Cover",
|
||||||
|
alt="Et fotografi av en gris med solbriller",
|
||||||
|
attribution="Foto: Test",
|
||||||
|
)
|
||||||
|
|
||||||
|
konsert = EventCategory.objects.create(
|
||||||
|
name="Konsert", slug="konsert", show_in_filters=True, pig="pigHeadLogo"
|
||||||
|
)
|
||||||
|
klubb = EventCategory.objects.create(name="Klubb", slug="klubb")
|
||||||
|
|
||||||
|
association = AssociationPageFactory(
|
||||||
|
parent=association_index,
|
||||||
|
title="Internal",
|
||||||
|
association_type="forening",
|
||||||
|
)
|
||||||
|
internal_org = EventOrganizer.objects.create(
|
||||||
|
name="Internal", slug="internal", association=association
|
||||||
|
)
|
||||||
|
external_org = EventOrganizer.objects.create(
|
||||||
|
name="External",
|
||||||
|
slug="external",
|
||||||
|
external_url="https://external.example.com",
|
||||||
|
)
|
||||||
|
|
||||||
|
event = EventPageFactory(
|
||||||
|
parent=event_index,
|
||||||
|
title="Et arrangement",
|
||||||
|
slug="et-arrangement",
|
||||||
|
subtitle="En undertekst",
|
||||||
|
lead="<p>Ingress.</p>",
|
||||||
|
body=[("paragraph", "<p>Body content.</p>")],
|
||||||
|
pig="automatic",
|
||||||
|
free=False,
|
||||||
|
price_regular="150",
|
||||||
|
price_student="100",
|
||||||
|
price_member="75",
|
||||||
|
ticket_url="https://example.com/tickets",
|
||||||
|
facebook_url="https://facebook.com/example",
|
||||||
|
featured_image=image,
|
||||||
|
)
|
||||||
|
event.categories.add(konsert, klubb)
|
||||||
|
EventOrganizerLink.objects.create(event=event, organizer=internal_org)
|
||||||
|
EventOrganizerLink.objects.create(event=event, organizer=external_org)
|
||||||
|
|
||||||
|
now = timezone.now()
|
||||||
|
EventOccurrence.objects.create(
|
||||||
|
event=event,
|
||||||
|
start=now + timedelta(days=5),
|
||||||
|
end=now + timedelta(days=5, hours=3),
|
||||||
|
venue=venue,
|
||||||
|
)
|
||||||
|
EventOccurrence.objects.create(
|
||||||
|
event=event,
|
||||||
|
start=now + timedelta(days=12),
|
||||||
|
end=now + timedelta(days=12, hours=2),
|
||||||
|
venue_custom="Frederikkeplassen",
|
||||||
|
)
|
||||||
|
|
||||||
|
event.save()
|
||||||
|
return event
|
||||||
|
|
||||||
|
|
||||||
|
def test_graphql_event_index_returns_all_fields_for_comprehensive_event(
|
||||||
|
comprehensive_event, graphql_post
|
||||||
|
):
|
||||||
|
response, body = graphql_post(
|
||||||
|
"""
|
||||||
|
query {
|
||||||
|
eventIndex {
|
||||||
|
futureEvents {
|
||||||
|
title
|
||||||
|
slug
|
||||||
|
subtitle
|
||||||
|
lead
|
||||||
|
body {
|
||||||
|
blockType
|
||||||
|
field
|
||||||
|
... on RichTextBlock {
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pig
|
||||||
|
free
|
||||||
|
priceRegular
|
||||||
|
priceStudent
|
||||||
|
priceMember
|
||||||
|
ticketUrl
|
||||||
|
facebookUrl
|
||||||
|
featuredImage {
|
||||||
|
alt
|
||||||
|
attribution
|
||||||
|
}
|
||||||
|
categories {
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
showInFilters
|
||||||
|
pig
|
||||||
|
}
|
||||||
|
organizers {
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
externalUrl
|
||||||
|
association {
|
||||||
|
title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
occurrences {
|
||||||
|
start
|
||||||
|
end
|
||||||
|
venueCustom
|
||||||
|
venue {
|
||||||
|
title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert "errors" not in body, body
|
||||||
|
|
||||||
|
events = body["data"]["eventIndex"]["futureEvents"]
|
||||||
|
event = next(e for e in events if e["title"] == "Et arrangement")
|
||||||
|
|
||||||
|
assert event["slug"] == "et-arrangement"
|
||||||
|
assert event["subtitle"] == "En undertekst"
|
||||||
|
assert "Ingress." in event["lead"]
|
||||||
|
assert event["pig"] == "automatic"
|
||||||
|
assert event["free"] is False
|
||||||
|
assert event["priceRegular"] == "150"
|
||||||
|
assert event["priceStudent"] == "100"
|
||||||
|
assert event["priceMember"] == "75"
|
||||||
|
assert event["ticketUrl"] == "https://example.com/tickets"
|
||||||
|
assert event["facebookUrl"] == "https://facebook.com/example"
|
||||||
|
|
||||||
|
assert event["featuredImage"]["alt"] == "Et fotografi av en gris med solbriller"
|
||||||
|
assert event["featuredImage"]["attribution"] == "Foto: Test"
|
||||||
|
|
||||||
|
assert event["body"][0]["blockType"] == "RichTextBlock"
|
||||||
|
assert "Body content." in event["body"][0]["value"]
|
||||||
|
|
||||||
|
categories_by_name = {c["name"]: c for c in event["categories"]}
|
||||||
|
assert set(categories_by_name) == {"Konsert", "Klubb"}
|
||||||
|
assert categories_by_name["Konsert"]["slug"] == "konsert"
|
||||||
|
assert categories_by_name["Konsert"]["showInFilters"] is True
|
||||||
|
assert categories_by_name["Konsert"]["pig"] == "pigHeadLogo"
|
||||||
|
assert categories_by_name["Klubb"]["showInFilters"] is False
|
||||||
|
|
||||||
|
organizers_by_name = {o["name"]: o for o in event["organizers"]}
|
||||||
|
assert set(organizers_by_name) == {"Internal", "External"}
|
||||||
|
assert organizers_by_name["Internal"]["association"]["title"] == "Internal"
|
||||||
|
assert organizers_by_name["Internal"]["externalUrl"] == ""
|
||||||
|
assert organizers_by_name["External"]["association"] is None
|
||||||
|
assert organizers_by_name["External"]["externalUrl"] == "https://external.example.com"
|
||||||
|
|
||||||
|
assert len(event["occurrences"]) == 2
|
||||||
|
venue_occ = next(o for o in event["occurrences"] if o["venue"] is not None)
|
||||||
|
custom_occ = next(o for o in event["occurrences"] if o["venueCustom"])
|
||||||
|
assert venue_occ["venueCustom"] == ""
|
||||||
|
assert venue_occ["venue"]["title"]
|
||||||
|
assert custom_occ["venue"] is None
|
||||||
|
assert custom_occ["venueCustom"] == "Frederikkeplassen"
|
||||||
|
|
||||||
|
venue_occ_db = comprehensive_event.occurrences.exclude(venue=None).get()
|
||||||
|
custom_occ_db = comprehensive_event.occurrences.exclude(venue_custom="").get()
|
||||||
|
assert datetime.fromisoformat(venue_occ["start"]) == venue_occ_db.start
|
||||||
|
assert datetime.fromisoformat(venue_occ["end"]) == venue_occ_db.end
|
||||||
|
assert datetime.fromisoformat(custom_occ["start"]) == custom_occ_db.start
|
||||||
|
assert datetime.fromisoformat(custom_occ["end"]) == custom_occ_db.end
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
from generic.models import GenericPage
|
||||||
|
from tests.conftest import GenericPageFactory
|
||||||
|
|
||||||
|
|
||||||
|
def test_generic_page_persists_via_factory(home_page):
|
||||||
|
page = GenericPageFactory(
|
||||||
|
parent=home_page,
|
||||||
|
title="Om oss",
|
||||||
|
slug="om-oss",
|
||||||
|
lead="<p>Ingress.</p>",
|
||||||
|
body=[("paragraph", "<p>Body content.</p>")],
|
||||||
|
pig="drink",
|
||||||
|
)
|
||||||
|
|
||||||
|
reloaded = GenericPage.objects.get(pk=page.pk)
|
||||||
|
assert reloaded.title == "Om oss"
|
||||||
|
assert reloaded.slug == "om-oss"
|
||||||
|
assert "Ingress." in reloaded.lead
|
||||||
|
assert reloaded.pig == "drink"
|
||||||
|
assert reloaded.body[0].block_type == "paragraph"
|
||||||
|
|
||||||
|
|
||||||
|
def test_generic_page_allows_recursive_children(home_page):
|
||||||
|
parent = GenericPageFactory(parent=home_page, title="Parent", slug="parent")
|
||||||
|
child = GenericPageFactory(parent=parent, title="Child", slug="child")
|
||||||
|
|
||||||
|
assert child.get_parent().specific == parent
|
||||||
|
assert list(parent.get_children().specific()) == [child]
|
||||||
|
|
||||||
|
|
||||||
|
def test_graphql_generic_page_query(home_page, graphql_post):
|
||||||
|
GenericPageFactory(
|
||||||
|
parent=home_page,
|
||||||
|
title="Om oss",
|
||||||
|
slug="om-oss",
|
||||||
|
lead="<p>Ingress text.</p>",
|
||||||
|
body=[("paragraph", "<p>Body content.</p>")],
|
||||||
|
pig="drink",
|
||||||
|
)
|
||||||
|
|
||||||
|
response, body = graphql_post(
|
||||||
|
"""
|
||||||
|
query {
|
||||||
|
page(slug: "om-oss", contentType: "generic.GenericPage") {
|
||||||
|
title
|
||||||
|
slug
|
||||||
|
... on GenericPage {
|
||||||
|
lead
|
||||||
|
pig
|
||||||
|
body {
|
||||||
|
blockType
|
||||||
|
field
|
||||||
|
... on RichTextBlock {
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert "errors" not in body, body
|
||||||
|
data = body["data"]["page"]
|
||||||
|
assert data["title"] == "Om oss"
|
||||||
|
assert data["slug"] == "om-oss"
|
||||||
|
assert "Ingress text." in data["lead"]
|
||||||
|
assert data["pig"] == "drink"
|
||||||
|
assert data["body"][0]["blockType"] == "RichTextBlock"
|
||||||
|
assert "Body content." in data["body"][0]["value"]
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
def test_graphql_endpoint_responds(db, graphql_post):
|
||||||
|
response, body = graphql_post("{ __schema { queryType { name } } }")
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert "errors" not in body
|
||||||
|
assert body["data"]["__schema"]["queryType"]["name"] == "Query"
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
from news.models import NewsPage
|
||||||
|
from tests.conftest import NewsPageFactory
|
||||||
|
|
||||||
|
|
||||||
|
def test_news_page_persists_via_factory(news_index):
|
||||||
|
page = NewsPageFactory(parent=news_index, title="Big news", excerpt="Short summary")
|
||||||
|
|
||||||
|
reloaded = NewsPage.objects.get(pk=page.pk)
|
||||||
|
assert reloaded.title == "Big news"
|
||||||
|
assert reloaded.excerpt == "Short summary"
|
||||||
|
|
||||||
|
|
||||||
|
def test_graphql_news_index_query(news_index, graphql_post):
|
||||||
|
response, body = graphql_post(
|
||||||
|
"""
|
||||||
|
query {
|
||||||
|
newsIndex {
|
||||||
|
title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert "errors" not in body, body
|
||||||
|
assert body["data"]["newsIndex"]["title"] == news_index.title
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import datetime
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from openinghours.models import OpeningHoursItem, OpeningHoursSet
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def opening_hours_set(db):
|
||||||
|
return OpeningHoursSet.objects.create(
|
||||||
|
name="Vanlige åpningstider",
|
||||||
|
effective_from=datetime.date(2025, 1, 1),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_opening_hours_set_str_with_end_date():
|
||||||
|
ohs = OpeningHoursSet(
|
||||||
|
name="Sommer",
|
||||||
|
effective_from=datetime.date(2025, 6, 1),
|
||||||
|
effective_to=datetime.date(2025, 8, 31),
|
||||||
|
)
|
||||||
|
assert str(ohs) == "Sommer (2025-06-01 - 2025-08-31)"
|
||||||
|
|
||||||
|
|
||||||
|
def test_opening_hours_set_str_uses_infinity_when_open_ended():
|
||||||
|
ohs = OpeningHoursSet(
|
||||||
|
name="Forever",
|
||||||
|
effective_from=datetime.date(2025, 1, 1),
|
||||||
|
effective_to=None,
|
||||||
|
)
|
||||||
|
assert str(ohs) == "Forever (2025-01-01 - ∞)"
|
||||||
|
|
||||||
|
|
||||||
|
def test_opening_hours_streamfield_week_roundtrip(opening_hours_set):
|
||||||
|
OpeningHoursItem.objects.create(
|
||||||
|
opening_hours_set=opening_hours_set,
|
||||||
|
function="glassbaren",
|
||||||
|
week=[
|
||||||
|
(
|
||||||
|
"week",
|
||||||
|
{
|
||||||
|
"monday": {
|
||||||
|
"time_from": datetime.time(15, 0),
|
||||||
|
"time_to": datetime.time(23, 0),
|
||||||
|
"custom": "",
|
||||||
|
},
|
||||||
|
"tuesday": {"time_from": None, "time_to": None, "custom": "Stengt"},
|
||||||
|
"wednesday": {"time_from": None, "time_to": None, "custom": ""},
|
||||||
|
"thursday": {"time_from": None, "time_to": None, "custom": ""},
|
||||||
|
"friday": {"time_from": None, "time_to": None, "custom": ""},
|
||||||
|
"saturday": {"time_from": None, "time_to": None, "custom": ""},
|
||||||
|
"sunday": {"time_from": None, "time_to": None, "custom": ""},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
reloaded = OpeningHoursSet.objects.get(pk=opening_hours_set.pk)
|
||||||
|
item = reloaded.items.get()
|
||||||
|
assert item.function == "glassbaren"
|
||||||
|
|
||||||
|
week_block = item.week[0]
|
||||||
|
assert week_block.block_type == "week"
|
||||||
|
assert week_block.value["monday"]["time_from"] == datetime.time(15, 0)
|
||||||
|
assert week_block.value["monday"]["time_to"] == datetime.time(23, 0)
|
||||||
|
assert week_block.value["tuesday"]["custom"] == "Stengt"
|
||||||
|
|
||||||
|
|
||||||
|
def test_graphql_opening_hours_sets_query(db, graphql_post):
|
||||||
|
OpeningHoursSet.objects.create(
|
||||||
|
name="Sommer 2025",
|
||||||
|
effective_from=datetime.date(2025, 6, 1),
|
||||||
|
effective_to=datetime.date(2025, 8, 31),
|
||||||
|
)
|
||||||
|
|
||||||
|
response, body = graphql_post(
|
||||||
|
"""
|
||||||
|
query {
|
||||||
|
openingHoursSets {
|
||||||
|
name
|
||||||
|
effectiveFrom
|
||||||
|
effectiveTo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert "errors" not in body, body
|
||||||
|
names = [s["name"] for s in body["data"]["openingHoursSets"]]
|
||||||
|
assert "Sommer 2025" in names
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
"""Round-trip tests for wagtail-headless-preview token resolution via grapple."""
|
||||||
|
|
||||||
|
from generic.models import GenericPage
|
||||||
|
from tests.conftest import GenericPageFactory
|
||||||
|
|
||||||
|
|
||||||
|
def test_generic_page_preview_token_resolves_draft(home_page, graphql_post):
|
||||||
|
"""A minted preview token returns the unsaved draft via grapple's page(token: …)."""
|
||||||
|
# Publish a baseline so there's a live revision to diverge from.
|
||||||
|
page = GenericPageFactory(parent=home_page, title="Original title", slug="generic-preview")
|
||||||
|
|
||||||
|
# Mutate in-memory to simulate unsaved editor state, then mint a token.
|
||||||
|
# create_page_preview() snapshots the current to_json() into a PagePreview row.
|
||||||
|
page.title = "Edited title (draft)"
|
||||||
|
preview = page.create_page_preview()
|
||||||
|
|
||||||
|
response, body = graphql_post(
|
||||||
|
"""
|
||||||
|
query previewPage($token: String!) {
|
||||||
|
page: page(token: $token) {
|
||||||
|
__typename
|
||||||
|
... on GenericPage {
|
||||||
|
title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
variables={"token": preview.token},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert "errors" not in body, body
|
||||||
|
assert body["data"]["page"]["__typename"] == "GenericPage"
|
||||||
|
assert body["data"]["page"]["title"] == "Edited title (draft)"
|
||||||
|
|
||||||
|
# Live revision is unchanged — token short-circuits the published query.
|
||||||
|
assert GenericPage.objects.get(pk=page.pk).title == "Original title"
|
||||||
@@ -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"]
|
||||||
Generated
+227
-61
@@ -62,6 +62,54 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" },
|
{ url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorama"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "coverage"
|
||||||
|
version = "7.14.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/23/7f/d0720730a397a999ffc0fd3f5bebef347338e3a47b727da66fbb228e2ff2/coverage-7.14.0.tar.gz", hash = "sha256:057a6af2f160a85384cde4ab36f0d2777bae1057bae255f95413cdd382aa5c74", size = 919489, upload-time = "2026-05-10T18:02:31.397Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1c/18/b9a6586d73992807c26f9a5f274131be3d76b56b18a82b9392e2a25d2e45/coverage-7.14.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9aed9fa983514ca032790f3fe0d1c0e42ca7e16b42432af1706b50a9a46bef5d", size = 220036, upload-time = "2026-05-10T18:01:33.057Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f3/9b/4165a1d56ddc302a0e2d518fd9d412a4fd0b57562618c78c5f21c57194f5/coverage-7.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ba3b8390db29296dbbf49e91b6fe08f990743a90c8f447ba4c2ffc29670dfa63", size = 220368, upload-time = "2026-05-10T18:01:34.705Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/69/aa/c12e52a5ba148d9995229d557e3be6e554fe469addc0e9241b2f0956d8ea/coverage-7.14.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3a5d8e876dfa2f102e970b183863d6dedd023d3c0eeca1fe7a9787bc5f28b212", size = 251417, upload-time = "2026-05-10T18:01:36.949Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d7/51/ec641c26e6dca1b25a7d2035ba6ecb7c884ef1a100a9e42fbe4ce4405139/coverage-7.14.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5ebb8f4614a3787d567e610bbfdf96a4798dd69a1afb1bd8ad228d4111fe6ff3", size = 253924, upload-time = "2026-05-10T18:01:38.985Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/33/c4/59c3de0bd1b538824173fd518fed51c1ce740ca5ed68e74545983f4053a9/coverage-7.14.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b9bf47223dd8db3d4c4b2e443b02bace480d428f0822c3f991600448a176c97", size = 255269, upload-time = "2026-05-10T18:01:40.957Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7b/a9/36dfa153a62040296f6e7febfdb20a5720622f6ef5a81a41e8237b9a5344/coverage-7.14.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3485a836550b303d006d57cc06e3d5afaabc642c77050b7c985a97b13e3776b8", size = 257583, upload-time = "2026-05-10T18:01:42.607Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/26/7b/cc2c048d4114d9ab1c2409e9ee365e5ae10736df6dffcfc9444effa6c708/coverage-7.14.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3e7e88110bae996d199d1693ca8ec3fd52441d426401ae963437598667b4c5eb", size = 251434, upload-time = "2026-05-10T18:01:44.537Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ee/df/6770eaa576e604575e9a78055313250faef5faa84bd6f71a39fece519c43/coverage-7.14.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:15228a6800ce7bdf1b74800595e56db7138cecb338fdbf044806e10dcf182dfe", size = 253280, upload-time = "2026-05-10T18:01:46.175Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ad/9e/1c0264514a3f98259a6d64765a397b2c8373e3ba59ee722a4802d3ec0c61/coverage-7.14.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9d26ac7f5398bafc5b57421ad994e8a4749e8a7a0e62d05ec7d53014d5963bfa", size = 251241, upload-time = "2026-05-10T18:01:48.732Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/64/16/4efdf3e3c4079cdbf0ece56a2fea872df9e8a3e15a13a0af4400e1075944/coverage-7.14.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2fb73254ff43c911c967a899e1359bc5049b4b115d6e8fbdde4937d0a2246cd5", size = 255516, upload-time = "2026-05-10T18:01:50.819Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/93/69/b1de96346603881b3d1bc8d6447c83200e1c9700ffbaff926ba01ff5724c/coverage-7.14.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:454a380af72c6adada298ed270d38c7a391288198dbfb8467f786f588751a90c", size = 251059, upload-time = "2026-05-10T18:01:52.773Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a4/66/2881853e0363a5e0a724d1103e53650795367471b6afb234f8b49e713bc6/coverage-7.14.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:65c86fb646d2bd2972e96bd1a8b45817ed907cee68655d6295fe7ec031d04cca", size = 252716, upload-time = "2026-05-10T18:01:54.506Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/55/5c/0d3305d002c41dcde873dbe456491e663dc55152ca526b630b5c47efd62f/coverage-7.14.0-cp314-cp314-win32.whl", hash = "sha256:6a6516b02a6101398e19a3f44820f69bab2590697f7def4331f668b14adaf828", size = 222788, upload-time = "2026-05-10T18:01:56.487Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f9/58/6e1b8f52fdc3184b47dc5037f5070d83a3d11042db1594b02d2a44d786c8/coverage-7.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:45e0f79d8351fa76e256716df91eab12890d32678b9590df7ae1042e4bd4cf5d", size = 223600, upload-time = "2026-05-10T18:01:58.497Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/00/70/a18c408e674bc26281cadaedc7351f929bd2094e191e4b15271c30b084cc/coverage-7.14.0-cp314-cp314-win_arm64.whl", hash = "sha256:4b899594a8b2d81e5cc064a0d7f9cac2081fed91049456cae7676787e41549c9", size = 222168, upload-time = "2026-05-10T18:02:00.411Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3d/89/2681f071d238b62aff8dfc2ab44fc24cfdb38d1c01f391a80522ff5d3a16/coverage-7.14.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f580f8c80acd94ac72e863efe2cab791d8c38d153e0b463b92dfa000d5c84cd1", size = 220766, upload-time = "2026-05-10T18:02:02.313Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bd/c7/c987babafd9207ffa1995e1ef1f9b26762cf4963aa768a66b6f0501e4616/coverage-7.14.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a2bd259c442cd43c49b30fbafc51776eb19ea396faf159d26a83e6a0a5f13b0c", size = 221035, upload-time = "2026-05-10T18:02:04.017Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5a/e9/d6a5ac3b333088143d6fc877d398a9a674dc03124a2f776e131f03864823/coverage-7.14.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a706b908dfa85538863504c624b237a3cc34232bf403c057414ebfdb3b4d9f84", size = 262405, upload-time = "2026-05-10T18:02:05.915Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/38/b1/e70838d29a7c08e22d44398a46db90815bbcbf28de06992bd9210d1a8d8e/coverage-7.14.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7333cd944ee4393b9b3d3c1b598c936d4fc8d70573a4c7dacfec5590dd50e436", size = 264530, upload-time = "2026-05-10T18:02:07.582Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6b/73/5c31ef97763288d03d9995152b96d5475b527c63d91c84b01caea894b83a/coverage-7.14.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f162bc9a15b82d947b02651b0c7e1609d6f7a8735ca330cfadec8481dd97d5a", size = 266932, upload-time = "2026-05-10T18:02:09.401Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e1/76/dd56d80f29c5f05b4d76f7e7c6d47cafacae017189c75c5759d24f9ff0cc/coverage-7.14.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:362cb78e01a5dc82009d88004cf60f2e6b6d6fcbfdec05b05af73b0abf40118f", size = 268062, upload-time = "2026-05-10T18:02:11.399Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6e/c7/27ba85cd5b95614f159ff93ebff1901584a8d192e2e5e24c4943a7453f59/coverage-7.14.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:acebd068fca5512c3a6fde9c045f901613478781a73f0e82b307b214daef23fb", size = 261504, upload-time = "2026-05-10T18:02:13.257Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/13/2e/e8149f60ab5d5684c6eee881bdf34b127115cddbb958b196768dd9d63473/coverage-7.14.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:29fe3da551dface75deb2ccbf87b6b66e2e7ef38f6d89050b428be94afff3490", size = 264398, upload-time = "2026-05-10T18:02:15.063Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d9/7f/1261b025285323225f4b4abffa5a643649dfd67e25ddca7ebcbdea3b7cb3/coverage-7.14.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b4cc4fce8672fffcb09b0eafc167b396b3ba53c4a7230f54b7aaffbf6c835fa9", size = 262000, upload-time = "2026-05-10T18:02:16.756Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d3/dc/829c54f60b9d08389439c00f813c752781c496fc5788c78d8006db4b4f2b/coverage-7.14.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5d4a51aad8ba8bdcd2b8bd8f03d4aca19693fa2327a3470e4718a25b03481020", size = 265732, upload-time = "2026-05-10T18:02:18.817Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ed/b0/70bd1419941652fa062689cba9c3eeafb8f5e6fbb890bce41c3bdda5dbd6/coverage-7.14.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:9f323af3e1e4f68b60b7b247e37b8515563a61375518fa59de1af48ba28a3db6", size = 260847, upload-time = "2026-05-10T18:02:20.528Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f2/73/be40b2390656c654d35ea0015ea7ba3d945769cf80790ad5e0bb2d56d2ba/coverage-7.14.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:1a0abc7342ea9711c469dd8b821c6c311e6bc6aac1442e5fbd6b27fae0a8f3db", size = 263166, upload-time = "2026-05-10T18:02:22.337Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/29/55/4a643f712fcf7cf2881f8ec1e0ccb7b164aff3108f69b51801246c8799f2/coverage-7.14.0-cp314-cp314t-win32.whl", hash = "sha256:a9f864ef57b7172e2db87a096642dd51e179e085ab6b2c371c29e885f65c8fb2", size = 223573, upload-time = "2026-05-10T18:02:24.11Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/27/96/3acae5da0953be042c0b4dea6d6789d2f080701c77b88e44d5bd41b9219b/coverage-7.14.0-cp314-cp314t-win_amd64.whl", hash = "sha256:29943e552fdc08e082eb51400fb2f58e118a83b5542bd06531214e084399b644", size = 224680, upload-time = "2026-05-10T18:02:25.896Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/93/3d/6ab5d2dd8325d838737c6f8d83d62eb6230e0d70b87b51b57bbfd08fa767/coverage-7.14.0-cp314-cp314t-win_arm64.whl", hash = "sha256:742a73ea621953b012f2c4c2219b512180dd84489acf5b1596b0aafc55b9100b", size = 222703, upload-time = "2026-05-10T18:02:27.822Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/61/e8/cb8e80d6f9f55b99588625062822bf946cf03ed06315df4bd8397f5632a1/coverage-7.14.0-py3-none-any.whl", hash = "sha256:8de5b61163aee3d05c8a2beab6f47913df7981dad1baf82c414d99158c286ab1", size = 211764, upload-time = "2026-05-10T18:02:29.538Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "defusedxml"
|
name = "defusedxml"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
@@ -73,16 +121,16 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "django"
|
name = "django"
|
||||||
version = "6.0.3"
|
version = "6.0.5"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "asgiref" },
|
{ name = "asgiref" },
|
||||||
{ name = "sqlparse" },
|
{ name = "sqlparse" },
|
||||||
{ name = "tzdata", marker = "sys_platform == 'win32'" },
|
{ name = "tzdata", marker = "sys_platform == 'win32'" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/80/e1/894115c6bd70e2c8b66b0c40a3c367d83a5a48c034a4d904d31b62f7c53a/django-6.0.3.tar.gz", hash = "sha256:90be765ee756af8a6cbd6693e56452404b5ad15294f4d5e40c0a55a0f4870fe1", size = 10872701, upload-time = "2026-03-03T13:55:15.026Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/5e/f1/bf85f0d29ef76abf901f193fe8fef4769d3da7794197832bc30151c071d8/django-6.0.5.tar.gz", hash = "sha256:bc6d6872e98a2864c836e42edd644b362db311147dd5aa8d5b82ba7a032f5269", size = 10924131, upload-time = "2026-05-05T13:54:39.329Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/72/b1/23f2556967c45e34d3d3cf032eb1bd3ef925ee458667fb99052a0b3ea3a6/django-6.0.3-py3-none-any.whl", hash = "sha256:2e5974441491ddb34c3f13d5e7a9f97b07ba03bf70234c0a9c68b79bbb235bc3", size = 8358527, upload-time = "2026-03-03T13:55:10.552Z" },
|
{ url = "https://files.pythonhosted.org/packages/94/5b/1328f8b84fce040c404f76822bf8c57d254e368e8cbd8bd67ec2b26d75f5/django-6.0.5-py3-none-any.whl", hash = "sha256:9d58a7cb49244e74c8e161d5e403a46d6209f1009ba40f5a66d6aa0d0786a8f0", size = 8368680, upload-time = "2026-05-05T13:54:33.532Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -111,14 +159,14 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "django-modelcluster"
|
name = "django-modelcluster"
|
||||||
version = "6.4.1"
|
version = "6.5"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "django" },
|
{ name = "django" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/5b/f7/51efcea27d74665230be07b63e631e16204893ef32338a0e671c5ee0cd40/django_modelcluster-6.4.1.tar.gz", hash = "sha256:e736fcee925f83b63218dbf9c869ab50618b0f5e98869a5aa497f7a5331aa263", size = 29029, upload-time = "2025-12-04T12:21:41.907Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/34/0f/c5fd0c280a10224d619325783bfaab5a54903f82340e41ae21ab3127ebc9/django_modelcluster-6.5.tar.gz", hash = "sha256:459cbf0fb3bbc8c15ea9a2a062abc0d1ef935a38e04aa8ac3cb241c826bbc6dd", size = 29707, upload-time = "2026-05-01T12:25:33.524Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/a5/e4/ec99d52aa04e204e938564b603f4591e2e82e236ed59af664fee35179e75/django_modelcluster-6.4.1-py2.py3-none-any.whl", hash = "sha256:ccc190cd9e22c24900ea2410bff64d444d48f43f0f4aedeed0f6cd94e2536698", size = 29315, upload-time = "2025-12-04T12:21:39.911Z" },
|
{ url = "https://files.pythonhosted.org/packages/cd/57/cdcdea9a56d114e9217d52ce9a8af19ad7fe8a0996faf8480d3f286c02e0/django_modelcluster-6.5-py3-none-any.whl", hash = "sha256:469b380a294027d5211d0e5d5746e0381f445b058d2229977a58fe0f1c58a596", size = 29865, upload-time = "2026-05-01T12:25:31.886Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -174,14 +222,14 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "django-treebeard"
|
name = "django-treebeard"
|
||||||
version = "4.7.1"
|
version = "5.1.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "django" },
|
{ name = "django" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/bb/24/eaccbce17355380cb3a8fe6ad92a85b76453dc1f0ecd04f48bfe8929065b/django-treebeard-4.7.1.tar.gz", hash = "sha256:846e462904b437155f76e04907ba4e48480716855f88b898df4122bdcfbd6e98", size = 294139, upload-time = "2024-01-31T16:35:19.751Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/f7/16/aa732ea1033586e4f946d8e47be9116ffb22b050485b179a7cafb82dfc34/django_treebeard-5.1.0.tar.gz", hash = "sha256:8ac2ba41307469a679c98188124933bc3baade36b02480343d1a301a1fca0700", size = 302339, upload-time = "2026-05-12T02:06:37.002Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/87/79/259966820614746cc4d81762edf97a53bf1e3b8e74797c010d310c6f4a8f/django_treebeard-4.7.1-py3-none-any.whl", hash = "sha256:995c7120153ab999898fe3043bbdcd8a0fc77cc106eb94de7350e9d02c885135", size = 93210, upload-time = "2024-01-31T16:35:17.843Z" },
|
{ url = "https://files.pythonhosted.org/packages/42/d3/311b9c43950238744f946540c48ea7068bf9e55bea976908a7be13b73082/django_treebeard-5.1.0-py3-none-any.whl", hash = "sha256:d0f68fcf1b49158e38d2322aa62a37bc0b89452d66a16f88e58dbe076d043c28", size = 78591, upload-time = "2026-05-12T02:06:35.62Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -207,27 +255,39 @@ dependencies = [
|
|||||||
{ name = "psycopg2-binary" },
|
{ name = "psycopg2-binary" },
|
||||||
{ name = "wagtail" },
|
{ name = "wagtail" },
|
||||||
{ name = "wagtail-grapple" },
|
{ name = "wagtail-grapple" },
|
||||||
|
{ name = "wagtail-headless-preview" },
|
||||||
{ name = "whitenoise" },
|
{ name = "whitenoise" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dev-dependencies]
|
[package.dev-dependencies]
|
||||||
dev = [
|
dev = [
|
||||||
|
{ name = "pytest" },
|
||||||
|
{ name = "pytest-cov" },
|
||||||
|
{ name = "pytest-django" },
|
||||||
{ name = "ruff" },
|
{ name = "ruff" },
|
||||||
|
{ name = "wagtail-factories" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "django", specifier = ">=6.0.3" },
|
{ name = "django", specifier = ">=6.0.5,<7" },
|
||||||
{ name = "django-extensions", specifier = ">=4.1" },
|
{ name = "django-extensions", specifier = ">=4.1,<5" },
|
||||||
{ name = "gunicorn", specifier = ">=25.1.0" },
|
{ name = "gunicorn", specifier = ">=26.0.0,<27" },
|
||||||
{ name = "psycopg2-binary", specifier = ">=2.9.11,<3" },
|
{ name = "psycopg2-binary", specifier = ">=2.9.12,<3" },
|
||||||
{ name = "wagtail", specifier = ">=7.3.1" },
|
{ name = "wagtail", specifier = ">=7.4.1,<8" },
|
||||||
{ name = "wagtail-grapple", specifier = ">=0.29.0" },
|
{ name = "wagtail-grapple", specifier = ">=0.31.0,<0.32" },
|
||||||
{ name = "whitenoise", specifier = ">=6.12.0" },
|
{ name = "wagtail-headless-preview", specifier = ">=0.8,<0.9" },
|
||||||
|
{ name = "whitenoise", specifier = ">=6.12.0,<7" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata.requires-dev]
|
[package.metadata.requires-dev]
|
||||||
dev = [{ name = "ruff", specifier = ">=0.15.1" }]
|
dev = [
|
||||||
|
{ name = "pytest", specifier = ">=9.0.3,<10" },
|
||||||
|
{ name = "pytest-cov", specifier = ">=7.0.0,<8" },
|
||||||
|
{ name = "pytest-django", specifier = ">=4.12.0,<5" },
|
||||||
|
{ name = "ruff", specifier = ">=0.15.13,<0.16" },
|
||||||
|
{ name = "wagtail-factories", specifier = ">=4.4.0,<5" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "draftjs-exporter"
|
name = "draftjs-exporter"
|
||||||
@@ -247,6 +307,30 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059, upload-time = "2024-10-25T17:25:39.051Z" },
|
{ url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059, upload-time = "2024-10-25T17:25:39.051Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "factory-boy"
|
||||||
|
version = "3.3.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "faker" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/ba/98/75cacae9945f67cfe323829fc2ac451f64517a8a330b572a06a323997065/factory_boy-3.3.3.tar.gz", hash = "sha256:866862d226128dfac7f2b4160287e899daf54f2612778327dd03d0e2cb1e3d03", size = 164146, upload-time = "2025-02-03T09:49:04.433Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/27/8d/2bc5f5546ff2ccb3f7de06742853483ab75bf74f36a92254702f8baecc79/factory_boy-3.3.3-py2.py3-none-any.whl", hash = "sha256:1c39e3289f7e667c4285433f305f8d506efc2fe9c73aaea4151ebd5cdea394fc", size = 37036, upload-time = "2025-02-03T09:49:01.659Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "faker"
|
||||||
|
version = "40.18.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "tzdata", marker = "sys_platform == 'win32'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/18/06/70886e82d8f1d2b73454f3a7c1b7405300128df22e70d85a828951366932/faker-40.18.0.tar.gz", hash = "sha256:2207575c0e8f90e6ccd6dbef764de875c614d16d3db4eee9712d9a00087f2e70", size = 1968243, upload-time = "2026-05-14T16:43:04.834Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/84/0b/5c0b2d3a4b7a715f1835dd3f963bfbe841a02ae5cad1df8ee0325dfad235/faker-40.18.0-py3-none-any.whl", hash = "sha256:61a6b94b74605ddb090a065deb197a1c585ae7a874c094cf6693671d271e6083", size = 2006355, upload-time = "2026-05-14T16:43:02.489Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "filetype"
|
name = "filetype"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
@@ -311,14 +395,14 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gunicorn"
|
name = "gunicorn"
|
||||||
version = "25.1.0"
|
version = "26.0.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "packaging" },
|
{ name = "packaging" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/66/13/ef67f59f6a7896fdc2c1d62b5665c5219d6b0a9a1784938eb9a28e55e128/gunicorn-25.1.0.tar.gz", hash = "sha256:1426611d959fa77e7de89f8c0f32eed6aa03ee735f98c01efba3e281b1c47616", size = 594377, upload-time = "2026-02-13T11:09:58.989Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/6d/b7/a4a3f632f823e432ce6bc65f62961b7980c898c77f075a2f7118cb3846fe/gunicorn-26.0.0.tar.gz", hash = "sha256:ca9346f85e3a4aeeb64d491045c16b9a35647abd37ea15efe53080eb8b090baf", size = 727286, upload-time = "2026-05-05T06:38:25.529Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/da/73/4ad5b1f6a2e21cf1e85afdaad2b7b1a933985e2f5d679147a1953aaa192c/gunicorn-25.1.0-py3-none-any.whl", hash = "sha256:d0b1236ccf27f72cfe14bce7caadf467186f19e865094ca84221424e839b8b8b", size = 197067, upload-time = "2026-02-13T11:09:57.146Z" },
|
{ url = "https://files.pythonhosted.org/packages/e6/40/9c2384fc2be4ad25dd4a49decd5ad9ea5a3639814c11bd40ab77cb9f0a14/gunicorn-26.0.0-py3-none-any.whl", hash = "sha256:40233d26a5f0d1872916188c276e21641155111c2853f0c2cd55260aec0d24fc", size = 212009, upload-time = "2026-05-05T06:38:23.007Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -330,6 +414,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
|
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iniconfig"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "laces"
|
name = "laces"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
@@ -344,15 +437,15 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "modelsearch"
|
name = "modelsearch"
|
||||||
version = "1.1.1"
|
version = "1.3.1"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "django" },
|
{ name = "django" },
|
||||||
{ name = "django-tasks" },
|
{ name = "django-tasks" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/3f/49/751b8872bb9c1ec667d0d312ab90022f0426326109163ade4719466c2e4d/modelsearch-1.1.1.tar.gz", hash = "sha256:25f329c4d93572729c931f65c46cedb5cfc32d368690ebdabc223aa6205251d6", size = 87514, upload-time = "2025-11-10T14:29:57.223Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/dd/25/81121175512af4a4b152f6db3ea749535659f5890f0987cd481646527dff/modelsearch-1.3.1.tar.gz", hash = "sha256:f3b2e304dbef9f7c838423f591cfe0c60f4cef584bae8793d2aa8ac35a3c46e0", size = 103935, upload-time = "2026-04-27T23:37:08.841Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/88/4b/e3eb1f4e4f7ca4bfa9b71cea497352c1ce13261254d59cb33a36bcbff335/modelsearch-1.1.1-py3-none-any.whl", hash = "sha256:d2580790af76c3a6404f651c9d8ca8695b284551583bb8ca6ddeb17eca0cfb52", size = 106987, upload-time = "2025-11-10T14:29:55.946Z" },
|
{ url = "https://files.pythonhosted.org/packages/0f/f1/63cf88da72bc557594a543dd19a2c7e219acccc70cb2b1e2639204580fe6/modelsearch-1.3.1-py3-none-any.whl", hash = "sha256:a92847f01788d0d615e8715fabb8e823029288840297fb9e7235ee03ad49b6a8", size = 127463, upload-time = "2026-04-27T23:37:07.651Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -424,6 +517,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/6b/3c/63cbbe3a6a54e3ec925d2433bb431a4bf61695f34b5f1e689db642fb20c1/pillow_heif-1.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:514c856230995dc2f918fb12d83906885d42829e911868e23035e2d916c4c7c5", size = 5573890, upload-time = "2025-08-02T09:58:05.049Z" },
|
{ url = "https://files.pythonhosted.org/packages/6b/3c/63cbbe3a6a54e3ec925d2433bb431a4bf61695f34b5f1e689db642fb20c1/pillow_heif-1.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:514c856230995dc2f918fb12d83906885d42829e911868e23035e2d916c4c7c5", size = 5573890, upload-time = "2025-08-02T09:58:05.049Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pluggy"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "promise"
|
name = "promise"
|
||||||
version = "2.3"
|
version = "2.3"
|
||||||
@@ -435,21 +537,72 @@ sdist = { url = "https://files.pythonhosted.org/packages/cf/9c/fb5d48abfe5d791cd
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "psycopg2-binary"
|
name = "psycopg2-binary"
|
||||||
version = "2.9.11"
|
version = "2.9.12"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/ac/6c/8767aaa597ba424643dc87348c6f1754dd9f48e80fdc1b9f7ca5c3a7c213/psycopg2-binary-2.9.11.tar.gz", hash = "sha256:b6aed9e096bf63f9e75edf2581aa9a7e7186d97ab5c177aa6c87797cd591236c", size = 379620, upload-time = "2025-10-10T11:14:48.041Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/2a/60/a3624f79acea344c16fbef3a94d28b89a8042ddfb8f3e4ca83f538671409/psycopg2_binary-2.9.12.tar.gz", hash = "sha256:5ac9444edc768c02a6b6a591f070b8aae28ff3a99be57560ac996001580f294c", size = 379686, upload-time = "2026-04-21T09:40:34.304Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/64/12/93ef0098590cf51d9732b4f139533732565704f45bdc1ffa741b7c95fb54/psycopg2_binary-2.9.11-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:92e3b669236327083a2e33ccfa0d320dd01b9803b3e14dd986a4fc54aa00f4e1", size = 3756567, upload-time = "2025-10-10T11:13:11.885Z" },
|
{ url = "https://files.pythonhosted.org/packages/13/1b/708c0dca874acfad6d65314271859899a79007686f3a1f74e82a2ed4b645/psycopg2_binary-2.9.12-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6f3b3de8a74ef8db215f22edffb19e32dc6fa41340456de7ec99efdc8a7b3ec2", size = 3712428, upload-time = "2026-04-20T23:35:23.453Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/7c/a9/9d55c614a891288f15ca4b5209b09f0f01e3124056924e17b81b9fa054cc/psycopg2_binary-2.9.11-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e0deeb03da539fa3577fcb0b3f2554a97f7e5477c246098dbb18091a4a01c16f", size = 3864755, upload-time = "2025-10-10T11:13:17.727Z" },
|
{ url = "https://files.pythonhosted.org/packages/d6/39/ddbea9d4b4de6aca9431b6ed253f530f8a02d3b8f9bcfd0dbfe2b3de6fe4/psycopg2_binary-2.9.12-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1006fb62f0f0bc5ce256a832356c6262e91be43f5e4eb15b5eaf38079464caf2", size = 3823184, upload-time = "2026-04-20T23:35:25.92Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/13/1e/98874ce72fd29cbde93209977b196a2edae03f8490d1bd8158e7f1daf3a0/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9b52a3f9bb540a3e4ec0f6ba6d31339727b2950c9772850d6545b7eae0b9d7c5", size = 4411646, upload-time = "2025-10-10T11:13:24.432Z" },
|
{ url = "https://files.pythonhosted.org/packages/bf/a0/bc2fef74b106fa345567122a0659e6d94512ed7dc0131ec44c9e5aba3725/psycopg2_binary-2.9.12-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:840066105706cd2eb29b9a1c2329620056582a4bf3e8169dec5c447042d0869f", size = 4579157, upload-time = "2026-04-20T23:35:28.542Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/5a/bd/a335ce6645334fb8d758cc358810defca14a1d19ffbc8a10bd38a2328565/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:db4fd476874ccfdbb630a54426964959e58da4c61c9feba73e6094d51303d7d8", size = 4468701, upload-time = "2025-10-10T11:13:29.266Z" },
|
{ url = "https://files.pythonhosted.org/packages/57/d7/d4e3b2005d3de607ca4fbb0e8742e248056e52184a6b94ebda3c1c2c329b/psycopg2_binary-2.9.12-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:863f5d12241ebe1c76a72a04c2113b6dc905f90b9cef0e9be0efd994affd9354", size = 4274970, upload-time = "2026-04-20T23:35:30.418Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/44/d6/c8b4f53f34e295e45709b7568bf9b9407a612ea30387d35eb9fa84f269b4/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:47f212c1d3be608a12937cc131bd85502954398aaa1320cb4c14421a0ffccf4c", size = 4166293, upload-time = "2025-10-10T11:13:33.336Z" },
|
{ url = "https://files.pythonhosted.org/packages/2e/42/c9853f8db3967fe08bcde11f53d53b85d351750cae726ce001cb68afa9c1/psycopg2_binary-2.9.12-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a99eaab34a9010f1a086b126de467466620a750634d114d20455f3a824aae033", size = 5895175, upload-time = "2026-04-20T23:35:33.584Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/4b/e0/f8cc36eadd1b716ab36bb290618a3292e009867e5c97ce4aba908cb99644/psycopg2_binary-2.9.11-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e35b7abae2b0adab776add56111df1735ccc71406e56203515e228a8dc07089f", size = 3983184, upload-time = "2025-10-30T02:55:32.483Z" },
|
{ url = "https://files.pythonhosted.org/packages/eb/fd/b82b5601a97630308bef079f545ffec481bbbc795c2ba5ec416a01d03f60/psycopg2_binary-2.9.12-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ffdd7dc5463ccd61845ac37b7012d0f35a1548df9febe14f8dd549be4a0bc81e", size = 4110658, upload-time = "2026-04-20T23:35:35.638Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/53/3e/2a8fe18a4e61cfb3417da67b6318e12691772c0696d79434184a511906dc/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fcf21be3ce5f5659daefd2b3b3b6e4727b028221ddc94e6c1523425579664747", size = 3652650, upload-time = "2025-10-10T11:13:38.181Z" },
|
{ url = "https://files.pythonhosted.org/packages/62/8c/32ca69b0389ef25dd22937bf9e8fbe2ce27aea20b05ded48c4ce4cb42475/psycopg2_binary-2.9.12-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:54a0dfecab1b48731f934e06139dfe11e24219fb6d0ceb32177cf0375f14c7b5", size = 3656251, upload-time = "2026-04-20T23:35:37.854Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/76/36/03801461b31b29fe58d228c24388f999fe814dfc302856e0d17f97d7c54d/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:9bd81e64e8de111237737b29d68039b9c813bdf520156af36d26819c9a979e5f", size = 3298663, upload-time = "2025-10-10T11:13:44.878Z" },
|
{ url = "https://files.pythonhosted.org/packages/c4/29/96992a2b59e3b9d730fcf9612d0a387305025dc867a9fc490a9e496e074e/psycopg2_binary-2.9.12-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:96937c9c5d891f772430f418a7a8b4691a90c3e6b93cf72b5bd7cad8cbca32a5", size = 3301810, upload-time = "2026-04-20T23:35:39.927Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/97/77/21b0ea2e1a73aa5fa9222b2a6b8ba325c43c3a8d54272839c991f2345656/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:32770a4d666fbdafab017086655bcddab791d7cb260a16679cc5a7338b64343b", size = 3044737, upload-time = "2025-10-30T02:55:35.69Z" },
|
{ url = "https://files.pythonhosted.org/packages/56/ad/44b06659949b243ae10112cd3b20a197f9bf3e81d5651379b9eb889bfaad/psycopg2_binary-2.9.12-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:77b348775efd4cdab410ec6609d81ccecd1139c90265fa583a7255c8064bc03d", size = 3048977, upload-time = "2026-04-20T23:35:41.806Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/67/69/f36abe5f118c1dca6d3726ceae164b9356985805480731ac6712a63f24f0/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c3cb3a676873d7506825221045bd70e0427c905b9c8ee8d6acd70cfcbd6e576d", size = 3347643, upload-time = "2025-10-10T11:13:53.499Z" },
|
{ url = "https://files.pythonhosted.org/packages/1d/f2/10a1bcebadb6aa55e280e1f58975c36a7b560ea525184c7aa4064c466633/psycopg2_binary-2.9.12-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:527e6342b3e44c2f0544f6b8e927d60de7f163f5723b8f1dfa7d2a84298738cd", size = 3351466, upload-time = "2026-04-20T23:35:43.993Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/e1/36/9c0c326fe3a4227953dfb29f5d0c8ae3b8eb8c1cd2967aa569f50cb3c61f/psycopg2_binary-2.9.11-cp314-cp314-win_amd64.whl", hash = "sha256:4012c9c954dfaccd28f94e84ab9f94e12df76b4afb22331b1f0d3154893a6316", size = 2803913, upload-time = "2025-10-10T11:13:57.058Z" },
|
{ url = "https://files.pythonhosted.org/packages/20/be/b732c8418ffa5bcfda002890f5dc4c869fc17db66ff11f53b17cfe44afc0/psycopg2_binary-2.9.12-cp314-cp314-win_amd64.whl", hash = "sha256:f12ae41fcafadb39b2785e64a40f9db05d6de2ac114077457e0e7c597f3af980", size = 2848762, upload-time = "2026-04-20T23:35:46.421Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pygments"
|
||||||
|
version = "2.20.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest"
|
||||||
|
version = "9.0.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||||
|
{ name = "iniconfig" },
|
||||||
|
{ name = "packaging" },
|
||||||
|
{ name = "pluggy" },
|
||||||
|
{ name = "pygments" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest-cov"
|
||||||
|
version = "7.1.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "coverage" },
|
||||||
|
{ name = "pluggy" },
|
||||||
|
{ name = "pytest" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest-django"
|
||||||
|
version = "4.12.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "pytest" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/13/2b/db9a193df89e5660137f5428063bcc2ced7ad790003b26974adf5c5ceb3b/pytest_django-4.12.0.tar.gz", hash = "sha256:df94ec819a83c8979c8f6de13d9cdfbe76e8c21d39473cfe2b40c9fc9be3c758", size = 91156, upload-time = "2026-02-14T18:40:49.235Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/83/a5/41d091f697c09609e7ef1d5d61925494e0454ebf51de7de05f0f0a728f1d/pytest_django-4.12.0-py3-none-any.whl", hash = "sha256:3ff300c49f8350ba2953b90297d23bf5f589db69545f56f1ec5f8cff5da83e85", size = 26123, upload-time = "2026-02-14T18:40:47.381Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -481,27 +634,27 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruff"
|
name = "ruff"
|
||||||
version = "0.15.1"
|
version = "0.15.13"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/04/dc/4e6ac71b511b141cf626357a3946679abeba4cf67bc7cc5a17920f31e10d/ruff-0.15.1.tar.gz", hash = "sha256:c590fe13fb57c97141ae975c03a1aedb3d3156030cabd740d6ff0b0d601e203f", size = 4540855, upload-time = "2026-02-12T23:09:09.998Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/24/21/a7d5c126d5b557715ef81098f3db2fe20f622a039ff2e626af28d674ab80/ruff-0.15.13.tar.gz", hash = "sha256:f9d89f17f7ba7fb2ed42921f0df75da797a9a5d71bc39049e2c687cf2baf44b7", size = 4678180, upload-time = "2026-05-14T13:44:37.869Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/23/bf/e6e4324238c17f9d9120a9d60aa99a7daaa21204c07fcd84e2ef03bb5fd1/ruff-0.15.1-py3-none-linux_armv6l.whl", hash = "sha256:b101ed7cf4615bda6ffe65bdb59f964e9f4a0d3f85cbf0e54f0ab76d7b90228a", size = 10367819, upload-time = "2026-02-12T23:09:03.598Z" },
|
{ url = "https://files.pythonhosted.org/packages/c6/61/11d458dc6ac22504fd8e237b29dfd40504c7fbbcc8930402cfe51a8e63ed/ruff-0.15.13-py3-none-linux_armv6l.whl", hash = "sha256:444b580fc72fd6887e650acd3e575e18cdc79dbcf42fb4030b491057921f61f8", size = 10738279, upload-time = "2026-05-14T13:44:18.7Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/b3/ea/c8f89d32e7912269d38c58f3649e453ac32c528f93bb7f4219258be2e7ed/ruff-0.15.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:939c995e9277e63ea632cc8d3fae17aa758526f49a9a850d2e7e758bfef46602", size = 10798618, upload-time = "2026-02-12T23:09:22.928Z" },
|
{ url = "https://files.pythonhosted.org/packages/86/ca/caa871ee7be718c45256fada4e16a218ee3e33f0c4a46b729a60a24912e6/ruff-0.15.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6590d009e7cb7ebf36f83dbdd44a3fa48a0994ff6f1cdc1b08006abe58f98dc7", size = 11124798, upload-time = "2026-05-14T13:44:06.427Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/5e/0f/1d0d88bc862624247d82c20c10d4c0f6bb2f346559d8af281674cf327f15/ruff-0.15.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1d83466455fdefe60b8d9c8df81d3c1bbb2115cede53549d3b522ce2bc703899", size = 10148518, upload-time = "2026-02-12T23:08:58.339Z" },
|
{ url = "https://files.pythonhosted.org/packages/d3/19/43f5f2e568dddde567fc41f8471f9432c09563e19d3e617a48cfa52f8f0a/ruff-0.15.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1c26d2f66163deeb6e08d8b39fbbe983ce3c71cea06a6d7591cfd1421793c629", size = 10460761, upload-time = "2026-05-14T13:44:04.375Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/f5/c8/291c49cefaa4a9248e986256df2ade7add79388fe179e0691be06fae6f37/ruff-0.15.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9457e3c3291024866222b96108ab2d8265b477e5b1534c7ddb1810904858d16", size = 10518811, upload-time = "2026-02-12T23:09:31.865Z" },
|
{ url = "https://files.pythonhosted.org/packages/99/df/cf938cd6de3003178f03ad7c1ea2a6c099468c03a35037985070b37e76be/ruff-0.15.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbd6f94b434f896308e4d57fb7bfde0d02b99f7a64b3bdab0fdfa6a864203a5", size = 10804451, upload-time = "2026-05-14T13:44:25.221Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/c3/1a/f5707440e5ae43ffa5365cac8bbb91e9665f4a883f560893829cf16a606b/ruff-0.15.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:92c92b003e9d4f7fbd33b1867bb15a1b785b1735069108dfc23821ba045b29bc", size = 10196169, upload-time = "2026-02-12T23:09:17.306Z" },
|
{ url = "https://files.pythonhosted.org/packages/c7/7d/5d0973129b154ded2225729169d7068f26b467760b146493fde138415f23/ruff-0.15.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf3259f3be4d181bda591da5db2571aed6853c6a048157756448020bc6c5cd22", size = 10534285, upload-time = "2026-05-14T13:44:08.888Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/2a/ff/26ddc8c4da04c8fd3ee65a89c9fb99eaa5c30394269d424461467be2271f/ruff-0.15.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fe5c41ab43e3a06778844c586251eb5a510f67125427625f9eb2b9526535779", size = 10990491, upload-time = "2026-02-12T23:09:25.503Z" },
|
{ url = "https://files.pythonhosted.org/packages/1f/e3/6b999bbc66cd51e5f073842bc2a3995e99c5e0e72e16b15e7261f7abf57a/ruff-0.15.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae9c17e5eb4430c154e76abc25d79a318190f5a997f38fb6b114416c5319ffc9", size = 11312063, upload-time = "2026-05-14T13:44:11.274Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/fc/00/50920cb385b89413f7cdb4bb9bc8fc59c1b0f30028d8bccc294189a54955/ruff-0.15.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66a6dd6df4d80dc382c6484f8ce1bcceb55c32e9f27a8b94c32f6c7331bf14fb", size = 11843280, upload-time = "2026-02-12T23:09:19.88Z" },
|
{ url = "https://files.pythonhosted.org/packages/af/5a/642639e9f5db04f1e97fbd6e091c6fd20725bdf072fb114d00eefb9e6eb8/ruff-0.15.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e2e39bff6c341f4b577a21b801326fab0b11847f48fcaa83f00a113c9b3cb55", size = 12183079, upload-time = "2026-05-14T13:44:01.634Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/5d/6d/2f5cad8380caf5632a15460c323ae326f1e1a2b5b90a6ee7519017a017ca/ruff-0.15.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a4a42cbb8af0bda9bcd7606b064d7c0bc311a88d141d02f78920be6acb5aa83", size = 11274336, upload-time = "2026-02-12T23:09:14.907Z" },
|
{ url = "https://files.pythonhosted.org/packages/19/4c/7585735f6b53b0f12de13618b2f7d250a844f018822efc899df2e7b8295f/ruff-0.15.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e8d9a8e08013542e94d3220bc5b62cc3e5ef87c5f74bff367d3fac14fab013e6", size = 11440833, upload-time = "2026-05-14T13:43:59.043Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a3/1d/5f56cae1d6c40b8a318513599b35ea4b075d7dc1cd1d04449578c29d1d75/ruff-0.15.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ab064052c31dddada35079901592dfba2e05f5b1e43af3954aafcbc1096a5b2", size = 11137288, upload-time = "2026-02-12T23:09:07.475Z" },
|
{ url = "https://files.pythonhosted.org/packages/e8/31/bf1a0803d077e679cfeee5f2f67290a0fa79c7385b5d9a8c17b9db2c48f0/ruff-0.15.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc411dfebe5eebe55ce041c6ae080eb7668955e866daa2fbb16692a784f1c4ca", size = 11434486, upload-time = "2026-05-14T13:44:27.761Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/cd/20/6f8d7d8f768c93b0382b33b9306b3b999918816da46537d5a61635514635/ruff-0.15.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:5631c940fe9fe91f817a4c2ea4e81f47bee3ca4aa646134a24374f3c19ad9454", size = 11070681, upload-time = "2026-02-12T23:08:55.43Z" },
|
{ url = "https://files.pythonhosted.org/packages/e1/4e/62c9b999875d4f14db80f277c030578f5e249c9852d65b7ac7ad0b43c041/ruff-0.15.13-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:768494eb08b9cee54e2fd27969966f74db5a57f6eaa7a90fcb3306af34dfc4bd", size = 11385189, upload-time = "2026-05-14T13:44:13.704Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/9a/67/d640ac76069f64cdea59dba02af2e00b1fa30e2103c7f8d049c0cff4cafd/ruff-0.15.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:68138a4ba184b4691ccdc39f7795c66b3c68160c586519e7e8444cf5a53e1b4c", size = 10486401, upload-time = "2026-02-12T23:09:27.927Z" },
|
{ url = "https://files.pythonhosted.org/packages/fc/89/7e959047a104df3eb12863447c110140191fc5b6c4f379ea2e803fcdb0e4/ruff-0.15.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:fb75f9a3a7e42ffe117d734494e6c5e5cb3565d66e12612cb63d0e572a41a5b6", size = 10781380, upload-time = "2026-05-14T13:43:56.734Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/65/3d/e1429f64a3ff89297497916b88c32a5cc88eeca7e9c787072d0e7f1d3e1e/ruff-0.15.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:518f9af03bfc33c03bdb4cb63fabc935341bb7f54af500f92ac309ecfbba6330", size = 10197452, upload-time = "2026-02-12T23:09:12.147Z" },
|
{ url = "https://files.pythonhosted.org/packages/ff/52/5fd18f3b88cab63e88aa11516b3b4e1e5f720e5c330f8dbe5c26210f41f8/ruff-0.15.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8cb74dd33bb2f6613faf7fc03b660053b5ac4f80e706d5788c6335e2a8048d51", size = 10540605, upload-time = "2026-05-14T13:44:20.748Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/78/83/e2c3bade17dad63bf1e1c2ffaf11490603b760be149e1419b07049b36ef2/ruff-0.15.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:da79f4d6a826caaea95de0237a67e33b81e6ec2e25fc7e1993a4015dffca7c61", size = 10693900, upload-time = "2026-02-12T23:09:34.418Z" },
|
{ url = "https://files.pythonhosted.org/packages/e8/e0/9e35f338990d3e41a82875ff7053ffe97541dae81c9d02143177f381d572/ruff-0.15.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:7ef823f817fcd191dc934e984be9cf4094f808effa16f2542ad8e821ba02bbf2", size = 11036554, upload-time = "2026-05-14T13:44:16.256Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a1/27/fdc0e11a813e6338e0706e8b39bb7a1d61ea5b36873b351acee7e524a72a/ruff-0.15.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3dd86dccb83cd7d4dcfac303ffc277e6048600dfc22e38158afa208e8bf94a1f", size = 11227302, upload-time = "2026-02-12T23:09:36.536Z" },
|
{ url = "https://files.pythonhosted.org/packages/c2/13/070fb048c24080fba188f66371e2a92785be257ad02242066dc7255ac6e9/ruff-0.15.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f345a13937bd7f09f6f5d19fa0721b0c103e00e7f62bc67089a8e5e037719e0b", size = 11528133, upload-time = "2026-05-14T13:44:22.808Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/f6/58/ac864a75067dcbd3b95be5ab4eb2b601d7fbc3d3d736a27e391a4f92a5c1/ruff-0.15.1-py3-none-win32.whl", hash = "sha256:660975d9cb49b5d5278b12b03bb9951d554543a90b74ed5d366b20e2c57c2098", size = 10462555, upload-time = "2026-02-12T23:09:29.899Z" },
|
{ url = "https://files.pythonhosted.org/packages/6b/8c/b1e1666aef7fc6555094d73ae6cd981701781ae85b97ceefc0eebd0b4668/ruff-0.15.13-py3-none-win32.whl", hash = "sha256:4044f94208b3b05ba0fc4a4abd0558cf4d6459bd18325eead7fd8cc66f909b41", size = 10721455, upload-time = "2026-05-14T13:44:35.697Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/e0/5e/d4ccc8a27ecdb78116feac4935dfc39d1304536f4296168f91ed3ec00cd2/ruff-0.15.1-py3-none-win_amd64.whl", hash = "sha256:c820fef9dd5d4172a6570e5721704a96c6679b80cf7be41659ed439653f62336", size = 11599956, upload-time = "2026-02-12T23:09:01.157Z" },
|
{ url = "https://files.pythonhosted.org/packages/ab/a6/870a3e8a50590bb92be184ad928c2922f088b00d9dc5c5ec7b924ee08c22/ruff-0.15.13-py3-none-win_amd64.whl", hash = "sha256:7064884d442b7d477b4e7473d12da7f08851d2b1982763c5d3f388a19468a1a4", size = 11900409, upload-time = "2026-05-14T13:44:30.389Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/2a/07/5bda6a85b220c64c65686bc85bd0bbb23b29c62b3a9f9433fa55f17cda93/ruff-0.15.1-py3-none-win_arm64.whl", hash = "sha256:5ff7d5f0f88567850f45081fac8f4ec212be8d0b963e385c3f7d0d2eb4899416", size = 10874604, upload-time = "2026-02-12T23:09:05.515Z" },
|
{ url = "https://files.pythonhosted.org/packages/9b/36/9c015cd052fca743dae8cb2aeb16b551444787467db42ceab0fc968865af/ruff-0.15.13-py3-none-win_arm64.whl", hash = "sha256:2471da9bd1068c8c064b5fd9c0c4b6dddffd6369cb1cd68b29993b1709ff1b21", size = 11179336, upload-time = "2026-05-14T13:44:33.026Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -578,7 +731,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wagtail"
|
name = "wagtail"
|
||||||
version = "7.3.1"
|
version = "7.4.1"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "anyascii" },
|
{ name = "anyascii" },
|
||||||
@@ -600,23 +753,36 @@ dependencies = [
|
|||||||
{ name = "telepath" },
|
{ name = "telepath" },
|
||||||
{ name = "willow", extra = ["heif"] },
|
{ name = "willow", extra = ["heif"] },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/4e/f9/e28a1b87ea61c68b74990c9f5c8cb11da9a689e07c8b769acc89121f8523/wagtail-7.3.1.tar.gz", hash = "sha256:2ce131d9a4e7d55fdb5b592d320a758a189174b2cc3966b70a34a1b3dc56f449", size = 6855119, upload-time = "2026-03-03T15:54:48.523Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/81/da/c060bd9ec00335336e04a610cd1d6762e459ade9c4983f7ecddae15af36d/wagtail-7.4.1.tar.gz", hash = "sha256:c7232cbe16afa5842c236e069b2baead3b3e87e9dc90a7493330b54b69e18b83", size = 6944979, upload-time = "2026-05-19T17:26:18.762Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/bb/0e/5efc903966b966df2261a66cce8cb88909e4ade86f1173a156aadbbd1a06/wagtail-7.3.1-py3-none-any.whl", hash = "sha256:eab131e15ab9edc7ed24143d44271e92af79239e105bc3e173d26c95d2b489b3", size = 9479191, upload-time = "2026-03-03T15:54:42.644Z" },
|
{ url = "https://files.pythonhosted.org/packages/53/c8/162fff3c5ef9e158127c9f4aa1f0d38edd8d1fa242629ae1d8af3b6306c7/wagtail-7.4.1-py3-none-any.whl", hash = "sha256:a42dc18b0fb818649c0d704ea50a2dea06d1b75614abdf837e90766263645c76", size = 9597601, upload-time = "2026-05-19T17:26:12.53Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wagtail-factories"
|
||||||
|
version = "4.4.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "factory-boy" },
|
||||||
|
{ name = "wagtail" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/42/8c/2ece22c2757bb3f6ef34f36cf185246b136134cf594b7b1dde92cb0a331f/wagtail_factories-4.4.0.tar.gz", hash = "sha256:c77c13d438a2e999a9220ff1829f060116013aa519a81944577353f460ba8cba", size = 9939, upload-time = "2026-02-10T15:14:13.7Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4c/85/bd5aeaa54a10b6defca927f75bc3890acb7c3a9a23df8e10f3aa42e75807/wagtail_factories-4.4.0-py2.py3-none-any.whl", hash = "sha256:be0abb96d36bf0e3c733d7520fc1944441a379ab06d93af447fc4e71b67e2a01", size = 10754, upload-time = "2026-02-10T15:14:12.773Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wagtail-grapple"
|
name = "wagtail-grapple"
|
||||||
version = "0.29.0"
|
version = "0.31.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "graphene-django" },
|
{ name = "graphene-django" },
|
||||||
{ name = "wagtail" },
|
{ name = "wagtail" },
|
||||||
{ name = "wagtail-headless-preview" },
|
{ name = "wagtail-headless-preview" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/c0/66/abe1b5ad7c335ff3969190c38ac259285b9f107d8273a18cc8c0ba0d36c5/wagtail_grapple-0.29.0.tar.gz", hash = "sha256:56b023dcfdce72532fba00610507b2705bf3a0be068946275c7658314de5431a", size = 39112, upload-time = "2025-07-02T10:25:19.811Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/9c/29/8cab682abde7f3a8d78a29aac6e441b7a9a20d9baf850c7fc438ad086c48/wagtail_grapple-0.31.0.tar.gz", hash = "sha256:5a70e88dca5188181e42306b207b1e38cf5df4452aa62e764b9e997e2ea19797", size = 39240, upload-time = "2026-04-21T09:24:28.195Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/48/ae/a7a26e331f9207ed864ebea292aac6af1e19a221391d2f833cd4fbe70f68/wagtail_grapple-0.29.0-py3-none-any.whl", hash = "sha256:d9c8c76bd9ba8c52ba1796b86043360d5ab083b24db6caaf2b1e6e41a7a2503e", size = 49124, upload-time = "2025-07-02T10:25:18.345Z" },
|
{ url = "https://files.pythonhosted.org/packages/08/05/d80a7675e359fa62d45b92f14e42850fcb5f42c4fec3950951585b0a0975/wagtail_grapple-0.31.0-py3-none-any.whl", hash = "sha256:fed9205df68861d0ec6c5be67ac5aeb00775ebabde6baa38d582e723076befc0", size = 49251, upload-time = "2026-04-21T09:24:26.18Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -1,731 +0,0 @@
|
|||||||
# Generated by Django 6.0.3 on 2026-04-15 20:21
|
|
||||||
|
|
||||||
import wagtail.fields
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("venues", "0024_venuepage_show_in_overview_and_more"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="venueindex",
|
|
||||||
name="body",
|
|
||||||
field=wagtail.fields.StreamField(
|
|
||||||
[
|
|
||||||
("paragraph", 0),
|
|
||||||
("image", 4),
|
|
||||||
("image_slider", 8),
|
|
||||||
("horizontal_rule", 10),
|
|
||||||
("featured", 18),
|
|
||||||
("page_section_navigation", 19),
|
|
||||||
("accordion", 23),
|
|
||||||
("fact_box", 26),
|
|
||||||
("photo_sphere", 29),
|
|
||||||
("embed", 30),
|
|
||||||
("raw_html", 31),
|
|
||||||
],
|
|
||||||
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.images.blocks.ImageChooserBlock",
|
|
||||||
(),
|
|
||||||
{"label": "360°-bilde"},
|
|
||||||
),
|
|
||||||
28: (
|
|
||||||
"wagtail.blocks.CharBlock",
|
|
||||||
(),
|
|
||||||
{
|
|
||||||
"help_text": "Beskrivende tittel på bildet (vises også til skjermlesere)",
|
|
||||||
"label": "Tittel",
|
|
||||||
"max_length": 256,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
29: (
|
|
||||||
"wagtail.blocks.StructBlock",
|
|
||||||
[[("image", 27), ("title", 28)]],
|
|
||||||
{},
|
|
||||||
),
|
|
||||||
30: ("wagtail.embeds.blocks.EmbedBlock", (), {}),
|
|
||||||
31: ("wagtail.blocks.RawHTMLBlock", (), {}),
|
|
||||||
},
|
|
||||||
default=[("paragraph", "")],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="venuepage",
|
|
||||||
name="body",
|
|
||||||
field=wagtail.fields.StreamField(
|
|
||||||
[
|
|
||||||
("paragraph", 0),
|
|
||||||
("image", 4),
|
|
||||||
("image_slider", 8),
|
|
||||||
("horizontal_rule", 10),
|
|
||||||
("featured", 18),
|
|
||||||
("page_section_navigation", 19),
|
|
||||||
("accordion", 23),
|
|
||||||
("fact_box", 26),
|
|
||||||
("photo_sphere", 29),
|
|
||||||
("embed", 30),
|
|
||||||
("raw_html", 31),
|
|
||||||
],
|
|
||||||
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.images.blocks.ImageChooserBlock",
|
|
||||||
(),
|
|
||||||
{"label": "360°-bilde"},
|
|
||||||
),
|
|
||||||
28: (
|
|
||||||
"wagtail.blocks.CharBlock",
|
|
||||||
(),
|
|
||||||
{
|
|
||||||
"help_text": "Beskrivende tittel på bildet (vises også til skjermlesere)",
|
|
||||||
"label": "Tittel",
|
|
||||||
"max_length": 256,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
29: (
|
|
||||||
"wagtail.blocks.StructBlock",
|
|
||||||
[[("image", 27), ("title", 28)]],
|
|
||||||
{},
|
|
||||||
),
|
|
||||||
30: ("wagtail.embeds.blocks.EmbedBlock", (), {}),
|
|
||||||
31: ("wagtail.blocks.RawHTMLBlock", (), {}),
|
|
||||||
},
|
|
||||||
default=[("paragraph", "")],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="venuerentalindex",
|
|
||||||
name="body",
|
|
||||||
field=wagtail.fields.StreamField(
|
|
||||||
[
|
|
||||||
("paragraph", 0),
|
|
||||||
("image", 4),
|
|
||||||
("image_slider", 8),
|
|
||||||
("horizontal_rule", 10),
|
|
||||||
("featured", 18),
|
|
||||||
("page_section_navigation", 19),
|
|
||||||
("accordion", 23),
|
|
||||||
("fact_box", 26),
|
|
||||||
("photo_sphere", 29),
|
|
||||||
("embed", 30),
|
|
||||||
("raw_html", 31),
|
|
||||||
],
|
|
||||||
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.images.blocks.ImageChooserBlock",
|
|
||||||
(),
|
|
||||||
{"label": "360°-bilde"},
|
|
||||||
),
|
|
||||||
28: (
|
|
||||||
"wagtail.blocks.CharBlock",
|
|
||||||
(),
|
|
||||||
{
|
|
||||||
"help_text": "Beskrivende tittel på bildet (vises også til skjermlesere)",
|
|
||||||
"label": "Tittel",
|
|
||||||
"max_length": 256,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
29: (
|
|
||||||
"wagtail.blocks.StructBlock",
|
|
||||||
[[("image", 27), ("title", 28)]],
|
|
||||||
{},
|
|
||||||
),
|
|
||||||
30: ("wagtail.embeds.blocks.EmbedBlock", (), {}),
|
|
||||||
31: ("wagtail.blocks.RawHTMLBlock", (), {}),
|
|
||||||
},
|
|
||||||
default=[("paragraph", "")],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -11,6 +11,7 @@ from wagtail.admin.panels import FieldPanel, FieldRowPanel, MultiFieldPanel
|
|||||||
from wagtail.fields import RichTextField, StreamField
|
from wagtail.fields import RichTextField, StreamField
|
||||||
from wagtail.models import Page
|
from wagtail.models import Page
|
||||||
from wagtail.search import index
|
from wagtail.search import index
|
||||||
|
from wagtail_headless_preview.models import HeadlessMixin
|
||||||
|
|
||||||
from dnscms.blocks import ImageSliderBlock
|
from dnscms.blocks import ImageSliderBlock
|
||||||
from dnscms.fields import CommonStreamField
|
from dnscms.fields import CommonStreamField
|
||||||
@@ -18,7 +19,7 @@ from dnscms.wordpress.models import WPImportedPageMixin
|
|||||||
|
|
||||||
|
|
||||||
@register_singular_query_field("venueIndex")
|
@register_singular_query_field("venueIndex")
|
||||||
class VenueIndex(Page):
|
class VenueIndex(HeadlessMixin, Page):
|
||||||
# there can only be one venue index page
|
# there can only be one venue index page
|
||||||
max_count = 1
|
max_count = 1
|
||||||
subpage_types = ["venues.VenuePage"]
|
subpage_types = ["venues.VenuePage"]
|
||||||
@@ -35,7 +36,7 @@ class VenueIndex(Page):
|
|||||||
|
|
||||||
|
|
||||||
@register_singular_query_field("venueRentalIndex")
|
@register_singular_query_field("venueRentalIndex")
|
||||||
class VenueRentalIndex(Page):
|
class VenueRentalIndex(HeadlessMixin, Page):
|
||||||
# there can only be one venue index page
|
# there can only be one venue index page
|
||||||
max_count = 1
|
max_count = 1
|
||||||
subpage_types = []
|
subpage_types = []
|
||||||
@@ -51,7 +52,7 @@ class VenueRentalIndex(Page):
|
|||||||
graphql_fields = [GraphQLRichText("lead"), GraphQLStreamfield("body")]
|
graphql_fields = [GraphQLRichText("lead"), GraphQLStreamfield("body")]
|
||||||
|
|
||||||
|
|
||||||
class VenuePage(WPImportedPageMixin, Page):
|
class VenuePage(HeadlessMixin, WPImportedPageMixin, Page):
|
||||||
# no children
|
# no children
|
||||||
subpage_types = []
|
subpage_types = []
|
||||||
parent_page_types = ["venues.VenueIndex"]
|
parent_page_types = ["venues.VenueIndex"]
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
[tools]
|
[tools]
|
||||||
python = "3.14"
|
python = "3.14"
|
||||||
uv = "latest"
|
uv = "latest"
|
||||||
|
node = "24"
|
||||||
|
prek = "latest"
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
exclude = '^web/src/gql/'
|
||||||
|
|
||||||
|
[[repos]]
|
||||||
|
repo = "https://github.com/pre-commit/pre-commit-hooks"
|
||||||
|
rev = "v6.0.0"
|
||||||
|
hooks = [
|
||||||
|
{ id = "end-of-file-fixer" },
|
||||||
|
{ id = "trailing-whitespace" },
|
||||||
|
{ id = "check-yaml" },
|
||||||
|
{ id = "check-toml" },
|
||||||
|
{ id = "check-merge-conflict" },
|
||||||
|
{ id = "check-added-large-files" },
|
||||||
|
{ id = "debug-statements" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[repos]]
|
||||||
|
repo = "https://github.com/astral-sh/ruff-pre-commit"
|
||||||
|
rev = "v0.15.13"
|
||||||
|
|
||||||
|
[[repos.hooks]]
|
||||||
|
id = "ruff-check"
|
||||||
|
args = ["--fix"]
|
||||||
|
files = '^dnscms/.*\.py$'
|
||||||
|
exclude = '/migrations/'
|
||||||
|
|
||||||
|
[[repos.hooks]]
|
||||||
|
id = "ruff-format"
|
||||||
|
files = '^dnscms/.*\.py$'
|
||||||
|
exclude = '/migrations/'
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
GRAPHQL_ENDPOINT=https://cms.neuf.no/api/graphql/
|
WAGTAIL_BASE_URL=https://cms.neuf.no
|
||||||
URL=http://localhost:3000
|
URL=http://localhost:3000
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
Run the development server:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
Update GraphQL definitions from `http://127.0.0.1:8000/api/graphql/`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run codegen
|
|
||||||
```
|
|
||||||
+19
-1
@@ -3,13 +3,31 @@ import { CodegenConfig } from "@graphql-codegen/cli";
|
|||||||
import { loadEnvConfig } from "@next/env";
|
import { loadEnvConfig } from "@next/env";
|
||||||
loadEnvConfig(process.cwd());
|
loadEnvConfig(process.cwd());
|
||||||
|
|
||||||
|
const wagtailBaseUrl = process.env.WAGTAIL_BASE_URL;
|
||||||
|
if (!wagtailBaseUrl) {
|
||||||
|
throw new Error("WAGTAIL_BASE_URL is not set");
|
||||||
|
}
|
||||||
|
const graphqlEndpoint = `${wagtailBaseUrl.replace(/\/$/, "")}/api/graphql/`;
|
||||||
|
|
||||||
const config: CodegenConfig = {
|
const config: CodegenConfig = {
|
||||||
schema: process.env.GRAPHQL_ENDPOINT,
|
schema: graphqlEndpoint,
|
||||||
documents: ["src/**/*.tsx", "src/**/*.ts"],
|
documents: ["src/**/*.tsx", "src/**/*.ts"],
|
||||||
ignoreNoDocuments: true, // for better experience with the watcher
|
ignoreNoDocuments: true, // for better experience with the watcher
|
||||||
generates: {
|
generates: {
|
||||||
"./src/gql/": {
|
"./src/gql/": {
|
||||||
preset: "client",
|
preset: "client",
|
||||||
|
presetConfig: {
|
||||||
|
fragmentMasking: { unmaskFunctionName: "unmaskFragment" },
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
scalars: {
|
||||||
|
DateTime: "string",
|
||||||
|
JSONString: "string",
|
||||||
|
PositiveInt: "number",
|
||||||
|
RichText: "string",
|
||||||
|
UUID: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
[tools]
|
|
||||||
node = "24"
|
|
||||||
@@ -16,6 +16,7 @@ const nextConfig = {
|
|||||||
hostname: "**",
|
hostname: "**",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
dangerouslyAllowLocalIP: process.env.NODE_ENV === "development",
|
||||||
},
|
},
|
||||||
turbopack: {
|
turbopack: {
|
||||||
root: __dirname,
|
root: __dirname,
|
||||||
|
|||||||
Generated
+1236
-1906
File diff suppressed because it is too large
Load Diff
+13
-14
@@ -10,34 +10,33 @@
|
|||||||
"codegen": "graphql-codegen"
|
"codegen": "graphql-codegen"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@graphql-codegen/cli": "^6.1.2",
|
"@graphql-codegen/cli": "^7.0.0",
|
||||||
"@graphql-codegen/client-preset": "^5.2.3",
|
"@graphql-codegen/client-preset": "^6.0.0",
|
||||||
"@parcel/watcher": "^2.5.6",
|
"@parcel/watcher": "^2.5.6",
|
||||||
"@sindresorhus/slugify": "^3.0.0",
|
"@sindresorhus/slugify": "^3.0.0",
|
||||||
"@urql/next": "^2.0.0",
|
"@urql/next": "^2.0.0",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"date-fns-tz": "^3.2.0",
|
"date-fns-tz": "^3.2.0",
|
||||||
"graphql": "^16.13.1",
|
"graphql": "^16.14.0",
|
||||||
"next": "^16.1.6",
|
"next": "^16.2.6",
|
||||||
"nuqs": "^2.8.9",
|
"nuqs": "^2.8.9",
|
||||||
"react": "19.2.4",
|
"react": "19.2.6",
|
||||||
"react-dom": "19.2.4",
|
"react-dom": "19.2.6",
|
||||||
"react-intersection-observer": "^10.0.3",
|
"react-intersection-observer": "^10.0.3",
|
||||||
"react-photo-sphere-viewer": "^6.2.3",
|
"sass": "^1.99.0",
|
||||||
"sass": "^1.97.3",
|
|
||||||
"sharp": "^0.34.5",
|
"sharp": "^0.34.5",
|
||||||
"swiper": "^12.1.2",
|
"swiper": "^12.1.4",
|
||||||
"urql": "^5.0.1",
|
"urql": "^5.0.2",
|
||||||
"use-debounce": "^10.1.0"
|
"use-debounce": "^10.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^24",
|
"@types/node": "^24",
|
||||||
"@types/react": "19.2.14",
|
"@types/react": "19.2.14",
|
||||||
"@types/react-dom": "19.2.3",
|
"@types/react-dom": "19.2.3",
|
||||||
"baseline-browser-mapping": "^2.10.0",
|
"baseline-browser-mapping": "^2.10.29",
|
||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "16.1.6",
|
"eslint-config-next": "16.2.6",
|
||||||
"typescript": "^5"
|
"typescript": "^6"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"@types/react": "19.2.14",
|
"@types/react": "19.2.14",
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 32 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 17 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 15 KiB |
@@ -1,41 +1,15 @@
|
|||||||
import { graphql } from "@/gql";
|
import { graphql } from "@/gql";
|
||||||
import { GenericFragment } from "@/gql/graphql";
|
|
||||||
import { getClient } from "@/app/client";
|
import { getClient } from "@/app/client";
|
||||||
import { Metadata, ResolvingMetadata } from "next";
|
import { Metadata, ResolvingMetadata } from "next";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { PageHeader } from "@/components/general/PageHeader";
|
import {
|
||||||
import { PageContent } from "@/components/general/PageContent";
|
GenericPageView,
|
||||||
import { BgPig } from "@/components/general/BgPig";
|
loadGenericPageProps,
|
||||||
|
} from "@/components/general/GenericPageView";
|
||||||
import { getSeoMetadata } from "@/lib/seo";
|
import { getSeoMetadata } from "@/lib/seo";
|
||||||
|
|
||||||
export const dynamicParams = false;
|
export const dynamicParams = false;
|
||||||
|
|
||||||
const GenericFragmentDefinition = graphql(`
|
|
||||||
fragment Generic on GenericPage {
|
|
||||||
__typename
|
|
||||||
id
|
|
||||||
urlPath
|
|
||||||
seoTitle
|
|
||||||
searchDescription
|
|
||||||
title
|
|
||||||
lead
|
|
||||||
pig
|
|
||||||
body {
|
|
||||||
...Blocks
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
const genericPageByUrlPathQuery = graphql(`
|
|
||||||
query genericPageByUrl($urlPath: String!) {
|
|
||||||
page: page(contentType: "generic.GenericPage", urlPath: $urlPath) {
|
|
||||||
... on GenericPage {
|
|
||||||
...Generic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
function getWagtailUrlPath(url: string[]): string {
|
function getWagtailUrlPath(url: string[]): string {
|
||||||
// for the page /foo/bar we need to look for `/home/foo/bar/`
|
// for the page /foo/bar we need to look for `/home/foo/bar/`
|
||||||
return `/home/${url.join("/")}/`;
|
return `/home/${url.join("/")}/`;
|
||||||
@@ -78,46 +52,14 @@ export async function generateMetadata(
|
|||||||
parent: ResolvingMetadata
|
parent: ResolvingMetadata
|
||||||
): Promise<Metadata | null> {
|
): Promise<Metadata | null> {
|
||||||
const { url } = await params;
|
const { url } = await params;
|
||||||
const urlPath = getWagtailUrlPath(url);
|
const props = await loadGenericPageProps({ urlPath: getWagtailUrlPath(url) });
|
||||||
const { data, error } = await getClient().query(genericPageByUrlPathQuery, {
|
if (!props) return null;
|
||||||
urlPath: urlPath,
|
return getSeoMetadata(props.page, parent);
|
||||||
});
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
throw new Error(error.message);
|
|
||||||
}
|
|
||||||
if (!data?.page) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const page = data.page as GenericFragment;
|
|
||||||
const metadata = await getSeoMetadata(page, parent);
|
|
||||||
return metadata;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function Page({ params }: { params: Params }) {
|
export default async function Page({ params }: { params: Params }) {
|
||||||
const { url } = await params;
|
const { url } = await params;
|
||||||
const urlPath = getWagtailUrlPath(url);
|
const props = await loadGenericPageProps({ urlPath: getWagtailUrlPath(url) });
|
||||||
const { data, error } = await getClient().query(genericPageByUrlPathQuery, {
|
if (!props) return notFound();
|
||||||
urlPath: urlPath,
|
return <GenericPageView {...props} />;
|
||||||
});
|
|
||||||
if (error) {
|
|
||||||
throw new Error(error.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!data?.page) {
|
|
||||||
return notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
const page = data?.page as GenericFragment;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<main className="site-main" id="main">
|
|
||||||
<PageHeader heading={page.title} lead={page.lead} />
|
|
||||||
<PageContent blocks={page.body} />
|
|
||||||
</main>
|
|
||||||
{page.pig && <BgPig type={page.pig} color="white" />}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,13 @@
|
|||||||
import { Metadata, ResolvingMetadata } from "next";
|
import { Metadata, ResolvingMetadata } from "next";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { getClient } from "@/app/client";
|
import { getClient } from "@/app/client";
|
||||||
import { Breadcrumb } from "@/components/general/Breadcrumb";
|
import {
|
||||||
import { ImageFigure } from "@/components/general/Image";
|
NewsPageView,
|
||||||
import { PageContent } from "@/components/general/PageContent";
|
loadNewsPageProps,
|
||||||
|
} from "@/components/news/NewsPageView";
|
||||||
import { graphql } from "@/gql";
|
import { graphql } from "@/gql";
|
||||||
import { NewsFragment } from "@/gql/graphql";
|
|
||||||
import { formatDate } from "@/lib/date";
|
|
||||||
import { getSeoMetadata } from "@/lib/seo";
|
import { getSeoMetadata } from "@/lib/seo";
|
||||||
|
|
||||||
const newsBySlugQuery = graphql(`
|
|
||||||
query newsBySlug($slug: String!) {
|
|
||||||
news: page(contentType: "news.NewsPage", slug: $slug) {
|
|
||||||
... on NewsPage {
|
|
||||||
...News
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
export async function generateStaticParams() {
|
export async function generateStaticParams() {
|
||||||
const allNewsSlugsQuery = graphql(`
|
const allNewsSlugsQuery = graphql(`
|
||||||
query allNewsSlugs {
|
query allNewsSlugs {
|
||||||
@@ -51,62 +40,14 @@ export async function generateMetadata(
|
|||||||
parent: ResolvingMetadata
|
parent: ResolvingMetadata
|
||||||
): Promise<Metadata | null> {
|
): Promise<Metadata | null> {
|
||||||
const { slug } = await params;
|
const { slug } = await params;
|
||||||
const { data, error } = await getClient().query(newsBySlugQuery, {
|
const props = await loadNewsPageProps({ slug });
|
||||||
slug,
|
if (!props) return null;
|
||||||
});
|
return getSeoMetadata(props.news, parent);
|
||||||
if (error) {
|
|
||||||
throw new Error(error.message);
|
|
||||||
}
|
|
||||||
if (!data?.news) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const news = data.news as NewsFragment;
|
|
||||||
const metadata = await getSeoMetadata(news, parent);
|
|
||||||
return metadata;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function Page({ params }: { params: Params }) {
|
export default async function Page({ params }: { params: Params }) {
|
||||||
const { slug } = await params;
|
const { slug } = await params;
|
||||||
const { data, error } = await getClient().query(newsBySlugQuery, {
|
const props = await loadNewsPageProps({ slug });
|
||||||
slug,
|
if (!props) return notFound();
|
||||||
});
|
return <NewsPageView {...props} />;
|
||||||
|
|
||||||
if (error) {
|
|
||||||
throw new Error(error.message);
|
|
||||||
}
|
|
||||||
if (!data?.news) {
|
|
||||||
return notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
const news = data?.news as NewsFragment;
|
|
||||||
const featuredImage: any = news.featuredImage;
|
|
||||||
return (
|
|
||||||
<main className="site-main" id="main">
|
|
||||||
<section className="news-header">
|
|
||||||
<Breadcrumb
|
|
||||||
link="/aktuelt"
|
|
||||||
text="Nyhet"
|
|
||||||
date={formatDate(news.firstPublishedAt, "d. MMMM yyyy")}
|
|
||||||
/>
|
|
||||||
<h1 className="news-title">{news.title}</h1>
|
|
||||||
{news.lead && (
|
|
||||||
<div
|
|
||||||
className="lead"
|
|
||||||
dangerouslySetInnerHTML={{ __html: news.lead }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{featuredImage && (
|
|
||||||
<ImageFigure
|
|
||||||
src={featuredImage.url}
|
|
||||||
alt={featuredImage.alt ?? ""}
|
|
||||||
width={featuredImage.width}
|
|
||||||
height={featuredImage.height}
|
|
||||||
attribution={featuredImage.attribution}
|
|
||||||
sizes="100vw"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</section>
|
|
||||||
<PageContent blocks={news.body} />
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,39 +1,19 @@
|
|||||||
import { getClient } from "@/app/client";
|
|
||||||
import { NewsList } from "@/components/news/NewsList";
|
|
||||||
import { Metadata, ResolvingMetadata } from "next";
|
import { Metadata, ResolvingMetadata } from "next";
|
||||||
import { PageHeader } from "@/components/general/PageHeader";
|
import {
|
||||||
import { newsQuery, NewsFragment, NewsIndexFragment } from "@/lib/news";
|
NewsIndexView,
|
||||||
|
loadNewsIndexProps,
|
||||||
|
} from "@/components/news/NewsIndexView";
|
||||||
import { getSeoMetadata } from "@/lib/seo";
|
import { getSeoMetadata } from "@/lib/seo";
|
||||||
|
|
||||||
export async function generateMetadata(
|
export async function generateMetadata(
|
||||||
{ params }: { params: Promise<{}> },
|
_: unknown,
|
||||||
parent: ResolvingMetadata
|
parent: ResolvingMetadata
|
||||||
): Promise<Metadata | null> {
|
): Promise<Metadata | null> {
|
||||||
const { data, error } = await getClient().query(newsQuery, {});
|
const { index } = await loadNewsIndexProps();
|
||||||
if (error) {
|
return getSeoMetadata(index, parent);
|
||||||
throw new Error(error.message);
|
|
||||||
}
|
|
||||||
if (!data?.index) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const index = data.index as NewsIndexFragment;
|
|
||||||
const metadata = await getSeoMetadata(index, parent);
|
|
||||||
return metadata;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function Page() {
|
export default async function Page() {
|
||||||
const { data, error } = await getClient().query(newsQuery, {});
|
const props = await loadNewsIndexProps();
|
||||||
if (error) {
|
return <NewsIndexView {...props} />;
|
||||||
throw new Error(error.message);
|
|
||||||
}
|
|
||||||
const news = (data?.news ?? []) as NewsFragment[];
|
|
||||||
const index = data?.index as NewsIndexFragment;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<main className="site-main" id="main">
|
|
||||||
<PageHeader heading={index.title} lead={index.lead} align="left" />
|
|
||||||
<NewsList news={news} />
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { cookies, draftMode } from "next/headers";
|
||||||
|
|
||||||
|
export async function POST() {
|
||||||
|
(await draftMode()).disable();
|
||||||
|
(await cookies()).delete("preview-token");
|
||||||
|
return new Response(null, { status: 204 });
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { cookies, draftMode } from "next/headers";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { NextRequest } from "next/server";
|
||||||
|
|
||||||
|
// Wagtail-headless-preview directs the editor's preview iframe here with
|
||||||
|
// ?content_type=app.Model&token=<signed>. We stash the token in a cookie,
|
||||||
|
// enable Next.js draft mode, and redirect to the type-dispatching renderer.
|
||||||
|
export async function GET(req: NextRequest) {
|
||||||
|
const token = req.nextUrl.searchParams.get("token");
|
||||||
|
const contentType = req.nextUrl.searchParams.get("content_type");
|
||||||
|
if (!token || !contentType) {
|
||||||
|
return new Response("missing token/content_type", { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
(await draftMode()).enable();
|
||||||
|
(await cookies()).set("preview-token", token, {
|
||||||
|
httpOnly: true,
|
||||||
|
sameSite: "lax",
|
||||||
|
secure: process.env.NODE_ENV === "production",
|
||||||
|
maxAge: 60 * 60 * 24,
|
||||||
|
path: "/",
|
||||||
|
});
|
||||||
|
|
||||||
|
redirect("/preview/render");
|
||||||
|
}
|
||||||
@@ -1,25 +1,13 @@
|
|||||||
import { Metadata, ResolvingMetadata } from "next";
|
import { Metadata, ResolvingMetadata } from "next";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { getClient } from "@/app/client";
|
import { getClient } from "@/app/client";
|
||||||
import { EventDetails } from "@/components/events/EventDetails";
|
import {
|
||||||
import { EventHeader } from "@/components/events/EventHeader";
|
EventPageView,
|
||||||
import { BgPig } from "@/components/general/BgPig";
|
loadEventPageProps,
|
||||||
import { PageContent } from "@/components/general/PageContent";
|
} from "@/components/events/EventPageView";
|
||||||
import { graphql } from "@/gql";
|
import { graphql } from "@/gql";
|
||||||
import { EventFragment } from "@/gql/graphql";
|
|
||||||
import { getEventPig } from "@/lib/event";
|
|
||||||
import { getSeoMetadata } from "@/lib/seo";
|
import { getSeoMetadata } from "@/lib/seo";
|
||||||
|
|
||||||
const eventBySlugQuery = graphql(`
|
|
||||||
query eventBySlug($slug: String!) {
|
|
||||||
event: page(contentType: "events.EventPage", slug: $slug) {
|
|
||||||
... on EventPage {
|
|
||||||
...Event
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
export async function generateStaticParams() {
|
export async function generateStaticParams() {
|
||||||
const allEventSlugsQuery = graphql(`
|
const allEventSlugsQuery = graphql(`
|
||||||
query allEventSlugs {
|
query allEventSlugs {
|
||||||
@@ -52,52 +40,14 @@ export async function generateMetadata(
|
|||||||
parent: ResolvingMetadata
|
parent: ResolvingMetadata
|
||||||
): Promise<Metadata | null> {
|
): Promise<Metadata | null> {
|
||||||
const { slug } = await params;
|
const { slug } = await params;
|
||||||
const { data, error } = await getClient().query(eventBySlugQuery, {
|
const props = await loadEventPageProps({ slug });
|
||||||
slug,
|
if (!props) return null;
|
||||||
});
|
return getSeoMetadata(props.event, parent);
|
||||||
|
|
||||||
if (error) {
|
|
||||||
throw new Error(error.message);
|
|
||||||
}
|
|
||||||
if (!data?.event) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const event = data.event as EventFragment;
|
|
||||||
const metadata = await getSeoMetadata(event, parent);
|
|
||||||
return metadata;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function Page({ params }: { params: Params }) {
|
export default async function Page({ params }: { params: Params }) {
|
||||||
const { slug } = await params;
|
const { slug } = await params;
|
||||||
const { data, error } = await getClient().query(eventBySlugQuery, {
|
const props = await loadEventPageProps({ slug });
|
||||||
slug,
|
if (!props) return notFound();
|
||||||
});
|
return <EventPageView {...props} />;
|
||||||
|
|
||||||
if (error) {
|
|
||||||
throw new Error(error.message);
|
|
||||||
}
|
|
||||||
if (!data?.event) {
|
|
||||||
return notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
const event = data.event as EventFragment;
|
|
||||||
const eventPig = getEventPig(event);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<main className="site-main" id="main">
|
|
||||||
<EventHeader event={event} />
|
|
||||||
<EventDetails event={event} />
|
|
||||||
{event.lead && (
|
|
||||||
<div
|
|
||||||
className="lead event-lead"
|
|
||||||
dangerouslySetInnerHTML={{ __html: event.lead }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<PageContent blocks={event.body} />
|
|
||||||
</main>
|
|
||||||
{eventPig && <BgPig type={eventPig} color="white" />}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,68 +1,24 @@
|
|||||||
import { Suspense } from "react";
|
|
||||||
import { Metadata, ResolvingMetadata } from "next";
|
import { Metadata, ResolvingMetadata } from "next";
|
||||||
import { getClient } from "@/app/client";
|
import { getClient } from "@/app/client";
|
||||||
import { EventContainer } from "@/components/events/EventContainer";
|
|
||||||
import {
|
import {
|
||||||
eventsOverviewQuery,
|
EventIndexView,
|
||||||
eventIndexMetadataQuery,
|
loadEventIndexProps,
|
||||||
EventFragment,
|
} from "@/components/events/EventIndexView";
|
||||||
EventCategory,
|
import { EventIndexFragment } from "@/gql/graphql";
|
||||||
EventOrganizer,
|
import { eventIndexMetadataQuery } from "@/lib/event";
|
||||||
} from "@/lib/event";
|
|
||||||
import { PageHeader } from "@/components/general/PageHeader";
|
|
||||||
import { EventIndexFragment, VenueFragment } from "@/gql/graphql";
|
|
||||||
import { getSeoMetadata } from "@/lib/seo";
|
import { getSeoMetadata } from "@/lib/seo";
|
||||||
|
|
||||||
export async function generateMetadata(
|
export async function generateMetadata(
|
||||||
{ params }: { params: Promise<{}> },
|
_: unknown,
|
||||||
parent: ResolvingMetadata
|
parent: ResolvingMetadata
|
||||||
): Promise<Metadata | null> {
|
): Promise<Metadata | null> {
|
||||||
const { data, error } = await getClient().query(eventIndexMetadataQuery, {});
|
const { data, error } = await getClient().query(eventIndexMetadataQuery, {});
|
||||||
|
if (error) throw new Error(error.message);
|
||||||
if (error) {
|
if (!data?.index) return null;
|
||||||
throw new Error(error.message);
|
return getSeoMetadata(data.index as EventIndexFragment, parent);
|
||||||
}
|
|
||||||
if (!data?.index) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const index = data.index as EventIndexFragment;
|
|
||||||
const metadata = await getSeoMetadata(index, parent);
|
|
||||||
return metadata;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function Page() {
|
export default async function Page() {
|
||||||
const { data, error } = await getClient().query(eventsOverviewQuery, {});
|
const props = await loadEventIndexProps();
|
||||||
if (error) {
|
return <EventIndexView {...props} />;
|
||||||
throw new Error(error.message);
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
!data?.index ||
|
|
||||||
!data?.events?.futureEvents ||
|
|
||||||
!data?.eventCategories ||
|
|
||||||
!data?.eventOrganizers ||
|
|
||||||
!data?.venues
|
|
||||||
) {
|
|
||||||
throw new Error("Failed to render /arrangementer");
|
|
||||||
}
|
|
||||||
|
|
||||||
const index = data?.index as EventIndexFragment;
|
|
||||||
const events = (data?.events?.futureEvents ?? []) as EventFragment[];
|
|
||||||
const eventCategories = (data?.eventCategories ?? []) as EventCategory[];
|
|
||||||
const eventOrganizers = (data?.eventOrganizers ?? []) as EventOrganizer[];
|
|
||||||
const venues = (data?.venues ?? []) as VenueFragment[];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<main className="site-main" id="main">
|
|
||||||
<PageHeader heading="Dette skjer på Chateau Neuf" align="left" />
|
|
||||||
<Suspense>
|
|
||||||
<EventContainer
|
|
||||||
events={events}
|
|
||||||
eventCategories={eventCategories}
|
|
||||||
eventOrganizers={eventOrganizers}
|
|
||||||
venues={venues}
|
|
||||||
/>
|
|
||||||
</Suspense>
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,15 @@ import "server-only";
|
|||||||
import { cacheExchange, createClient, fetchExchange } from "@urql/core";
|
import { cacheExchange, createClient, fetchExchange } from "@urql/core";
|
||||||
import { registerUrql } from "@urql/next/rsc";
|
import { registerUrql } from "@urql/next/rsc";
|
||||||
|
|
||||||
|
const wagtailBaseUrl = process.env.WAGTAIL_BASE_URL;
|
||||||
|
if (!wagtailBaseUrl) {
|
||||||
|
throw new Error("WAGTAIL_BASE_URL is not set");
|
||||||
|
}
|
||||||
|
const graphqlEndpoint = `${wagtailBaseUrl.replace(/\/$/, "")}/api/graphql/`;
|
||||||
|
|
||||||
const makeClient = () => {
|
const makeClient = () => {
|
||||||
return createClient({
|
return createClient({
|
||||||
url: process.env.GRAPHQL_ENDPOINT ?? "",
|
url: graphqlEndpoint,
|
||||||
exchanges: [cacheExchange, fetchExchange],
|
exchanges: [cacheExchange, fetchExchange],
|
||||||
// requestPolicy: "network-only",
|
// requestPolicy: "network-only",
|
||||||
fetchOptions: { next: { revalidate: 0 } },
|
fetchOptions: { next: { revalidate: 0 } },
|
||||||
|
|||||||
@@ -1,25 +1,13 @@
|
|||||||
import { Metadata, ResolvingMetadata } from "next";
|
import { Metadata, ResolvingMetadata } from "next";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { getClient } from "@/app/client";
|
import { getClient } from "@/app/client";
|
||||||
import { AssociationHeader } from "@/components/associations/AssociationHeader";
|
import {
|
||||||
import { PageContent } from "@/components/general/PageContent";
|
AssociationPageView,
|
||||||
|
loadAssociationPageProps,
|
||||||
|
} from "@/components/associations/AssociationPageView";
|
||||||
import { graphql } from "@/gql";
|
import { graphql } from "@/gql";
|
||||||
import { AssociationFragment } from "@/gql/graphql";
|
|
||||||
import { getSeoMetadata } from "@/lib/seo";
|
import { getSeoMetadata } from "@/lib/seo";
|
||||||
|
|
||||||
const associationBySlugQuery = graphql(`
|
|
||||||
query associationBySlug($slug: String!) {
|
|
||||||
association: page(
|
|
||||||
contentType: "associations.AssociationPage"
|
|
||||||
slug: $slug
|
|
||||||
) {
|
|
||||||
... on AssociationPage {
|
|
||||||
...Association
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
type Params = Promise<{ slug: string }>;
|
type Params = Promise<{ slug: string }>;
|
||||||
|
|
||||||
export async function generateMetadata(
|
export async function generateMetadata(
|
||||||
@@ -27,20 +15,9 @@ export async function generateMetadata(
|
|||||||
parent: ResolvingMetadata
|
parent: ResolvingMetadata
|
||||||
): Promise<Metadata | null> {
|
): Promise<Metadata | null> {
|
||||||
const { slug } = await params;
|
const { slug } = await params;
|
||||||
const { data, error } = await getClient().query(associationBySlugQuery, {
|
const props = await loadAssociationPageProps({ slug });
|
||||||
slug,
|
if (!props) return null;
|
||||||
});
|
return getSeoMetadata(props.association, parent);
|
||||||
|
|
||||||
if (error) {
|
|
||||||
throw new Error(error.message);
|
|
||||||
}
|
|
||||||
if (!data?.association) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const association = data.association as AssociationFragment;
|
|
||||||
const metadata = await getSeoMetadata(association, parent);
|
|
||||||
return metadata;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generateStaticParams() {
|
export async function generateStaticParams() {
|
||||||
@@ -70,23 +47,7 @@ export async function generateStaticParams() {
|
|||||||
|
|
||||||
export default async function Page({ params }: { params: Params }) {
|
export default async function Page({ params }: { params: Params }) {
|
||||||
const { slug } = await params;
|
const { slug } = await params;
|
||||||
const { data, error } = await getClient().query(associationBySlugQuery, {
|
const props = await loadAssociationPageProps({ slug });
|
||||||
slug,
|
if (!props) return notFound();
|
||||||
});
|
return <AssociationPageView {...props} />;
|
||||||
|
|
||||||
if (error) {
|
|
||||||
throw new Error(error.message);
|
|
||||||
}
|
|
||||||
if (!data?.association) {
|
|
||||||
return notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
const association = data.association as AssociationFragment;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<main className="site-main" id="main">
|
|
||||||
<AssociationHeader association={association} />
|
|
||||||
<PageContent blocks={association.body} />
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,106 +1,19 @@
|
|||||||
import { Metadata, ResolvingMetadata } from "next";
|
import { Metadata, ResolvingMetadata } from "next";
|
||||||
import { graphql } from "@/gql";
|
import {
|
||||||
import { AssociationFragment, AssociationIndexFragment } from "@/gql/graphql";
|
AssociationIndexView,
|
||||||
import { getClient } from "@/app/client";
|
loadAssociationIndexProps,
|
||||||
import { AssociationList } from "@/components/associations/AssociationList";
|
} from "@/components/associations/AssociationIndexView";
|
||||||
import { PageHeader } from "@/components/general/PageHeader";
|
|
||||||
import { PageContent } from "@/components/general/PageContent";
|
|
||||||
import { getSeoMetadata } from "@/lib/seo";
|
import { getSeoMetadata } from "@/lib/seo";
|
||||||
|
|
||||||
const allAssociationsQuery = graphql(`
|
|
||||||
query allAssociations {
|
|
||||||
index: associationIndex {
|
|
||||||
... on AssociationIndex {
|
|
||||||
...AssociationIndex
|
|
||||||
}
|
|
||||||
}
|
|
||||||
associations: pages(
|
|
||||||
contentType: "associations.AssociationPage"
|
|
||||||
limit: 1000
|
|
||||||
) {
|
|
||||||
... on AssociationPage {
|
|
||||||
...Association
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
export async function generateMetadata(
|
export async function generateMetadata(
|
||||||
{ params }: { params: Promise<{}> },
|
_: unknown,
|
||||||
parent: ResolvingMetadata
|
parent: ResolvingMetadata
|
||||||
): Promise<Metadata | null> {
|
): Promise<Metadata | null> {
|
||||||
const { data, error } = await getClient().query(allAssociationsQuery, {});
|
const { index } = await loadAssociationIndexProps();
|
||||||
|
return getSeoMetadata(index, parent);
|
||||||
if (error) {
|
|
||||||
throw new Error(error.message);
|
|
||||||
}
|
|
||||||
if (!data?.index) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const index = data.index as AssociationIndexFragment;
|
|
||||||
const metadata = await getSeoMetadata(index, parent);
|
|
||||||
return metadata;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const AssociationIndexDefinition = graphql(`
|
|
||||||
fragment AssociationIndex on AssociationIndex {
|
|
||||||
... on AssociationIndex {
|
|
||||||
title
|
|
||||||
seoTitle
|
|
||||||
searchDescription
|
|
||||||
lead
|
|
||||||
body {
|
|
||||||
...Blocks
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
const AssociationFragmentDefinition = graphql(`
|
|
||||||
fragment Association on AssociationPage {
|
|
||||||
__typename
|
|
||||||
id
|
|
||||||
slug
|
|
||||||
title
|
|
||||||
seoTitle
|
|
||||||
searchDescription
|
|
||||||
excerpt
|
|
||||||
lead
|
|
||||||
body {
|
|
||||||
...Blocks
|
|
||||||
}
|
|
||||||
logo {
|
|
||||||
url
|
|
||||||
width
|
|
||||||
height
|
|
||||||
}
|
|
||||||
associationType
|
|
||||||
websiteUrl
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
export default async function Page() {
|
export default async function Page() {
|
||||||
const { data, error } = await getClient().query(allAssociationsQuery, {});
|
const props = await loadAssociationIndexProps();
|
||||||
|
return <AssociationIndexView {...props} />;
|
||||||
if (error) {
|
|
||||||
throw new Error(error.message);
|
|
||||||
}
|
|
||||||
if (!data?.associations || !data.index) {
|
|
||||||
throw new Error("Failed to render /foreninger");
|
|
||||||
}
|
|
||||||
|
|
||||||
const associations = data.associations as AssociationFragment[];
|
|
||||||
const index = data.index as AssociationIndexFragment;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<main className="site-main" id="main">
|
|
||||||
<PageHeader heading={index.title} lead={index.lead} />
|
|
||||||
{index.body && <PageContent blocks={index.body} />}
|
|
||||||
<AssociationList
|
|
||||||
associations={associations}
|
|
||||||
heading="Foreninger og utvalg"
|
|
||||||
/>
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,72 +1,19 @@
|
|||||||
import { Metadata, ResolvingMetadata } from "next";
|
import { Metadata, ResolvingMetadata } from "next";
|
||||||
import { notFound } from "next/navigation";
|
import {
|
||||||
import { graphql } from "@/gql";
|
ContactIndexView,
|
||||||
import { ContactIndexFragment } from "@/gql/graphql";
|
loadContactIndexProps,
|
||||||
import { getClient } from "@/app/client";
|
} from "@/components/contact/ContactIndexView";
|
||||||
import { PageHeader } from "@/components/general/PageHeader";
|
|
||||||
import { PageContent } from "@/components/general/PageContent";
|
|
||||||
import { GeneralContactBlock } from "@/components/blocks/GeneralContactBlock";
|
|
||||||
import { getSeoMetadata } from "@/lib/seo";
|
import { getSeoMetadata } from "@/lib/seo";
|
||||||
|
|
||||||
const contactQuery = graphql(`
|
|
||||||
query contacts {
|
|
||||||
index: contactIndex {
|
|
||||||
... on ContactIndex {
|
|
||||||
...ContactIndex
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
const ContactIndexDefinition = graphql(`
|
|
||||||
fragment ContactIndex on ContactIndex {
|
|
||||||
... on ContactIndex {
|
|
||||||
title
|
|
||||||
seoTitle
|
|
||||||
searchDescription
|
|
||||||
lead
|
|
||||||
body {
|
|
||||||
...Blocks
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
export async function generateMetadata(
|
export async function generateMetadata(
|
||||||
{ params }: { params: Promise<{}> },
|
_: unknown,
|
||||||
parent: ResolvingMetadata
|
parent: ResolvingMetadata
|
||||||
): Promise<Metadata | null> {
|
): Promise<Metadata | null> {
|
||||||
const { data, error } = await getClient().query(contactQuery, {});
|
const { index } = await loadContactIndexProps();
|
||||||
|
return getSeoMetadata(index, parent);
|
||||||
if (error) {
|
|
||||||
throw new Error(error.message);
|
|
||||||
}
|
|
||||||
if (!data?.index) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const index = data.index as ContactIndexFragment;
|
|
||||||
const metadata = await getSeoMetadata(index, parent);
|
|
||||||
return metadata;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function Page() {
|
export default async function Page() {
|
||||||
const { data, error } = await getClient().query(contactQuery, {});
|
const props = await loadContactIndexProps();
|
||||||
|
return <ContactIndexView {...props} />;
|
||||||
if (error) {
|
|
||||||
throw new Error(error.message);
|
|
||||||
}
|
|
||||||
if (!data?.index) {
|
|
||||||
return notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
const index = data.index as ContactIndexFragment;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<main className="site-main" id="main">
|
|
||||||
<PageHeader heading={index.title} lead={index.lead} />
|
|
||||||
<GeneralContactBlock />
|
|
||||||
{index.body && <PageContent blocks={index.body} />}
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,13 @@
|
|||||||
import { Metadata, ResolvingMetadata } from "next";
|
import { Metadata, ResolvingMetadata } from "next";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { getClient } from "@/app/client";
|
import { getClient } from "@/app/client";
|
||||||
import { ImageSliderBlock } from "@/components/blocks/ImageSliderBlock";
|
import {
|
||||||
import { Breadcrumb } from "@/components/general/Breadcrumb";
|
VenuePageView,
|
||||||
import { PageContent } from "@/components/general/PageContent";
|
loadVenuePageProps,
|
||||||
import { NeufMap } from "@/components/venues/NeufMap";
|
} from "@/components/venues/VenuePageView";
|
||||||
import { VenueInfo } from "@/components/venues/VenueInfo";
|
|
||||||
import { graphql } from "@/gql";
|
import { graphql } from "@/gql";
|
||||||
import { VenueFragment } from "@/gql/graphql";
|
|
||||||
import { getSeoMetadata } from "@/lib/seo";
|
import { getSeoMetadata } from "@/lib/seo";
|
||||||
|
|
||||||
const venueBySlugQuery = graphql(`
|
|
||||||
query venueBySlug($slug: String!) {
|
|
||||||
venue: page(contentType: "venues.VenuePage", slug: $slug) {
|
|
||||||
... on VenuePage {
|
|
||||||
...Venue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
type Params = Promise<{ slug: string }>;
|
type Params = Promise<{ slug: string }>;
|
||||||
|
|
||||||
export async function generateMetadata(
|
export async function generateMetadata(
|
||||||
@@ -27,20 +15,9 @@ export async function generateMetadata(
|
|||||||
parent: ResolvingMetadata
|
parent: ResolvingMetadata
|
||||||
): Promise<Metadata | null> {
|
): Promise<Metadata | null> {
|
||||||
const { slug } = await params;
|
const { slug } = await params;
|
||||||
const { data, error } = await getClient().query(venueBySlugQuery, {
|
const props = await loadVenuePageProps({ slug });
|
||||||
slug,
|
if (!props) return null;
|
||||||
});
|
return getSeoMetadata(props.venue, parent);
|
||||||
|
|
||||||
if (error) {
|
|
||||||
throw new Error(error.message);
|
|
||||||
}
|
|
||||||
if (!data?.venue) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const venue = data.venue as VenueFragment;
|
|
||||||
const metadata = await getSeoMetadata(venue, parent);
|
|
||||||
return metadata;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generateStaticParams() {
|
export async function generateStaticParams() {
|
||||||
@@ -70,32 +47,7 @@ export async function generateStaticParams() {
|
|||||||
|
|
||||||
export default async function Page({ params }: { params: Params }) {
|
export default async function Page({ params }: { params: Params }) {
|
||||||
const { slug } = await params;
|
const { slug } = await params;
|
||||||
const { data, error } = await getClient().query(venueBySlugQuery, {
|
const props = await loadVenuePageProps({ slug });
|
||||||
slug,
|
if (!props) return notFound();
|
||||||
});
|
return <VenuePageView {...props} />;
|
||||||
|
|
||||||
if (error) {
|
|
||||||
throw new Error(error.message);
|
|
||||||
}
|
|
||||||
if (!data?.venue) {
|
|
||||||
return notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
const venue = data.venue as VenueFragment;
|
|
||||||
const featuredImage: any = venue.featuredImage;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<main className="site-main" id="main">
|
|
||||||
{venue.images && venue.images.length !== 0 && (
|
|
||||||
<ImageSliderBlock block={venue.images[0]} hero />
|
|
||||||
)}
|
|
||||||
<div className="page-header-small">
|
|
||||||
<Breadcrumb link="/utleie" text="Lokale" />
|
|
||||||
<h1 className="page-title">{venue.title}</h1>
|
|
||||||
</div>
|
|
||||||
<PageContent blocks={venue.body} />
|
|
||||||
<VenueInfo venue={venue} />
|
|
||||||
<NeufMap venueSlug={venue.slug} />
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,111 +1,19 @@
|
|||||||
import { Metadata, ResolvingMetadata } from "next";
|
import { Metadata, ResolvingMetadata } from "next";
|
||||||
import { graphql } from "@/gql";
|
import {
|
||||||
import { VenueFragment, VenueIndexFragment } from "@/gql/graphql";
|
VenueIndexView,
|
||||||
import { getClient } from "@/app/client";
|
loadVenueIndexProps,
|
||||||
import { VenueList } from "@/components/venues/VenueList";
|
} from "@/components/venues/VenueIndexView";
|
||||||
import { PageHeader } from "@/components/general/PageHeader";
|
|
||||||
import { PageContent } from "@/components/general/PageContent";
|
|
||||||
import { getSeoMetadata } from "@/lib/seo";
|
import { getSeoMetadata } from "@/lib/seo";
|
||||||
|
|
||||||
const venueIndexQuery = graphql(`
|
|
||||||
query venueIndex {
|
|
||||||
index: venueIndex {
|
|
||||||
... on VenueIndex {
|
|
||||||
...VenueIndex
|
|
||||||
}
|
|
||||||
}
|
|
||||||
venues: pages(contentType: "venues.VenuePage", limit: 100) {
|
|
||||||
... on VenuePage {
|
|
||||||
...Venue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
const VenueIndexDefinition = graphql(`
|
|
||||||
fragment VenueIndex on VenueIndex {
|
|
||||||
... on VenueIndex {
|
|
||||||
title
|
|
||||||
seoTitle
|
|
||||||
searchDescription
|
|
||||||
lead
|
|
||||||
body {
|
|
||||||
...Blocks
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
const VenueFragmentDefinition = graphql(`
|
|
||||||
fragment Venue on VenuePage {
|
|
||||||
__typename
|
|
||||||
id
|
|
||||||
slug
|
|
||||||
title
|
|
||||||
seoTitle
|
|
||||||
searchDescription
|
|
||||||
images {
|
|
||||||
...Blocks
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
...Blocks
|
|
||||||
}
|
|
||||||
featuredImage {
|
|
||||||
...Image
|
|
||||||
}
|
|
||||||
showAsBookable
|
|
||||||
showInOverview
|
|
||||||
floor
|
|
||||||
preposition
|
|
||||||
usedFor
|
|
||||||
techSpecsUrl
|
|
||||||
capabilityAudio
|
|
||||||
capabilityAudioVideo
|
|
||||||
capabilityBar
|
|
||||||
capabilityLighting
|
|
||||||
capacityLegal
|
|
||||||
capacityStanding
|
|
||||||
capacitySitting
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
export async function generateMetadata(
|
export async function generateMetadata(
|
||||||
{ params }: { params: Promise<{}> },
|
_: unknown,
|
||||||
parent: ResolvingMetadata
|
parent: ResolvingMetadata
|
||||||
): Promise<Metadata | null> {
|
): Promise<Metadata | null> {
|
||||||
const { data, error } = await getClient().query(venueIndexQuery, {});
|
const { index } = await loadVenueIndexProps();
|
||||||
|
return getSeoMetadata(index, parent);
|
||||||
if (error) {
|
|
||||||
throw new Error(error.message);
|
|
||||||
}
|
|
||||||
if (!data?.index) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const index = (data?.index ?? []) as VenueIndexFragment;
|
|
||||||
const metadata = await getSeoMetadata(index, parent);
|
|
||||||
return metadata;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function Page() {
|
export default async function Page() {
|
||||||
const { data, error } = await getClient().query(venueIndexQuery, {});
|
const props = await loadVenueIndexProps();
|
||||||
|
return <VenueIndexView {...props} />;
|
||||||
if (error) {
|
|
||||||
throw new Error(error.message);
|
|
||||||
}
|
|
||||||
if (!data?.index || !data?.venues) {
|
|
||||||
throw new Error("Failed to render /lokaler");
|
|
||||||
}
|
|
||||||
|
|
||||||
const index = data.index as VenueIndexFragment;
|
|
||||||
const venues = (data?.venues ?? []) as VenueFragment[];
|
|
||||||
const visibleVenues = venues.filter((x) => x.showInOverview);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<main className="site-main" id="main">
|
|
||||||
<PageHeader heading={index.title} lead={index.lead} />
|
|
||||||
<PageContent blocks={index.body} />
|
|
||||||
<VenueList venues={visibleVenues} />
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-94
@@ -1,97 +1,9 @@
|
|||||||
import { graphql } from "@/gql";
|
import {
|
||||||
import { EventFragment } from "@/lib/event";
|
HomePageView,
|
||||||
import { NewsFragment } from "@/lib/news";
|
loadHomePageProps,
|
||||||
import { HomeFragment } from "@/gql/graphql";
|
} from "@/components/home/HomePageView";
|
||||||
import { getClient } from "@/app/client";
|
|
||||||
import { FeaturedEvents } from "@/components/events/FeaturedEvents";
|
|
||||||
import { NewsList } from "@/components/news/NewsList";
|
|
||||||
import { Newsletter } from "@/components/general/Newsletter";
|
|
||||||
import { UpcomingEvents } from "@/components/events/UpcomingEvents";
|
|
||||||
import { Pig } from "@/components/general/Pig";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { Icon } from "@/components/general/Icon";
|
|
||||||
import { SectionHeader } from "@/components/general/SectionHeader";
|
|
||||||
import { SectionFooter } from "@/components/general/SectionFooter";
|
|
||||||
|
|
||||||
const HomeFragmentDefinition = graphql(`
|
|
||||||
fragment Home on HomePage {
|
|
||||||
... on HomePage {
|
|
||||||
featuredEvents {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
export default async function Home() {
|
export default async function Home() {
|
||||||
const homeQuery = graphql(`
|
const props = await loadHomePageProps();
|
||||||
query home {
|
return <HomePageView {...props} />;
|
||||||
events: eventIndex {
|
|
||||||
... on EventIndex {
|
|
||||||
futureEvents {
|
|
||||||
... on EventPage {
|
|
||||||
...Event
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
home: page(contentType: "home.HomePage", urlPath: "/home/") {
|
|
||||||
... on HomePage {
|
|
||||||
...Home
|
|
||||||
}
|
|
||||||
}
|
|
||||||
news: pages(contentType: "news.newsPage", order: "-first_published_at", limit: 4) {
|
|
||||||
... on NewsPage {
|
|
||||||
...News
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
const { data, error } = await getClient().query(homeQuery, {});
|
|
||||||
const home = (data?.home ?? []) as HomeFragment;
|
|
||||||
const events = (data?.events?.futureEvents ?? []) as EventFragment[];
|
|
||||||
const news = (data?.news ?? []) as NewsFragment[];
|
|
||||||
|
|
||||||
const featuredEventIds = home.featuredEvents.map((x) => x.id);
|
|
||||||
const featuredEvents = [
|
|
||||||
...events.filter((x) => featuredEventIds.includes(x.id)),
|
|
||||||
...events.filter((x) => !featuredEventIds.includes(x.id)),
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<main className="site-main index" id="main">
|
|
||||||
<FeaturedEvents events={featuredEvents} />
|
|
||||||
<UpcomingEvents events={events} />
|
|
||||||
<div className="infoBlock">
|
|
||||||
<SectionHeader heading="Besøk oss" link="/praktisk" linkText="Praktisk info" />
|
|
||||||
<div>
|
|
||||||
<h2 className="title">Skal du besøke Chateau Neuf?</h2>
|
|
||||||
<p>
|
|
||||||
Vi hjelper deg med å finne frem, og sørger for at du har en fin
|
|
||||||
opplevelse.
|
|
||||||
</p>
|
|
||||||
<Link href="/praktisk#adkomst" className="button">
|
|
||||||
<span>Adresse og adkomst</span>
|
|
||||||
<Icon type="arrowRight" />
|
|
||||||
</Link>
|
|
||||||
<Link href="/praktisk#billetter" className="button">
|
|
||||||
<span>Billetter</span>
|
|
||||||
<Icon type="arrowRight" />
|
|
||||||
</Link>
|
|
||||||
<Link href="/praktisk#apningstider" className="button">
|
|
||||||
<span>Åpningstider</span>
|
|
||||||
<Icon type="arrowRight" />
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
<div className="pig">
|
|
||||||
<Pig type="point" />
|
|
||||||
</div>
|
|
||||||
<SectionFooter link="/praktisk" linkText="Praktisk info" />
|
|
||||||
</div>
|
|
||||||
<NewsList heading="Siste nytt" featured news={news} />
|
|
||||||
</main>
|
|
||||||
<Newsletter />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,256 @@
|
|||||||
|
import { getClient } from "@/app/client";
|
||||||
|
import { PreviewBanner } from "@/components/general/PreviewBanner";
|
||||||
|
import {
|
||||||
|
AssociationIndexView,
|
||||||
|
loadAssociationIndexProps,
|
||||||
|
} from "@/components/associations/AssociationIndexView";
|
||||||
|
import {
|
||||||
|
AssociationPageView,
|
||||||
|
loadAssociationPageProps,
|
||||||
|
} from "@/components/associations/AssociationPageView";
|
||||||
|
import {
|
||||||
|
ContactIndexView,
|
||||||
|
loadContactIndexProps,
|
||||||
|
} from "@/components/contact/ContactIndexView";
|
||||||
|
import {
|
||||||
|
EventIndexView,
|
||||||
|
loadEventIndexProps,
|
||||||
|
} from "@/components/events/EventIndexView";
|
||||||
|
import {
|
||||||
|
EventPageView,
|
||||||
|
loadEventPageProps,
|
||||||
|
} from "@/components/events/EventPageView";
|
||||||
|
import {
|
||||||
|
GenericPageView,
|
||||||
|
loadGenericPageProps,
|
||||||
|
} from "@/components/general/GenericPageView";
|
||||||
|
import {
|
||||||
|
HomePageView,
|
||||||
|
loadHomePageProps,
|
||||||
|
} from "@/components/home/HomePageView";
|
||||||
|
import {
|
||||||
|
NewsIndexView,
|
||||||
|
loadNewsIndexProps,
|
||||||
|
} from "@/components/news/NewsIndexView";
|
||||||
|
import {
|
||||||
|
NewsPageView,
|
||||||
|
loadNewsPageProps,
|
||||||
|
} from "@/components/news/NewsPageView";
|
||||||
|
import {
|
||||||
|
SponsorsPageView,
|
||||||
|
loadSponsorsPageProps,
|
||||||
|
} from "@/components/sponsor/SponsorsPageView";
|
||||||
|
import {
|
||||||
|
StudioPageView,
|
||||||
|
loadStudioPageProps,
|
||||||
|
} from "@/components/studio/StudioPageView";
|
||||||
|
import {
|
||||||
|
VenueIndexView,
|
||||||
|
loadVenueIndexProps,
|
||||||
|
} from "@/components/venues/VenueIndexView";
|
||||||
|
import {
|
||||||
|
VenuePageView,
|
||||||
|
loadVenuePageProps,
|
||||||
|
} from "@/components/venues/VenuePageView";
|
||||||
|
import {
|
||||||
|
VenueRentalIndexView,
|
||||||
|
loadVenueRentalIndexProps,
|
||||||
|
} from "@/components/venues/VenueRentalIndexView";
|
||||||
|
import { graphql } from "@/gql";
|
||||||
|
import {
|
||||||
|
AssociationFragment,
|
||||||
|
AssociationIndexFragment,
|
||||||
|
ContactIndexFragment,
|
||||||
|
EventFragment,
|
||||||
|
GenericFragment,
|
||||||
|
HomeFragment,
|
||||||
|
NewsFragment,
|
||||||
|
NewsIndexFragment,
|
||||||
|
SponsorsPageFragment,
|
||||||
|
StudioFragment,
|
||||||
|
VenueFragment,
|
||||||
|
VenueIndexFragment,
|
||||||
|
VenueRentalIndexFragment,
|
||||||
|
} from "@/gql/graphql";
|
||||||
|
import { cookies } from "next/headers";
|
||||||
|
export const dynamic = "force-dynamic";
|
||||||
|
export const revalidate = 0;
|
||||||
|
|
||||||
|
const previewPageQuery = graphql(`
|
||||||
|
query previewPage($token: String!) {
|
||||||
|
page: page(token: $token) {
|
||||||
|
__typename
|
||||||
|
... on GenericPage {
|
||||||
|
...Generic
|
||||||
|
}
|
||||||
|
... on StudioPage {
|
||||||
|
...Studio
|
||||||
|
}
|
||||||
|
... on SponsorsPage {
|
||||||
|
...SponsorsPage
|
||||||
|
}
|
||||||
|
... on HomePage {
|
||||||
|
...Home
|
||||||
|
}
|
||||||
|
... on EventPage {
|
||||||
|
...Event
|
||||||
|
}
|
||||||
|
... on NewsPage {
|
||||||
|
...News
|
||||||
|
}
|
||||||
|
... on AssociationPage {
|
||||||
|
...Association
|
||||||
|
}
|
||||||
|
... on VenuePage {
|
||||||
|
...Venue
|
||||||
|
}
|
||||||
|
... on NewsIndex {
|
||||||
|
...NewsIndex
|
||||||
|
}
|
||||||
|
... on AssociationIndex {
|
||||||
|
...AssociationIndex
|
||||||
|
}
|
||||||
|
... on VenueIndex {
|
||||||
|
...VenueIndex
|
||||||
|
}
|
||||||
|
... on VenueRentalIndex {
|
||||||
|
...VenueRentalIndex
|
||||||
|
}
|
||||||
|
... on ContactIndex {
|
||||||
|
...ContactIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
function ExpiredPreview() {
|
||||||
|
return (
|
||||||
|
<main className="site-main" id="main">
|
||||||
|
<h1>Preview session expired</h1>
|
||||||
|
<p>Click Preview again in the Wagtail admin to start a new session.</p>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function UnsupportedType({ typename }: { typename: string }) {
|
||||||
|
return (
|
||||||
|
<main className="site-main" id="main">
|
||||||
|
<h1>Preview not available</h1>
|
||||||
|
<p>
|
||||||
|
Type <code>{typename}</code> cannot be previewed.
|
||||||
|
</p>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function PreviewRender() {
|
||||||
|
const token = (await cookies()).get("preview-token")?.value;
|
||||||
|
if (!token) {
|
||||||
|
return <ExpiredPreview />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data, error } = await getClient().query(previewPageQuery, { token });
|
||||||
|
if (error) {
|
||||||
|
throw new Error(error.message);
|
||||||
|
}
|
||||||
|
if (!data?.page) {
|
||||||
|
return <ExpiredPreview />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const page = data.page;
|
||||||
|
const view = await (async () => {
|
||||||
|
switch (page.__typename) {
|
||||||
|
case "GenericPage": {
|
||||||
|
const props = await loadGenericPageProps({
|
||||||
|
pageOverride: page as GenericFragment,
|
||||||
|
});
|
||||||
|
return <GenericPageView {...props!} />;
|
||||||
|
}
|
||||||
|
case "StudioPage": {
|
||||||
|
const props = await loadStudioPageProps({
|
||||||
|
pageOverride: page as StudioFragment,
|
||||||
|
});
|
||||||
|
return <StudioPageView {...props} />;
|
||||||
|
}
|
||||||
|
case "SponsorsPage": {
|
||||||
|
const props = await loadSponsorsPageProps({
|
||||||
|
pageOverride: page as SponsorsPageFragment,
|
||||||
|
});
|
||||||
|
return <SponsorsPageView {...props} />;
|
||||||
|
}
|
||||||
|
case "EventPage": {
|
||||||
|
const props = await loadEventPageProps({
|
||||||
|
eventOverride: page as EventFragment,
|
||||||
|
});
|
||||||
|
return <EventPageView {...props!} />;
|
||||||
|
}
|
||||||
|
case "NewsPage": {
|
||||||
|
const props = await loadNewsPageProps({
|
||||||
|
newsOverride: page as NewsFragment,
|
||||||
|
});
|
||||||
|
return <NewsPageView {...props!} />;
|
||||||
|
}
|
||||||
|
case "AssociationPage": {
|
||||||
|
const props = await loadAssociationPageProps({
|
||||||
|
associationOverride: page as AssociationFragment,
|
||||||
|
});
|
||||||
|
return <AssociationPageView {...props!} />;
|
||||||
|
}
|
||||||
|
case "VenuePage": {
|
||||||
|
const props = await loadVenuePageProps({
|
||||||
|
venueOverride: page as VenueFragment,
|
||||||
|
});
|
||||||
|
return <VenuePageView {...props!} />;
|
||||||
|
}
|
||||||
|
case "HomePage": {
|
||||||
|
const props = await loadHomePageProps({
|
||||||
|
homeOverride: page as HomeFragment,
|
||||||
|
});
|
||||||
|
return <HomePageView {...props} />;
|
||||||
|
}
|
||||||
|
case "EventIndex": {
|
||||||
|
const props = await loadEventIndexProps();
|
||||||
|
return <EventIndexView {...props} />;
|
||||||
|
}
|
||||||
|
case "NewsIndex": {
|
||||||
|
const props = await loadNewsIndexProps({
|
||||||
|
indexOverride: page as NewsIndexFragment,
|
||||||
|
});
|
||||||
|
return <NewsIndexView {...props} />;
|
||||||
|
}
|
||||||
|
case "AssociationIndex": {
|
||||||
|
const props = await loadAssociationIndexProps({
|
||||||
|
indexOverride: page as AssociationIndexFragment,
|
||||||
|
});
|
||||||
|
return <AssociationIndexView {...props} />;
|
||||||
|
}
|
||||||
|
case "VenueIndex": {
|
||||||
|
const props = await loadVenueIndexProps({
|
||||||
|
indexOverride: page as VenueIndexFragment,
|
||||||
|
});
|
||||||
|
return <VenueIndexView {...props} />;
|
||||||
|
}
|
||||||
|
case "VenueRentalIndex": {
|
||||||
|
const props = await loadVenueRentalIndexProps({
|
||||||
|
indexOverride: page as VenueRentalIndexFragment,
|
||||||
|
});
|
||||||
|
return <VenueRentalIndexView {...props} />;
|
||||||
|
}
|
||||||
|
case "ContactIndex": {
|
||||||
|
const props = await loadContactIndexProps({
|
||||||
|
indexOverride: page as ContactIndexFragment,
|
||||||
|
});
|
||||||
|
return <ContactIndexView {...props} />;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return <UnsupportedType typename={page.__typename ?? "unknown"} />;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<PreviewBanner />
|
||||||
|
{view}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,82 +1,19 @@
|
|||||||
import { Metadata, ResolvingMetadata } from "next";
|
import { Metadata, ResolvingMetadata } from "next";
|
||||||
import { graphql } from "@/gql";
|
import {
|
||||||
import { SponsorsPage, SponsorBlock } from "@/gql/graphql";
|
SponsorsPageView,
|
||||||
import { getClient } from "@/app/client";
|
loadSponsorsPageProps,
|
||||||
import { PageHeader } from "@/components/general/PageHeader";
|
} from "@/components/sponsor/SponsorsPageView";
|
||||||
import { PageContent } from "@/components/general/PageContent";
|
|
||||||
import { getSeoMetadata } from "@/lib/seo";
|
import { getSeoMetadata } from "@/lib/seo";
|
||||||
import { SponsorList } from "@/components/sponsor/SponsorList";
|
|
||||||
|
|
||||||
const sponsorsPageQuery = graphql(`
|
|
||||||
query sponsors {
|
|
||||||
page: sponsorsPage {
|
|
||||||
... on SponsorsPage {
|
|
||||||
...SponsorsPage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
export async function generateMetadata(
|
export async function generateMetadata(
|
||||||
{ params }: { params: Promise<{}> },
|
_: unknown,
|
||||||
parent: ResolvingMetadata
|
parent: ResolvingMetadata
|
||||||
): Promise<Metadata | null> {
|
): Promise<Metadata | null> {
|
||||||
const { data, error } = await getClient().query(sponsorsPageQuery, {});
|
const { page } = await loadSponsorsPageProps();
|
||||||
|
return getSeoMetadata(page, parent);
|
||||||
if (error) {
|
|
||||||
throw new Error(error.message);
|
|
||||||
}
|
|
||||||
if (!data?.page) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const index = data.page as SponsorsPage;
|
|
||||||
const metadata = await getSeoMetadata(index, parent);
|
|
||||||
return metadata;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const SponsorsPageFragmentDefinition = graphql(`
|
|
||||||
fragment SponsorsPage on SponsorsPage {
|
|
||||||
... on SponsorsPage {
|
|
||||||
title
|
|
||||||
seoTitle
|
|
||||||
searchDescription
|
|
||||||
lead
|
|
||||||
body {
|
|
||||||
...Blocks
|
|
||||||
}
|
|
||||||
sponsors {
|
|
||||||
... on SponsorBlock {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
logo {
|
|
||||||
...Image
|
|
||||||
}
|
|
||||||
text
|
|
||||||
website
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
export default async function Page() {
|
export default async function Page() {
|
||||||
const { data, error } = await getClient().query(sponsorsPageQuery, {});
|
const props = await loadSponsorsPageProps();
|
||||||
|
return <SponsorsPageView {...props} />;
|
||||||
if (error) {
|
|
||||||
throw new Error(error.message);
|
|
||||||
}
|
|
||||||
if (!data?.page) {
|
|
||||||
throw new Error("Failed to render /sponsorer");
|
|
||||||
}
|
|
||||||
|
|
||||||
const page = data.page as SponsorsPage;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<main className="site-main" id="main">
|
|
||||||
<PageHeader heading={page.title} lead={page.lead} />
|
|
||||||
{page.body && <PageContent blocks={page.body} />}
|
|
||||||
<SponsorList sponsors={page.sponsors as SponsorBlock[]} />
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { Metadata, ResolvingMetadata } from "next";
|
||||||
|
import {
|
||||||
|
StudioPageView,
|
||||||
|
loadStudioPageProps,
|
||||||
|
} from "@/components/studio/StudioPageView";
|
||||||
|
import { getSeoMetadata } from "@/lib/seo";
|
||||||
|
|
||||||
|
export async function generateMetadata(
|
||||||
|
_: unknown,
|
||||||
|
parent: ResolvingMetadata
|
||||||
|
): Promise<Metadata | null> {
|
||||||
|
const { page } = await loadStudioPageProps();
|
||||||
|
return getSeoMetadata(page, parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function Page() {
|
||||||
|
const props = await loadStudioPageProps();
|
||||||
|
return <StudioPageView {...props} />;
|
||||||
|
}
|
||||||
@@ -1,82 +1,19 @@
|
|||||||
import { Metadata, ResolvingMetadata } from "next";
|
import { Metadata, ResolvingMetadata } from "next";
|
||||||
import { graphql } from "@/gql";
|
import {
|
||||||
import { VenueFragment, VenueRentalIndexFragment } from "@/gql/graphql";
|
VenueRentalIndexView,
|
||||||
import { getClient } from "@/app/client";
|
loadVenueRentalIndexProps,
|
||||||
import { VenueList } from "@/components/venues/VenueList";
|
} from "@/components/venues/VenueRentalIndexView";
|
||||||
import { PageHeader } from "@/components/general/PageHeader";
|
|
||||||
import { BgPig } from "@/components/general/BgPig";
|
|
||||||
import { PageContent } from "@/components/general/PageContent";
|
|
||||||
import { getSeoMetadata } from "@/lib/seo";
|
import { getSeoMetadata } from "@/lib/seo";
|
||||||
|
|
||||||
const venueRentalIndexQuery = graphql(`
|
|
||||||
query venueRentalIndex {
|
|
||||||
index: venueRentalIndex {
|
|
||||||
... on VenueRentalIndex {
|
|
||||||
...VenueRentalIndex
|
|
||||||
}
|
|
||||||
}
|
|
||||||
venues: pages(contentType: "venues.VenuePage", limit: 100) {
|
|
||||||
... on VenuePage {
|
|
||||||
...Venue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
const VenueRentalIndexDefinition = graphql(`
|
|
||||||
fragment VenueRentalIndex on VenueRentalIndex {
|
|
||||||
... on VenueRentalIndex {
|
|
||||||
title
|
|
||||||
seoTitle
|
|
||||||
searchDescription
|
|
||||||
lead
|
|
||||||
body {
|
|
||||||
...Blocks
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
export async function generateMetadata(
|
export async function generateMetadata(
|
||||||
{ params }: { params: Promise<{}> },
|
_: unknown,
|
||||||
parent: ResolvingMetadata
|
parent: ResolvingMetadata
|
||||||
): Promise<Metadata | null> {
|
): Promise<Metadata | null> {
|
||||||
const { data, error } = await getClient().query(venueRentalIndexQuery, {});
|
const { index } = await loadVenueRentalIndexProps();
|
||||||
|
return getSeoMetadata(index, parent);
|
||||||
if (error) {
|
|
||||||
throw new Error(error.message);
|
|
||||||
}
|
|
||||||
if (!data?.index) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const index = data.index as VenueRentalIndexFragment;
|
|
||||||
const metadata = await getSeoMetadata(index, parent);
|
|
||||||
return metadata;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function Page() {
|
export default async function Page() {
|
||||||
const { data, error } = await getClient().query(venueRentalIndexQuery, {});
|
const props = await loadVenueRentalIndexProps();
|
||||||
|
return <VenueRentalIndexView {...props} />;
|
||||||
if (error) {
|
|
||||||
throw new Error(error.message);
|
|
||||||
}
|
|
||||||
if (!data?.index || !data?.venues) {
|
|
||||||
throw new Error("Failed to render /utleie");
|
|
||||||
}
|
|
||||||
|
|
||||||
const index = data.index as VenueRentalIndexFragment;
|
|
||||||
const venues = (data?.venues ?? []) as VenueFragment[];
|
|
||||||
const bookableVenues = venues.filter((venue) => venue.showAsBookable);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<main className="site-main" id="main">
|
|
||||||
<PageHeader heading={index.title} lead={index.lead} />
|
|
||||||
{index.body && <PageContent blocks={index.body} />}
|
|
||||||
<VenueList venues={bookableVenues} heading="Våre lokaler" />
|
|
||||||
</main>
|
|
||||||
<BgPig type="key" />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
import { graphql } from "@/gql";
|
||||||
|
import { AssociationFragment, AssociationIndexFragment } from "@/gql/graphql";
|
||||||
|
import { getClient } from "@/app/client";
|
||||||
|
import { AssociationList } from "@/components/associations/AssociationList";
|
||||||
|
import { PageHeader } from "@/components/general/PageHeader";
|
||||||
|
import { PageContent } from "@/components/general/PageContent";
|
||||||
|
|
||||||
|
const AssociationIndexDefinition = graphql(`
|
||||||
|
fragment AssociationIndex on AssociationIndex {
|
||||||
|
__typename
|
||||||
|
title
|
||||||
|
seoTitle
|
||||||
|
searchDescription
|
||||||
|
lead
|
||||||
|
body {
|
||||||
|
...Blocks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const AssociationFragmentDefinition = graphql(`
|
||||||
|
fragment Association on AssociationPage {
|
||||||
|
__typename
|
||||||
|
id
|
||||||
|
slug
|
||||||
|
title
|
||||||
|
seoTitle
|
||||||
|
searchDescription
|
||||||
|
excerpt
|
||||||
|
lead
|
||||||
|
body {
|
||||||
|
...Blocks
|
||||||
|
}
|
||||||
|
logo {
|
||||||
|
url
|
||||||
|
width
|
||||||
|
height
|
||||||
|
}
|
||||||
|
associationType
|
||||||
|
websiteUrl
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const allAssociationsQuery = graphql(`
|
||||||
|
query allAssociations {
|
||||||
|
index: associationIndex {
|
||||||
|
... on AssociationIndex {
|
||||||
|
...AssociationIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
associations: pages(
|
||||||
|
contentType: "associations.AssociationPage"
|
||||||
|
limit: 1000
|
||||||
|
) {
|
||||||
|
... on AssociationPage {
|
||||||
|
...Association
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
export type AssociationIndexViewProps = {
|
||||||
|
index: AssociationIndexFragment;
|
||||||
|
associations: AssociationFragment[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function loadAssociationIndexProps(overrides?: {
|
||||||
|
indexOverride?: AssociationIndexFragment;
|
||||||
|
}): Promise<AssociationIndexViewProps> {
|
||||||
|
const { data, error } = await getClient().query(allAssociationsQuery, {});
|
||||||
|
if (error) throw new Error(error.message);
|
||||||
|
const index =
|
||||||
|
overrides?.indexOverride ?? (data?.index as AssociationIndexFragment | undefined);
|
||||||
|
if (!index) throw new Error("Failed to load /foreninger");
|
||||||
|
const associations = (data?.associations ?? []) as AssociationFragment[];
|
||||||
|
return { index, associations };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AssociationIndexView({
|
||||||
|
index,
|
||||||
|
associations,
|
||||||
|
}: AssociationIndexViewProps) {
|
||||||
|
return (
|
||||||
|
<main className="site-main" id="main">
|
||||||
|
<PageHeader heading={index.title} lead={index.lead} />
|
||||||
|
{index.body && <PageContent blocks={index.body} />}
|
||||||
|
<AssociationList
|
||||||
|
associations={associations}
|
||||||
|
heading="Foreninger og utvalg"
|
||||||
|
/>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { graphql } from "@/gql";
|
||||||
|
import { AssociationFragment } from "@/gql/graphql";
|
||||||
|
import { getClient } from "@/app/client";
|
||||||
|
import { AssociationHeader } from "@/components/associations/AssociationHeader";
|
||||||
|
import { PageContent } from "@/components/general/PageContent";
|
||||||
|
|
||||||
|
const associationBySlugQuery = graphql(`
|
||||||
|
query associationBySlug($slug: String!) {
|
||||||
|
association: page(
|
||||||
|
contentType: "associations.AssociationPage"
|
||||||
|
slug: $slug
|
||||||
|
) {
|
||||||
|
... on AssociationPage {
|
||||||
|
...Association
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
export type AssociationPageViewProps = { association: AssociationFragment };
|
||||||
|
|
||||||
|
export async function loadAssociationPageProps(args: {
|
||||||
|
slug?: string;
|
||||||
|
associationOverride?: AssociationFragment;
|
||||||
|
}): Promise<AssociationPageViewProps | null> {
|
||||||
|
if (args.associationOverride) {
|
||||||
|
return { association: args.associationOverride };
|
||||||
|
}
|
||||||
|
if (!args.slug) throw new Error("loadAssociationPageProps needs slug or associationOverride");
|
||||||
|
const { data, error } = await getClient().query(associationBySlugQuery, {
|
||||||
|
slug: args.slug,
|
||||||
|
});
|
||||||
|
if (error) throw new Error(error.message);
|
||||||
|
const association = data?.association as AssociationFragment | undefined;
|
||||||
|
if (!association) return null;
|
||||||
|
return { association };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AssociationPageView({ association }: AssociationPageViewProps) {
|
||||||
|
return (
|
||||||
|
<main className="site-main" id="main">
|
||||||
|
<AssociationHeader association={association} />
|
||||||
|
<PageContent blocks={association.body} />
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,15 +1,26 @@
|
|||||||
import { AccordionBlock as AccordionBlockType } from "@/gql/graphql";
|
import { graphql } from "@/gql";
|
||||||
|
import { type AccordionBlockFragment } from "@/gql/graphql";
|
||||||
import { Blocks } from "./Blocks";
|
import { Blocks } from "./Blocks";
|
||||||
import { Accordion } from "@/components/general/Accordion";
|
import { Accordion } from "@/components/general/Accordion";
|
||||||
|
|
||||||
|
const AccordionBlockFragmentDefinition = graphql(`
|
||||||
|
fragment AccordionBlock on AccordionBlock {
|
||||||
|
heading
|
||||||
|
body {
|
||||||
|
id
|
||||||
|
blockType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
export const AccordionBlock = ({
|
export const AccordionBlock = ({
|
||||||
block,
|
block,
|
||||||
}: {
|
}: {
|
||||||
block: AccordionBlockType;
|
block: AccordionBlockFragment;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Accordion heading={block.heading}>
|
<Accordion heading={block.heading}>
|
||||||
<Blocks blocks={block.body} />
|
<Blocks blocks={block.body} />
|
||||||
</Accordion>
|
</Accordion>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { FeaturedBlock } from "./FeaturedBlock";
|
|||||||
import { AccordionBlock } from "./AccordionBlock";
|
import { AccordionBlock } from "./AccordionBlock";
|
||||||
import { EmbedBlock } from "./EmbedBlock";
|
import { EmbedBlock } from "./EmbedBlock";
|
||||||
import { FactBoxBlock } from "./FactBoxBlock";
|
import { FactBoxBlock } from "./FactBoxBlock";
|
||||||
import { PhotoSphereBlock } from "./PhotoSphereBlock";
|
|
||||||
import { PageSectionBlock, PageSectionNavigationBlock } from "./PageSection";
|
import { PageSectionBlock, PageSectionNavigationBlock } from "./PageSection";
|
||||||
import { ContactSectionBlock, ContactSubsectionBlock } from "./ContactSection";
|
import { ContactSectionBlock, ContactSubsectionBlock } from "./ContactSection";
|
||||||
import { ContactListBlock } from "./ContactListBlock";
|
import { ContactListBlock } from "./ContactListBlock";
|
||||||
@@ -45,9 +44,6 @@ export const Blocks = ({ blocks, pageContent }: { blocks: any, pageContent?: boo
|
|||||||
case "FactBoxBlock":
|
case "FactBoxBlock":
|
||||||
return <FactBoxBlock key={block.id} block={block} />;
|
return <FactBoxBlock key={block.id} block={block} />;
|
||||||
break;
|
break;
|
||||||
case "PhotoSphereBlock":
|
|
||||||
return <PhotoSphereBlock key={block.id} block={block} />;
|
|
||||||
break;
|
|
||||||
case "PageSectionBlock":
|
case "PageSectionBlock":
|
||||||
return <PageSectionBlock key={block.id} block={block} />;
|
return <PageSectionBlock key={block.id} block={block} />;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -1,15 +1,33 @@
|
|||||||
import { ContactEntityBlock as ContactEntityBlockType } from "@/gql/graphql";
|
import { graphql, unmaskFragment } from "@/gql";
|
||||||
|
import { type ContactEntityBlockFragment } from "@/gql/graphql";
|
||||||
import styles from "./contactEntityBlock.module.scss";
|
import styles from "./contactEntityBlock.module.scss";
|
||||||
import { formatNorwegianPhoneNumber, formatPhoneE164 } from "@/lib/common";
|
import {
|
||||||
|
ContactEntityFragmentDefinition,
|
||||||
|
ImageFragmentDefinition,
|
||||||
|
formatNorwegianPhoneNumber,
|
||||||
|
formatPhoneE164,
|
||||||
|
} from "@/lib/common";
|
||||||
import { Icon } from "../general/Icon";
|
import { Icon } from "../general/Icon";
|
||||||
import { Image } from "../general/Image";
|
import { Image } from "../general/Image";
|
||||||
|
|
||||||
|
const ContactEntityBlockFragmentDefinition = graphql(`
|
||||||
|
fragment ContactEntityBlock on ContactEntityBlock {
|
||||||
|
contactEntity {
|
||||||
|
...ContactEntity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
export const ContactEntityBlock = ({
|
export const ContactEntityBlock = ({
|
||||||
block,
|
block,
|
||||||
}: {
|
}: {
|
||||||
block: ContactEntityBlockType;
|
block: ContactEntityBlockFragment;
|
||||||
}) => {
|
}) => {
|
||||||
const contact = block?.contactEntity;
|
const contact = unmaskFragment(
|
||||||
|
ContactEntityFragmentDefinition,
|
||||||
|
block?.contactEntity
|
||||||
|
);
|
||||||
|
const image = unmaskFragment(ImageFragmentDefinition, contact?.image);
|
||||||
|
|
||||||
if (!contact) {
|
if (!contact) {
|
||||||
return <></>;
|
return <></>;
|
||||||
@@ -21,18 +39,18 @@ export const ContactEntityBlock = ({
|
|||||||
return (
|
return (
|
||||||
<li className={styles.contactItem}>
|
<li className={styles.contactItem}>
|
||||||
<div className={styles.image}>
|
<div className={styles.image}>
|
||||||
{!contact.image && (
|
{!image && (
|
||||||
<img
|
<img
|
||||||
src="/assets/graphics/portrait-pig.svg"
|
src="/assets/graphics/portrait-pig.svg"
|
||||||
className={styles.portraitPlaceholder}
|
className={styles.portraitPlaceholder}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{contact.image && (
|
{image && (
|
||||||
<Image
|
<Image
|
||||||
src={contact.image.url}
|
src={image.url}
|
||||||
alt={contact.image.alt ?? ""}
|
alt={image.alt ?? ""}
|
||||||
width={contact.image.width}
|
width={image.width}
|
||||||
height={contact.image.height}
|
height={image.height}
|
||||||
sizes="25vw"
|
sizes="25vw"
|
||||||
className={styles.portrait}
|
className={styles.portrait}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,11 +1,25 @@
|
|||||||
import { ContactListBlock as ContactListBlockType } from "@/gql/graphql";
|
import { graphql } from "@/gql";
|
||||||
|
import { type ContactListBlockFragment } from "@/gql/graphql";
|
||||||
import styles from "./contactListBlock.module.scss";
|
import styles from "./contactListBlock.module.scss";
|
||||||
import { Blocks } from "./Blocks";
|
import { Blocks } from "./Blocks";
|
||||||
|
|
||||||
|
const ContactListBlockFragmentDefinition = graphql(`
|
||||||
|
fragment ContactListBlock on ContactListBlock {
|
||||||
|
items {
|
||||||
|
blockType
|
||||||
|
... on ContactEntityBlock {
|
||||||
|
contactEntity {
|
||||||
|
...ContactEntity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
export const ContactListBlock = ({
|
export const ContactListBlock = ({
|
||||||
block,
|
block,
|
||||||
}: {
|
}: {
|
||||||
block: ContactListBlockType;
|
block: ContactListBlockFragment;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<ul className={styles.contactList}>
|
<ul className={styles.contactList}>
|
||||||
|
|||||||
@@ -1,11 +1,37 @@
|
|||||||
import { ContactSectionBlock as ContactSectionBlockType } from "@/gql/graphql";
|
import { graphql } from "@/gql";
|
||||||
|
import {
|
||||||
|
type ContactSectionBlockFragment,
|
||||||
|
type ContactSubsectionBlockFragment,
|
||||||
|
} from "@/gql/graphql";
|
||||||
import styles from "./contactSection.module.scss";
|
import styles from "./contactSection.module.scss";
|
||||||
import { Blocks } from "./Blocks";
|
import { Blocks } from "./Blocks";
|
||||||
|
|
||||||
|
const ContactSectionBlockFragmentDefinition = graphql(`
|
||||||
|
fragment ContactSectionBlock on ContactSectionBlock {
|
||||||
|
title
|
||||||
|
text
|
||||||
|
blocks {
|
||||||
|
id
|
||||||
|
blockType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const ContactSubsectionBlockFragmentDefinition = graphql(`
|
||||||
|
fragment ContactSubsectionBlock on ContactSubsectionBlock {
|
||||||
|
title
|
||||||
|
text
|
||||||
|
blocks {
|
||||||
|
id
|
||||||
|
blockType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
export const ContactSectionBlock = ({
|
export const ContactSectionBlock = ({
|
||||||
block,
|
block,
|
||||||
}: {
|
}: {
|
||||||
block: ContactSectionBlockType;
|
block: ContactSectionBlockFragment;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<section className={styles.contactSection}>
|
<section className={styles.contactSection}>
|
||||||
@@ -24,7 +50,7 @@ export const ContactSectionBlock = ({
|
|||||||
export const ContactSubsectionBlock = ({
|
export const ContactSubsectionBlock = ({
|
||||||
block,
|
block,
|
||||||
}: {
|
}: {
|
||||||
block: ContactSectionBlockType;
|
block: ContactSubsectionBlockFragment;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<section className={styles.contactSubsection}>
|
<section className={styles.contactSubsection}>
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
import { EmbedBlock as EmbedBlockType } from "@/gql/graphql";
|
import { graphql } from "@/gql";
|
||||||
|
import { type EmbedBlockFragment } from "@/gql/graphql";
|
||||||
import styles from "./embedBlock.module.scss";
|
import styles from "./embedBlock.module.scss";
|
||||||
|
|
||||||
export const EmbedBlock = ({ block }: { block: EmbedBlockType }) => {
|
const EmbedBlockFragmentDefinition = graphql(`
|
||||||
|
fragment EmbedBlock on EmbedBlock {
|
||||||
|
url
|
||||||
|
embed
|
||||||
|
rawEmbed
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
export const EmbedBlock = ({ block }: { block: EmbedBlockFragment }) => {
|
||||||
if (!block.embed) {
|
if (!block.embed) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
@@ -18,7 +27,7 @@ export const EmbedBlock = ({ block }: { block: EmbedBlockType }) => {
|
|||||||
*/
|
*/
|
||||||
let embedData: any = {};
|
let embedData: any = {};
|
||||||
try {
|
try {
|
||||||
embedData = JSON.parse(block.rawEmbed);
|
embedData = block.rawEmbed ? JSON.parse(block.rawEmbed) : {};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
embedData = {};
|
embedData = {};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
import { FactBoxBlock as FactBoxBlockType } from "@/gql/graphql";
|
import { graphql } from "@/gql";
|
||||||
|
import { type FactBoxBlockFragment } from "@/gql/graphql";
|
||||||
import styles from "./factBoxBlock.module.scss";
|
import styles from "./factBoxBlock.module.scss";
|
||||||
|
|
||||||
type FactBoxBlockTypeWithAlias = FactBoxBlockType & {
|
const FactBoxBlockFragmentDefinition = graphql(`
|
||||||
factBoxBody?: string;
|
fragment FactBoxBlock on FactBoxBlock {
|
||||||
};
|
backgroundColor
|
||||||
|
factBoxBody: body
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
export const FactBoxBlock = ({
|
export const FactBoxBlock = ({
|
||||||
block,
|
block,
|
||||||
}: {
|
}: {
|
||||||
block: FactBoxBlockTypeWithAlias;
|
block: FactBoxBlockFragment;
|
||||||
}) => {
|
}) => {
|
||||||
if (!block.factBoxBody) {
|
if (!block.factBoxBody) {
|
||||||
return <></>;
|
return <></>;
|
||||||
|
|||||||
@@ -1,22 +1,47 @@
|
|||||||
import { FeaturedBlock as FeaturedBlockType } from "@/gql/graphql";
|
import { graphql, unmaskFragment } from "@/gql";
|
||||||
|
import { type FeaturedBlockFragment } from "@/gql/graphql";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Image } from "@/components/general/Image";
|
import { Image } from "@/components/general/Image";
|
||||||
|
import { ImageFragmentDefinition } from "@/lib/common";
|
||||||
import styles from "./featuredBlock.module.scss";
|
import styles from "./featuredBlock.module.scss";
|
||||||
|
|
||||||
// the 'text' field has been aliased to 'featuredBlockText' and i'm
|
const FeaturedBlockFragmentDefinition = graphql(`
|
||||||
// using codegen the wrong way. let's specify the field here and move on
|
fragment FeaturedBlock on FeaturedBlock {
|
||||||
type FeaturedBlockTypeWithAlias = FeaturedBlockType & {
|
title
|
||||||
featuredBlockText: string;
|
featuredBlockText: text
|
||||||
};
|
linkText
|
||||||
|
imagePosition
|
||||||
|
backgroundColor
|
||||||
|
featuredPage {
|
||||||
|
contentType
|
||||||
|
pageType
|
||||||
|
url
|
||||||
|
... on EventPage {
|
||||||
|
featuredImage {
|
||||||
|
...Image
|
||||||
|
}
|
||||||
|
}
|
||||||
|
... on NewsPage {
|
||||||
|
featuredImage {
|
||||||
|
...Image
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
featuredImageOverride {
|
||||||
|
...Image
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
export const FeaturedBlock = ({
|
export const FeaturedBlock = ({
|
||||||
block,
|
block,
|
||||||
}: {
|
}: {
|
||||||
block: FeaturedBlockTypeWithAlias;
|
block: FeaturedBlockFragment;
|
||||||
}) => {
|
}) => {
|
||||||
const image = !!block.featuredImageOverride
|
const image = unmaskFragment(
|
||||||
? block.featuredImageOverride
|
ImageFragmentDefinition,
|
||||||
: null;
|
block.featuredImageOverride
|
||||||
|
);
|
||||||
// TODO: fetch image from target page
|
// TODO: fetch image from target page
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
import { HorizontalRuleBlock as HorizontalRuleBlockType } from "@/gql/graphql";
|
import { graphql } from "@/gql";
|
||||||
|
import { type HorizontalRuleBlockFragment } from "@/gql/graphql";
|
||||||
import { Image } from "@/components/general/Image";
|
import { Image } from "@/components/general/Image";
|
||||||
import styles from "./horizontalRuleBlock.module.scss";
|
import styles from "./horizontalRuleBlock.module.scss";
|
||||||
|
|
||||||
|
const HorizontalRuleBlockFragmentDefinition = graphql(`
|
||||||
|
fragment HorizontalRuleBlock on HorizontalRuleBlock {
|
||||||
|
color
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
export const HorizontalRuleBlock = ({
|
export const HorizontalRuleBlock = ({
|
||||||
block,
|
block,
|
||||||
}: {
|
}: {
|
||||||
block: HorizontalRuleBlockType;
|
block: HorizontalRuleBlockFragment;
|
||||||
}) => {
|
}) => {
|
||||||
const knownColors = [
|
const knownColors = [
|
||||||
"deepBrick",
|
"deepBrick",
|
||||||
|
|||||||
@@ -1,8 +1,30 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { ImageSliderBlock as ImageSliderBlockType } from "@/gql/graphql";
|
import { graphql, unmaskFragment, type FragmentType } from "@/gql";
|
||||||
|
import { type ImageSliderBlockFragment } from "@/gql/graphql";
|
||||||
import { ImageFigure } from "@/components/general/Image";
|
import { ImageFigure } from "@/components/general/Image";
|
||||||
|
import { ImageFragmentDefinition } from "@/lib/common";
|
||||||
import styles from "./imageSliderBlock.module.scss";
|
import styles from "./imageSliderBlock.module.scss";
|
||||||
|
|
||||||
|
const ImageSliderItemFragmentDefinition = graphql(`
|
||||||
|
fragment ImageSliderItem on ImageSliderItemBlock {
|
||||||
|
image {
|
||||||
|
...Image
|
||||||
|
}
|
||||||
|
text
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
export const ImageSliderBlockFragmentDefinition = graphql(`
|
||||||
|
fragment ImageSliderBlock on ImageSliderBlock {
|
||||||
|
images {
|
||||||
|
__typename
|
||||||
|
... on ImageSliderItemBlock {
|
||||||
|
...ImageSliderItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
// import swiper modules & styles
|
// import swiper modules & styles
|
||||||
import { Swiper, SwiperSlide } from "swiper/react";
|
import { Swiper, SwiperSlide } from "swiper/react";
|
||||||
import { Pagination, Navigation } from "swiper/modules";
|
import { Pagination, Navigation } from "swiper/modules";
|
||||||
@@ -11,17 +33,42 @@ import "swiper/css/pagination";
|
|||||||
import "swiper/css/navigation";
|
import "swiper/css/navigation";
|
||||||
import "./swiper.scss";
|
import "./swiper.scss";
|
||||||
|
|
||||||
|
const Slide = ({
|
||||||
|
item: maskedItem,
|
||||||
|
}: {
|
||||||
|
item: FragmentType<typeof ImageSliderItemFragmentDefinition>;
|
||||||
|
}) => {
|
||||||
|
const item = unmaskFragment(ImageSliderItemFragmentDefinition, maskedItem);
|
||||||
|
const image = unmaskFragment(ImageFragmentDefinition, item.image);
|
||||||
|
return (
|
||||||
|
<ImageFigure
|
||||||
|
key={image.id}
|
||||||
|
src={image.url}
|
||||||
|
alt={image.alt ?? ""}
|
||||||
|
width={image.width}
|
||||||
|
height={image.height}
|
||||||
|
attribution={image.attribution}
|
||||||
|
caption={item.text}
|
||||||
|
sizes="100vw"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const ImageSliderBlock = ({
|
export const ImageSliderBlock = ({
|
||||||
block,
|
block,
|
||||||
hero,
|
hero,
|
||||||
pageContent
|
pageContent,
|
||||||
}: {
|
}: {
|
||||||
block: ImageSliderBlockType | any;
|
block: ImageSliderBlockFragment;
|
||||||
hero?: boolean;
|
hero?: boolean;
|
||||||
pageContent?: boolean;
|
pageContent?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className={styles.imageSliderBlock} data-hero={hero} data-pagecontent={pageContent}>
|
<div
|
||||||
|
className={styles.imageSliderBlock}
|
||||||
|
data-hero={hero}
|
||||||
|
data-pagecontent={pageContent}
|
||||||
|
>
|
||||||
<Swiper
|
<Swiper
|
||||||
pagination={{
|
pagination={{
|
||||||
type: "fraction",
|
type: "fraction",
|
||||||
@@ -30,21 +77,16 @@ export const ImageSliderBlock = ({
|
|||||||
modules={[Pagination, Navigation]}
|
modules={[Pagination, Navigation]}
|
||||||
className="mySwiper"
|
className="mySwiper"
|
||||||
>
|
>
|
||||||
{block.images &&
|
{block.images?.map((item, index) => {
|
||||||
block.images.map((imageItem: any, index: number) => (
|
if (item?.__typename !== "ImageSliderItemBlock") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
<SwiperSlide key={index}>
|
<SwiperSlide key={index}>
|
||||||
<ImageFigure
|
<Slide item={item} />
|
||||||
key={imageItem.image.id}
|
|
||||||
src={imageItem.image.url}
|
|
||||||
alt={imageItem.image.alt ?? ""}
|
|
||||||
width={imageItem.image.width}
|
|
||||||
height={imageItem.image.height}
|
|
||||||
attribution={imageItem.image.attribution}
|
|
||||||
caption={imageItem.text}
|
|
||||||
sizes="100vw"
|
|
||||||
/>
|
|
||||||
</SwiperSlide>
|
</SwiperSlide>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</Swiper>
|
</Swiper>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,18 +1,31 @@
|
|||||||
import { ImageWithTextBlock as ImageWithTextBlockType } from "@/gql/graphql";
|
import { graphql, unmaskFragment } from "@/gql";
|
||||||
|
import { type ImageWithTextBlockFragment } from "@/gql/graphql";
|
||||||
import { ImageFigure } from "@/components/general/Image";
|
import { ImageFigure } from "@/components/general/Image";
|
||||||
|
import { ImageFragmentDefinition } from "@/lib/common";
|
||||||
|
|
||||||
|
const ImageWithTextBlockFragmentDefinition = graphql(`
|
||||||
|
fragment ImageWithTextBlock on ImageWithTextBlock {
|
||||||
|
image {
|
||||||
|
...Image
|
||||||
|
}
|
||||||
|
imageFormat
|
||||||
|
text
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
export function ImageWithTextBlock({
|
export function ImageWithTextBlock({
|
||||||
block,
|
block,
|
||||||
}: {
|
}: {
|
||||||
block: ImageWithTextBlockType;
|
block: ImageWithTextBlockFragment;
|
||||||
}) {
|
}) {
|
||||||
|
const image = unmaskFragment(ImageFragmentDefinition, block.image);
|
||||||
return (
|
return (
|
||||||
<ImageFigure
|
<ImageFigure
|
||||||
src={block.image.url}
|
src={image.url}
|
||||||
alt={block.image.alt ?? ""}
|
alt={image.alt ?? ""}
|
||||||
width={block.image.width}
|
width={image.width}
|
||||||
height={block.image.height}
|
height={image.height}
|
||||||
attribution={block.image.attribution}
|
attribution={image.attribution}
|
||||||
caption={block.text}
|
caption={block.text}
|
||||||
imageFormat={block.imageFormat}
|
imageFormat={block.imageFormat}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,13 +1,26 @@
|
|||||||
import { PageSectionBlock as PageSectionBlockType } from "@/gql/graphql";
|
import { graphql } from "@/gql";
|
||||||
|
import { type PageSectionBlockFragment } from "@/gql/graphql";
|
||||||
import styles from "./pageSection.module.scss";
|
import styles from "./pageSection.module.scss";
|
||||||
import { Blocks } from "./Blocks";
|
import { Blocks } from "./Blocks";
|
||||||
import slugify from "@sindresorhus/slugify";
|
import slugify from "@sindresorhus/slugify";
|
||||||
import { DecorativeIcon } from "../general/Icon";
|
import { DecorativeIcon } from "../general/Icon";
|
||||||
|
|
||||||
|
const PageSectionBlockFragmentDefinition = graphql(`
|
||||||
|
fragment PageSectionBlock on PageSectionBlock {
|
||||||
|
title
|
||||||
|
backgroundColor
|
||||||
|
icon
|
||||||
|
body {
|
||||||
|
id
|
||||||
|
blockType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
export const PageSectionBlock = ({
|
export const PageSectionBlock = ({
|
||||||
block,
|
block,
|
||||||
}: {
|
}: {
|
||||||
block: PageSectionBlockType;
|
block: PageSectionBlockFragment;
|
||||||
}) => {
|
}) => {
|
||||||
const anchor = slugify(block.title);
|
const anchor = slugify(block.title);
|
||||||
|
|
||||||
@@ -31,7 +44,7 @@ export const PageSectionBlock = ({
|
|||||||
export const PageSectionNavigationBlock = ({
|
export const PageSectionNavigationBlock = ({
|
||||||
sections,
|
sections,
|
||||||
}: {
|
}: {
|
||||||
sections: PageSectionBlockType[];
|
sections: PageSectionBlockFragment[];
|
||||||
}) => {
|
}) => {
|
||||||
if (!sections.length) {
|
if (!sections.length) {
|
||||||
return <></>;
|
return <></>;
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import dynamic from "next/dynamic";
|
|
||||||
import { PhotoSphereBlock as PhotoSphereBlockType } from "@/gql/graphql";
|
|
||||||
import styles from "./photoSphereBlock.module.scss";
|
|
||||||
|
|
||||||
const ReactPhotoSphereViewer = dynamic(
|
|
||||||
() =>
|
|
||||||
import("react-photo-sphere-viewer").then(
|
|
||||||
(mod) => mod.ReactPhotoSphereViewer
|
|
||||||
),
|
|
||||||
{
|
|
||||||
ssr: false,
|
|
||||||
loading: () => (
|
|
||||||
<div className={styles.loading} aria-busy="true" aria-label="Laster 360°-bilde">
|
|
||||||
<span className={styles.loadingText}>Laster 360°-bilde…</span>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
type PhotoSphereBlockTypeWithAlias = PhotoSphereBlockType & {
|
|
||||||
photoSphereImage?: PhotoSphereBlockType["image"];
|
|
||||||
photoSphereTitle?: string | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const PhotoSphereBlock = ({
|
|
||||||
block,
|
|
||||||
}: {
|
|
||||||
block: PhotoSphereBlockTypeWithAlias;
|
|
||||||
}) => {
|
|
||||||
const image = block.photoSphereImage ?? block.image;
|
|
||||||
|
|
||||||
if (!image?.url) {
|
|
||||||
return <></>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<figure className={styles.photoSphereWrapper}>
|
|
||||||
<div
|
|
||||||
className={styles.photoSphereViewer}
|
|
||||||
role="img"
|
|
||||||
aria-label={block.photoSphereTitle ?? image.alt ?? "360°-bilde"}
|
|
||||||
>
|
|
||||||
<ReactPhotoSphereViewer
|
|
||||||
src={image.url}
|
|
||||||
height="500px"
|
|
||||||
width="100%"
|
|
||||||
navbar={["zoom", "fullscreen"]}
|
|
||||||
littlePlanet={false}
|
|
||||||
touchmoveTwoFingers
|
|
||||||
/>
|
|
||||||
<noscript>
|
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
||||||
<img
|
|
||||||
src={image.url}
|
|
||||||
alt={block.photoSphereTitle ?? image.alt ?? "360°-bilde"}
|
|
||||||
className={styles.fallbackImage}
|
|
||||||
/>
|
|
||||||
</noscript>
|
|
||||||
</div>
|
|
||||||
{block.photoSphereTitle && <figcaption>{block.photoSphereTitle}</figcaption>}
|
|
||||||
</figure>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,6 +1,15 @@
|
|||||||
|
import { graphql } from "@/gql";
|
||||||
|
import { type RichTextBlockFragment } from "@/gql/graphql";
|
||||||
import styles from "./richTextBlock.module.scss";
|
import styles from "./richTextBlock.module.scss";
|
||||||
|
|
||||||
export const RichTextBlock = ({ block }: any) => {
|
const RichTextBlockFragmentDefinition = graphql(`
|
||||||
|
fragment RichTextBlock on RichTextBlock {
|
||||||
|
rawValue
|
||||||
|
value
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
export const RichTextBlock = ({ block }: { block: RichTextBlockFragment }) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={styles.richTextBlock}
|
className={styles.richTextBlock}
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
.photoSphereWrapper {
|
|
||||||
max-width: var(--size-width-p);
|
|
||||||
margin: 0 auto var(--spacing-m);
|
|
||||||
}
|
|
||||||
|
|
||||||
.photoSphereViewer {
|
|
||||||
width: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
border-radius: 2px;
|
|
||||||
background: var(--color-betongGray);
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
height: 500px;
|
|
||||||
width: 100%;
|
|
||||||
background: var(--color-background-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.loadingText {
|
|
||||||
font-size: var(--font-size-body);
|
|
||||||
color: var(--color-text-secondary, currentColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
.photoSphereWrapper figcaption {
|
|
||||||
width: 100%;
|
|
||||||
max-width: var(--size-width-p);
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: var(--spacing-xs) 0 var(--spacing-s);
|
|
||||||
font-size: var(--font-size-caption);
|
|
||||||
line-height: 1.4;
|
|
||||||
opacity: .8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fallbackImage {
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { graphql } from "@/gql";
|
||||||
|
import { ContactIndexFragment } from "@/gql/graphql";
|
||||||
|
import { getClient } from "@/app/client";
|
||||||
|
import { GeneralContactBlock } from "@/components/blocks/GeneralContactBlock";
|
||||||
|
import { PageContent } from "@/components/general/PageContent";
|
||||||
|
import { PageHeader } from "@/components/general/PageHeader";
|
||||||
|
|
||||||
|
const ContactIndexDefinition = graphql(`
|
||||||
|
fragment ContactIndex on ContactIndex {
|
||||||
|
__typename
|
||||||
|
title
|
||||||
|
seoTitle
|
||||||
|
searchDescription
|
||||||
|
lead
|
||||||
|
body {
|
||||||
|
...Blocks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const contactQuery = graphql(`
|
||||||
|
query contacts {
|
||||||
|
index: contactIndex {
|
||||||
|
... on ContactIndex {
|
||||||
|
...ContactIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
export type ContactIndexViewProps = { index: ContactIndexFragment };
|
||||||
|
|
||||||
|
export async function loadContactIndexProps(overrides?: {
|
||||||
|
indexOverride?: ContactIndexFragment;
|
||||||
|
}): Promise<ContactIndexViewProps> {
|
||||||
|
if (overrides?.indexOverride) {
|
||||||
|
return { index: overrides.indexOverride };
|
||||||
|
}
|
||||||
|
const { data, error } = await getClient().query(contactQuery, {});
|
||||||
|
if (error) throw new Error(error.message);
|
||||||
|
const index = data?.index as ContactIndexFragment | undefined;
|
||||||
|
if (!index) throw new Error("Failed to load /kontakt");
|
||||||
|
return { index };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ContactIndexView({ index }: ContactIndexViewProps) {
|
||||||
|
return (
|
||||||
|
<main className="site-main" id="main">
|
||||||
|
<PageHeader heading={index.title} lead={index.lead} />
|
||||||
|
<GeneralContactBlock />
|
||||||
|
{index.body && <PageContent blocks={index.body} />}
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -9,7 +9,10 @@ import {
|
|||||||
} from "nuqs";
|
} from "nuqs";
|
||||||
import { EventItem } from "./EventItem";
|
import { EventItem } from "./EventItem";
|
||||||
import { EventFilter, EventFilterExplained } from "./EventFilter";
|
import { EventFilter, EventFilterExplained } from "./EventFilter";
|
||||||
|
import { unmaskFragment } from "@/gql";
|
||||||
import {
|
import {
|
||||||
|
EventCategoryFragmentDefinition,
|
||||||
|
EventOrganizerFragmentDefinition,
|
||||||
EventFragment,
|
EventFragment,
|
||||||
EventCategory,
|
EventCategory,
|
||||||
SingularEvent,
|
SingularEvent,
|
||||||
@@ -48,11 +51,11 @@ export const EventContainer = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const [mode, setMode] = useQueryState(
|
const [mode, setMode] = useQueryState(
|
||||||
"mode",
|
"mode",
|
||||||
parseAsStringLiteral(["list", "calendar"]).withDefault("list")
|
parseAsStringLiteral(["list", "calendar"]).withDefault("list"),
|
||||||
);
|
);
|
||||||
const [categories, setCategories] = useQueryState(
|
const [categories, setCategories] = useQueryState(
|
||||||
"category",
|
"category",
|
||||||
parseAsArrayOf(parseAsString, ",")
|
parseAsArrayOf(parseAsString, ","),
|
||||||
);
|
);
|
||||||
const [organizer, setOrganizer] = useQueryState("organizer", parseAsString);
|
const [organizer, setOrganizer] = useQueryState("organizer", parseAsString);
|
||||||
const [venue, setVenue] = useQueryState("venue", parseAsString);
|
const [venue, setVenue] = useQueryState("venue", parseAsString);
|
||||||
@@ -75,13 +78,15 @@ export const EventContainer = ({
|
|||||||
Filtering on an organizer with no upcoming events will work,
|
Filtering on an organizer with no upcoming events will work,
|
||||||
and in that case it's included in the dropdown
|
and in that case it's included in the dropdown
|
||||||
*/
|
*/
|
||||||
|
const allOrganizers = unmaskFragment(
|
||||||
|
EventOrganizerFragmentDefinition,
|
||||||
|
events.flatMap((x) => x.organizers),
|
||||||
|
);
|
||||||
const uniqueOrganizers: string[] = unique(
|
const uniqueOrganizers: string[] = unique(
|
||||||
events
|
allOrganizers
|
||||||
.map((x) => x.organizers)
|
|
||||||
.flat()
|
|
||||||
.filter((x) => x.__typename === "EventOrganizer")
|
.filter((x) => x.__typename === "EventOrganizer")
|
||||||
.map((x) => x.slug)
|
.map((x) => x.slug)
|
||||||
.filter((x) => typeof x === "string" && x !== "")
|
.filter((x) => typeof x === "string" && x !== ""),
|
||||||
);
|
);
|
||||||
const filterableOrganizers = uniqueOrganizers
|
const filterableOrganizers = uniqueOrganizers
|
||||||
.map((slug) => eventOrganizers.find((haystack) => haystack.slug === slug))
|
.map((slug) => eventOrganizers.find((haystack) => haystack.slug === slug))
|
||||||
@@ -118,11 +123,11 @@ export const EventContainer = ({
|
|||||||
.flat()
|
.flat()
|
||||||
.filter((x) => x.venue?.__typename === "VenuePage")
|
.filter((x) => x.venue?.__typename === "VenuePage")
|
||||||
.map((x) => x.venue?.slug)
|
.map((x) => x.venue?.slug)
|
||||||
.filter((x) => typeof x === "string")
|
.filter((x) => typeof x === "string"),
|
||||||
);
|
);
|
||||||
const filterableVenues = venues
|
const filterableVenues = venues
|
||||||
.filter(
|
.filter(
|
||||||
(x) => venueSlugsWithUpcomingEvents.includes(x.slug) || x.slug === venue
|
(x) => venueSlugsWithUpcomingEvents.includes(x.slug) || x.slug === venue,
|
||||||
)
|
)
|
||||||
.map((x) => venues.find((haystack) => haystack.slug === x.slug))
|
.map((x) => venues.find((haystack) => haystack.slug === x.slug))
|
||||||
.filter((x) => x !== undefined) as VenueFragment[];
|
.filter((x) => x !== undefined) as VenueFragment[];
|
||||||
@@ -134,27 +139,32 @@ export const EventContainer = ({
|
|||||||
}
|
}
|
||||||
}, [venues, venue]);
|
}, [venues, venue]);
|
||||||
|
|
||||||
const filteredEvents = events
|
const filteredEvents = events.filter((event) => {
|
||||||
.filter(
|
if (organizer) {
|
||||||
(x) =>
|
const organizers = unmaskFragment(
|
||||||
!organizer ||
|
EventOrganizerFragmentDefinition,
|
||||||
x.organizers.map((organizer) => organizer.slug).includes(organizer)
|
event.organizers,
|
||||||
)
|
);
|
||||||
.filter(
|
if (!organizers.some((o) => o.slug === organizer)) {
|
||||||
(x) =>
|
return false;
|
||||||
!categories ||
|
}
|
||||||
x.categories
|
}
|
||||||
.map((eventCategory) => eventCategory.slug)
|
if (categories) {
|
||||||
.filter((x) => categories.includes(x)).length !== 0
|
const eventCategories = unmaskFragment(
|
||||||
)
|
EventCategoryFragmentDefinition,
|
||||||
.filter(
|
event.categories,
|
||||||
(x) =>
|
);
|
||||||
!venue ||
|
if (!eventCategories.some((c) => categories.includes(c.slug))) {
|
||||||
x.occurrences
|
return false;
|
||||||
.map((occurrence) => occurrence.venue?.slug)
|
}
|
||||||
.filter((x) => typeof x === "string")
|
}
|
||||||
.includes(venue)
|
if (venue) {
|
||||||
);
|
if (!event.occurrences.some((occ) => occ.venue?.slug === venue)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
const [showFilter, setShowFilter] = useState(false);
|
const [showFilter, setShowFilter] = useState(false);
|
||||||
function toggleFilter() {
|
function toggleFilter() {
|
||||||
@@ -302,12 +312,12 @@ function maybeYear(yearMonthString: string) {
|
|||||||
|
|
||||||
const EventCalendar = ({ events }: { events: EventFragment[] }) => {
|
const EventCalendar = ({ events }: { events: EventFragment[] }) => {
|
||||||
const futureSingularEvents = getSingularEvents(events).filter(
|
const futureSingularEvents = getSingularEvents(events).filter(
|
||||||
(x) => x.occurrence?.start && isTodayOrFuture(x.occurrence.start)
|
(x) => x.occurrence?.start && isTodayOrFuture(x.occurrence.start),
|
||||||
);
|
);
|
||||||
const eventsByDate = organizeEventsInCalendar(futureSingularEvents);
|
const eventsByDate = organizeEventsInCalendar(futureSingularEvents);
|
||||||
const yearMonths = Object.keys(eventsByDate);
|
const yearMonths = Object.keys(eventsByDate);
|
||||||
const [visibleYearMonths, setVisibleYearMonths] = useState(
|
const [visibleYearMonths, setVisibleYearMonths] = useState(
|
||||||
yearMonths.slice(0, 2)
|
yearMonths.slice(0, 2),
|
||||||
);
|
);
|
||||||
|
|
||||||
const toggleYearMonth = (yearMonth: string) => {
|
const toggleYearMonth = (yearMonth: string) => {
|
||||||
@@ -327,9 +337,9 @@ const EventCalendar = ({ events }: { events: EventFragment[] }) => {
|
|||||||
yearMonthSum +
|
yearMonthSum +
|
||||||
Object.values(week).reduce(
|
Object.values(week).reduce(
|
||||||
(weekSum, day) => weekSum + day.length,
|
(weekSum, day) => weekSum + day.length,
|
||||||
0
|
0,
|
||||||
),
|
),
|
||||||
0
|
0,
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,19 +1,30 @@
|
|||||||
import { EventFragment } from "@/lib/event";
|
import { unmaskFragment } from "@/gql";
|
||||||
|
import {
|
||||||
|
EventCategoryFragmentDefinition,
|
||||||
|
EventFragment,
|
||||||
|
} from "@/lib/event";
|
||||||
|
import { ImageFragmentDefinition } from "@/lib/common";
|
||||||
import styles from "./eventHeader.module.scss";
|
import styles from "./eventHeader.module.scss";
|
||||||
import { ImageFigure } from "@/components/general/Image";
|
import { ImageFigure } from "@/components/general/Image";
|
||||||
import { Breadcrumb } from "../general/Breadcrumb";
|
|
||||||
import { Icon } from "../general/Icon";
|
import { Icon } from "../general/Icon";
|
||||||
|
|
||||||
export const EventHeader = ({ event }: { event: EventFragment }) => {
|
export const EventHeader = ({ event }: { event: EventFragment }) => {
|
||||||
const featuredImage: any = event.featuredImage;
|
const featuredImage = unmaskFragment(
|
||||||
|
ImageFragmentDefinition,
|
||||||
|
event.featuredImage
|
||||||
|
);
|
||||||
|
const categories = unmaskFragment(
|
||||||
|
EventCategoryFragmentDefinition,
|
||||||
|
event.categories
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.eventHeader}>
|
<div className={styles.eventHeader}>
|
||||||
<div className={styles.heading}>
|
<div className={styles.heading}>
|
||||||
{/*<Breadcrumb link="/arrangementer" text="Arrangement" />*/}
|
{/*<Breadcrumb link="/arrangementer" text="Arrangement" />*/}
|
||||||
{event.categories.length > 0 && (
|
{categories.length > 0 && (
|
||||||
<div className={styles.categories}>
|
<div className={styles.categories}>
|
||||||
{event.categories.map((category) => (
|
{categories.map((category) => (
|
||||||
<div key={category.name} className="tag">
|
<div key={category.name} className="tag">
|
||||||
{category.name}
|
{category.name}
|
||||||
</div>
|
</div>
|
||||||
@@ -33,7 +44,7 @@ export const EventHeader = ({ event }: { event: EventFragment }) => {
|
|||||||
{featuredImage && (
|
{featuredImage && (
|
||||||
<ImageFigure
|
<ImageFigure
|
||||||
src={featuredImage.url}
|
src={featuredImage.url}
|
||||||
alt={featuredImage.alt}
|
alt={featuredImage.alt ?? ""}
|
||||||
width={featuredImage.width}
|
width={featuredImage.width}
|
||||||
height={featuredImage.height}
|
height={featuredImage.height}
|
||||||
attribution={featuredImage.attribution}
|
attribution={featuredImage.attribution}
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { Suspense } from "react";
|
||||||
|
import { VenueFragment } from "@/gql/graphql";
|
||||||
|
import { getClient } from "@/app/client";
|
||||||
|
import { EventContainer } from "@/components/events/EventContainer";
|
||||||
|
import { PageHeader } from "@/components/general/PageHeader";
|
||||||
|
import {
|
||||||
|
EventCategory,
|
||||||
|
EventFragment,
|
||||||
|
EventOrganizer,
|
||||||
|
eventsOverviewQuery,
|
||||||
|
} from "@/lib/event";
|
||||||
|
|
||||||
|
export type EventIndexViewProps = {
|
||||||
|
events: EventFragment[];
|
||||||
|
eventCategories: EventCategory[];
|
||||||
|
eventOrganizers: EventOrganizer[];
|
||||||
|
venues: VenueFragment[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function loadEventIndexProps(): Promise<EventIndexViewProps> {
|
||||||
|
const { data, error } = await getClient().query(eventsOverviewQuery, {});
|
||||||
|
if (error) throw new Error(error.message);
|
||||||
|
if (
|
||||||
|
!data?.index ||
|
||||||
|
!data?.events?.futureEvents ||
|
||||||
|
!data?.eventCategories ||
|
||||||
|
!data?.eventOrganizers ||
|
||||||
|
!data?.venues
|
||||||
|
) {
|
||||||
|
throw new Error("Failed to load /arrangementer");
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
events: data.events.futureEvents as EventFragment[],
|
||||||
|
eventCategories: data.eventCategories as EventCategory[],
|
||||||
|
eventOrganizers: data.eventOrganizers as EventOrganizer[],
|
||||||
|
venues: data.venues as VenueFragment[],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function EventIndexView({
|
||||||
|
events,
|
||||||
|
eventCategories,
|
||||||
|
eventOrganizers,
|
||||||
|
venues,
|
||||||
|
}: EventIndexViewProps) {
|
||||||
|
return (
|
||||||
|
<main className="site-main" id="main">
|
||||||
|
<PageHeader heading="Dette skjer på Chateau Neuf" align="left" />
|
||||||
|
<Suspense>
|
||||||
|
<EventContainer
|
||||||
|
events={events}
|
||||||
|
eventCategories={eventCategories}
|
||||||
|
eventOrganizers={eventOrganizers}
|
||||||
|
venues={venues}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { graphql } from "@/gql";
|
||||||
|
import { EventFragment } from "@/gql/graphql";
|
||||||
|
import { getClient } from "@/app/client";
|
||||||
|
import { EventDetails } from "@/components/events/EventDetails";
|
||||||
|
import { EventHeader } from "@/components/events/EventHeader";
|
||||||
|
import { BgPig } from "@/components/general/BgPig";
|
||||||
|
import { PageContent } from "@/components/general/PageContent";
|
||||||
|
import { getEventPig } from "@/lib/event";
|
||||||
|
|
||||||
|
const eventBySlugQuery = graphql(`
|
||||||
|
query eventBySlug($slug: String!) {
|
||||||
|
event: page(contentType: "events.EventPage", slug: $slug) {
|
||||||
|
... on EventPage {
|
||||||
|
...Event
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
export type EventPageViewProps = { event: EventFragment };
|
||||||
|
|
||||||
|
export async function loadEventPageProps(args: {
|
||||||
|
slug?: string;
|
||||||
|
eventOverride?: EventFragment;
|
||||||
|
}): Promise<EventPageViewProps | null> {
|
||||||
|
if (args.eventOverride) {
|
||||||
|
return { event: args.eventOverride };
|
||||||
|
}
|
||||||
|
if (!args.slug) throw new Error("loadEventPageProps needs slug or eventOverride");
|
||||||
|
const { data, error } = await getClient().query(eventBySlugQuery, {
|
||||||
|
slug: args.slug,
|
||||||
|
});
|
||||||
|
if (error) throw new Error(error.message);
|
||||||
|
const event = data?.event as EventFragment | undefined;
|
||||||
|
if (!event) return null;
|
||||||
|
return { event };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function EventPageView({ event }: EventPageViewProps) {
|
||||||
|
const eventPig = getEventPig(event);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<main className="site-main" id="main">
|
||||||
|
<EventHeader event={event} />
|
||||||
|
<EventDetails event={event} />
|
||||||
|
{event.lead && (
|
||||||
|
<div
|
||||||
|
className="lead event-lead"
|
||||||
|
dangerouslySetInnerHTML={{ __html: event.lead }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<PageContent blocks={event.body} />
|
||||||
|
</main>
|
||||||
|
{eventPig && <BgPig type={eventPig} color="white" />}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,15 +1,23 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { EventFragment, EventOrganizer } from "@/lib/event";
|
import { unmaskFragment } from "@/gql";
|
||||||
|
import {
|
||||||
|
EventFragment,
|
||||||
|
EventOrganizerFragmentDefinition,
|
||||||
|
} from "@/lib/event";
|
||||||
import styles from "./organizerList.module.scss";
|
import styles from "./organizerList.module.scss";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Fragment } from "react";
|
import { Fragment } from "react";
|
||||||
|
|
||||||
export const OrganizerList = ({ event }: { event: EventFragment }) => {
|
export const OrganizerList = ({ event }: { event: EventFragment }) => {
|
||||||
const total = event.organizers.length;
|
const organizers = unmaskFragment(
|
||||||
|
EventOrganizerFragmentDefinition,
|
||||||
|
event.organizers
|
||||||
|
);
|
||||||
|
const total = organizers.length;
|
||||||
return (
|
return (
|
||||||
<div className={styles.organizerList}>
|
<div className={styles.organizerList}>
|
||||||
{event.organizers.map((organizer, index) => {
|
{organizers.map((organizer, index) => {
|
||||||
const url = organizer.association?.url ?? organizer.externalUrl ?? null;
|
const url = organizer.association?.url ?? organizer.externalUrl ?? null;
|
||||||
const hasValidUrl =
|
const hasValidUrl =
|
||||||
typeof url === "string" &&
|
typeof url === "string" &&
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import { graphql } from "@/gql";
|
||||||
|
import { GenericFragment } from "@/gql/graphql";
|
||||||
|
import { getClient } from "@/app/client";
|
||||||
|
import { PageHeader } from "@/components/general/PageHeader";
|
||||||
|
import { PageContent } from "@/components/general/PageContent";
|
||||||
|
import { BgPig } from "@/components/general/BgPig";
|
||||||
|
|
||||||
|
const GenericFragmentDefinition = graphql(`
|
||||||
|
fragment Generic on GenericPage {
|
||||||
|
__typename
|
||||||
|
id
|
||||||
|
urlPath
|
||||||
|
seoTitle
|
||||||
|
searchDescription
|
||||||
|
title
|
||||||
|
lead
|
||||||
|
pig
|
||||||
|
body {
|
||||||
|
...Blocks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const genericPageByUrlPathQuery = graphql(`
|
||||||
|
query genericPageByUrl($urlPath: String!) {
|
||||||
|
page: page(contentType: "generic.GenericPage", urlPath: $urlPath) {
|
||||||
|
... on GenericPage {
|
||||||
|
...Generic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
export type GenericPageViewProps = { page: GenericFragment };
|
||||||
|
|
||||||
|
export async function loadGenericPageProps(args: {
|
||||||
|
urlPath?: string;
|
||||||
|
pageOverride?: GenericFragment;
|
||||||
|
}): Promise<GenericPageViewProps | null> {
|
||||||
|
if (args.pageOverride) {
|
||||||
|
return { page: args.pageOverride };
|
||||||
|
}
|
||||||
|
if (!args.urlPath) throw new Error("loadGenericPageProps needs urlPath or pageOverride");
|
||||||
|
const { data, error } = await getClient().query(genericPageByUrlPathQuery, {
|
||||||
|
urlPath: args.urlPath,
|
||||||
|
});
|
||||||
|
if (error) throw new Error(error.message);
|
||||||
|
const page = data?.page as GenericFragment | undefined;
|
||||||
|
if (!page) return null;
|
||||||
|
return { page };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GenericPageView({ page }: GenericPageViewProps) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<main className="site-main" id="main">
|
||||||
|
<PageHeader heading={page.title} lead={page.lead} />
|
||||||
|
<PageContent blocks={page.body} />
|
||||||
|
</main>
|
||||||
|
{page.pig && <BgPig type={page.pig} color="white" />}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import NextImage, { ImageProps as NextImageProps } from "next/image";
|
import NextImage, { type ImageProps as NextImageProps } from "next/image";
|
||||||
import styles from "./image.module.scss";
|
import styles from "./image.module.scss";
|
||||||
|
|
||||||
type ImageProps = NextImageProps & {
|
type ImageProps = NextImageProps & {
|
||||||
@@ -7,14 +7,35 @@ type ImageProps = NextImageProps & {
|
|||||||
imageFormat?: string | null; // "original" | "bleed" | "fullWidth"
|
imageFormat?: string | null; // "original" | "bleed" | "fullWidth"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function isSvgSrc(src: NextImageProps["src"]): src is string {
|
||||||
|
return typeof src === "string" && /\.svg(\?|#|$)/i.test(src);
|
||||||
|
}
|
||||||
|
|
||||||
|
function MaybeNextImage(props: NextImageProps) {
|
||||||
|
if (isSvgSrc(props.src)) {
|
||||||
|
const { src, alt, width, height, className, style } = props;
|
||||||
|
return (
|
||||||
|
<img // eslint-disable-line @next/next/no-img-element
|
||||||
|
src={src}
|
||||||
|
alt={alt}
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
className={className}
|
||||||
|
style={style}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <NextImage {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
export function ImageFigure(props: ImageProps) {
|
export function ImageFigure(props: ImageProps) {
|
||||||
const { attribution, caption, imageFormat, ...nextImageProps } = props;
|
const { attribution, caption, imageFormat, ...imageProps } = props;
|
||||||
return (
|
return (
|
||||||
<figure
|
<figure
|
||||||
className={`${styles.image} ${imageFormat ? styles[imageFormat] : ""}`}
|
className={`${styles.image} ${imageFormat ? styles[imageFormat] : ""}`}
|
||||||
>
|
>
|
||||||
<div className={styles.imageWrapper}>
|
<div className={styles.imageWrapper}>
|
||||||
<NextImage {...nextImageProps} />
|
<MaybeNextImage {...imageProps} />
|
||||||
{attribution && <div className={styles.attribution}>{attribution}</div>}
|
{attribution && <div className={styles.attribution}>{attribution}</div>}
|
||||||
</div>
|
</div>
|
||||||
{caption && <figcaption>{caption}</figcaption>}
|
{caption && <figcaption>{caption}</figcaption>}
|
||||||
@@ -23,5 +44,5 @@ export function ImageFigure(props: ImageProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Image(props: NextImageProps) {
|
export function Image(props: NextImageProps) {
|
||||||
return <NextImage {...props} />;
|
return <MaybeNextImage {...props} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export const PageHeader = ({
|
|||||||
align
|
align
|
||||||
}: {
|
}: {
|
||||||
heading: string;
|
heading: string;
|
||||||
lead?: string;
|
lead?: string | null;
|
||||||
align?: "center" | "left"
|
align?: "center" | "left"
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import styles from "./previewBanner.module.scss";
|
||||||
|
|
||||||
|
export function PreviewBanner() {
|
||||||
|
return (
|
||||||
|
<div className={styles.previewBanner} role="status">
|
||||||
|
<span className={styles.label}>Forhåndsvisning</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="tertiary"
|
||||||
|
onClick={async () => {
|
||||||
|
await fetch("/api/preview/disable", { method: "POST" });
|
||||||
|
window.location.reload();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Avslutt forhåndsvisning
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
.previewBanner {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: var(--spacing-s);
|
||||||
|
padding: var(--spacing-xs) var(--spacing-s);
|
||||||
|
background: color-mix(in srgb, var(--color-deepBrick) 60%, transparent);
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
color: var(--color-betongGray);
|
||||||
|
font-family: var(--font-main-demi);
|
||||||
|
font-size: var(--font-size-caption);
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
import { graphql } from "@/gql";
|
||||||
|
import { HomeFragment } from "@/gql/graphql";
|
||||||
|
import { getClient } from "@/app/client";
|
||||||
|
import { EventFragment } from "@/lib/event";
|
||||||
|
import { NewsFragment } from "@/lib/news";
|
||||||
|
import { FeaturedEvents } from "@/components/events/FeaturedEvents";
|
||||||
|
import { UpcomingEvents } from "@/components/events/UpcomingEvents";
|
||||||
|
import { Icon } from "@/components/general/Icon";
|
||||||
|
import { Newsletter } from "@/components/general/Newsletter";
|
||||||
|
import { Pig } from "@/components/general/Pig";
|
||||||
|
import { SectionFooter } from "@/components/general/SectionFooter";
|
||||||
|
import { SectionHeader } from "@/components/general/SectionHeader";
|
||||||
|
import { NewsList } from "@/components/news/NewsList";
|
||||||
|
|
||||||
|
const HomeFragmentDefinition = graphql(`
|
||||||
|
fragment Home on HomePage {
|
||||||
|
__typename
|
||||||
|
featuredEvents {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const homeQuery = graphql(`
|
||||||
|
query home {
|
||||||
|
events: eventIndex {
|
||||||
|
... on EventIndex {
|
||||||
|
futureEvents {
|
||||||
|
... on EventPage {
|
||||||
|
...Event
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
home: page(contentType: "home.HomePage", urlPath: "/home/") {
|
||||||
|
... on HomePage {
|
||||||
|
...Home
|
||||||
|
}
|
||||||
|
}
|
||||||
|
news: pages(contentType: "news.newsPage", order: "-first_published_at", limit: 4) {
|
||||||
|
... on NewsPage {
|
||||||
|
...News
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
export type HomePageViewProps = {
|
||||||
|
home: HomeFragment;
|
||||||
|
events: EventFragment[];
|
||||||
|
news: NewsFragment[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function loadHomePageProps(overrides?: {
|
||||||
|
homeOverride?: HomeFragment;
|
||||||
|
}): Promise<HomePageViewProps> {
|
||||||
|
const { data, error } = await getClient().query(homeQuery, {});
|
||||||
|
if (error) throw new Error(error.message);
|
||||||
|
const home = overrides?.homeOverride ?? (data?.home as HomeFragment | undefined);
|
||||||
|
if (!home) throw new Error("Failed to load /");
|
||||||
|
const events = (data?.events?.futureEvents ?? []) as EventFragment[];
|
||||||
|
const news = (data?.news ?? []) as NewsFragment[];
|
||||||
|
return { home, events, news };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function HomePageView({ home, events, news }: HomePageViewProps) {
|
||||||
|
const featuredEventIds = home.featuredEvents.map((x) => x.id);
|
||||||
|
const featuredEvents = [
|
||||||
|
...events.filter((x) => featuredEventIds.includes(x.id)),
|
||||||
|
...events.filter((x) => !featuredEventIds.includes(x.id)),
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<main className="site-main index" id="main">
|
||||||
|
<FeaturedEvents events={featuredEvents} />
|
||||||
|
<UpcomingEvents events={events} />
|
||||||
|
<div className="infoBlock">
|
||||||
|
<SectionHeader heading="Besøk oss" link="/praktisk" linkText="Praktisk info" />
|
||||||
|
<div>
|
||||||
|
<h2 className="title">Skal du besøke Chateau Neuf?</h2>
|
||||||
|
<p>
|
||||||
|
Vi hjelper deg med å finne frem, og sørger for at du har en fin
|
||||||
|
opplevelse.
|
||||||
|
</p>
|
||||||
|
<Link href="/praktisk#adkomst" className="button">
|
||||||
|
<span>Adresse og adkomst</span>
|
||||||
|
<Icon type="arrowRight" />
|
||||||
|
</Link>
|
||||||
|
<Link href="/praktisk#billetter" className="button">
|
||||||
|
<span>Billetter</span>
|
||||||
|
<Icon type="arrowRight" />
|
||||||
|
</Link>
|
||||||
|
<Link href="/praktisk#apningstider" className="button">
|
||||||
|
<span>Åpningstider</span>
|
||||||
|
<Icon type="arrowRight" />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="pig">
|
||||||
|
<Pig type="point" />
|
||||||
|
</div>
|
||||||
|
<SectionFooter link="/praktisk" linkText="Praktisk info" />
|
||||||
|
</div>
|
||||||
|
<NewsList heading="Siste nytt" featured news={news} />
|
||||||
|
</main>
|
||||||
|
<Newsletter />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { getClient } from "@/app/client";
|
||||||
|
import { PageHeader } from "@/components/general/PageHeader";
|
||||||
|
import { NewsList } from "@/components/news/NewsList";
|
||||||
|
import { NewsFragment, NewsIndexFragment, newsQuery } from "@/lib/news";
|
||||||
|
|
||||||
|
export type NewsIndexViewProps = {
|
||||||
|
index: NewsIndexFragment;
|
||||||
|
news: NewsFragment[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function loadNewsIndexProps(overrides?: {
|
||||||
|
indexOverride?: NewsIndexFragment;
|
||||||
|
}): Promise<NewsIndexViewProps> {
|
||||||
|
const { data, error } = await getClient().query(newsQuery, {});
|
||||||
|
if (error) throw new Error(error.message);
|
||||||
|
const index = overrides?.indexOverride ?? (data?.index as NewsIndexFragment | undefined);
|
||||||
|
if (!index) throw new Error("Failed to load /aktuelt");
|
||||||
|
const news = (data?.news ?? []) as NewsFragment[];
|
||||||
|
return { index, news };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function NewsIndexView({ index, news }: NewsIndexViewProps) {
|
||||||
|
return (
|
||||||
|
<main className="site-main" id="main">
|
||||||
|
<PageHeader heading={index.title} lead={index.lead} align="left" />
|
||||||
|
<NewsList news={news} />
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user