Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9ca9f5db11 | |||
| 696e6b8f11 | |||
| 5f354972d9 | |||
| 6a9fff8917 | |||
| 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
|
||||
*.swp
|
||||
.vscode/
|
||||
.coverage
|
||||
/venv/
|
||||
/.venv/
|
||||
/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.models import Page
|
||||
from wagtail.search import index
|
||||
from wagtail_headless_preview.models import HeadlessMixin
|
||||
|
||||
from dnscms.fields import CommonStreamField
|
||||
from dnscms.wordpress.models import WPImportedPageMixin
|
||||
|
||||
|
||||
@register_singular_query_field("associationIndex")
|
||||
class AssociationIndex(Page):
|
||||
class AssociationIndex(HeadlessMixin, Page):
|
||||
max_count = 1
|
||||
subpage_types = ["associations.AssociationPage"]
|
||||
|
||||
@@ -37,7 +38,7 @@ class AssociationIndex(Page):
|
||||
search_fields = Page.search_fields
|
||||
|
||||
|
||||
class AssociationPage(WPImportedPageMixin, Page):
|
||||
class AssociationPage(HeadlessMixin, WPImportedPageMixin, Page):
|
||||
subpage_types = []
|
||||
parent_page_types = ["associations.AssociationIndex"]
|
||||
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.search import index
|
||||
from wagtail.snippets.models import register_snippet
|
||||
from wagtail_headless_preview.models import HeadlessMixin
|
||||
|
||||
from contacts.blocks import ContactSectionBlock
|
||||
from dnscms.blocks import BASE_BLOCKS
|
||||
@@ -22,7 +23,7 @@ PHONE_REGEX_VALIDATOR = RegexValidator(
|
||||
|
||||
|
||||
@register_singular_query_field("contactIndex")
|
||||
class ContactIndex(Page):
|
||||
class ContactIndex(HeadlessMixin, Page):
|
||||
max_count = 1
|
||||
subpage_types = []
|
||||
|
||||
|
||||
@@ -203,25 +203,6 @@ class AccordionBlock(blocks.StructBlock):
|
||||
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
|
||||
class FactBoxBlock(blocks.StructBlock):
|
||||
background_color = blocks.ChoiceBlock(
|
||||
@@ -253,7 +234,6 @@ BASE_BLOCKS = [
|
||||
("page_section_navigation", PageSectionNavigationBlock()),
|
||||
("accordion", AccordionBlock()),
|
||||
("fact_box", FactBoxBlock()),
|
||||
("photo_sphere", PhotoSphereBlock()),
|
||||
("embed", EmbedBlock()),
|
||||
("raw_html", blocks.RawHTMLBlock()),
|
||||
]
|
||||
|
||||
@@ -37,9 +37,11 @@ INSTALLED_APPS = [
|
||||
"news",
|
||||
"openinghours",
|
||||
"sponsors",
|
||||
"studio",
|
||||
# end cms apps
|
||||
"grapple",
|
||||
"graphene_django",
|
||||
"wagtail_headless_preview",
|
||||
"wagtail.contrib.forms",
|
||||
"wagtail.contrib.redirects",
|
||||
"wagtail.contrib.settings",
|
||||
@@ -142,6 +144,8 @@ USE_L10N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
LOCALE_PATHS = [os.path.join(BASE_DIR, "locale")]
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/4.2/howto/static-files/
|
||||
@@ -173,6 +177,7 @@ WAGTAIL_SITE_NAME = "dnscms"
|
||||
WAGTAIL_ALLOW_UNICODE_SLUGS = False
|
||||
|
||||
WAGTAILIMAGES_IMAGE_MODEL = "images.CustomImage"
|
||||
WAGTAILIMAGES_EXTENSIONS = ["avif", "gif", "jpg", "jpeg", "png", "webp", "svg"]
|
||||
|
||||
# Search
|
||||
# https://docs.wagtail.org/en/stable/topics/search/backends.html
|
||||
@@ -183,11 +188,22 @@ WAGTAILSEARCH_BACKENDS = {
|
||||
}
|
||||
|
||||
# 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://example.com"
|
||||
# e.g. in notification emails. Don't include '/admin' or a trailing slash.
|
||||
# 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
|
||||
BASE_URL = "http://example.com"
|
||||
# Public URL of the Next.js frontend. Used to direct preview iframes and to
|
||||
# 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
|
||||
DATA_UPLOAD_MAX_NUMBER_FIELDS = 10_000
|
||||
@@ -206,6 +222,7 @@ GRAPPLE = {
|
||||
"news",
|
||||
"openinghours",
|
||||
"sponsors",
|
||||
"studio",
|
||||
],
|
||||
"EXPOSE_GRAPHIQL": True,
|
||||
"PAGE_SIZE": 100,
|
||||
|
||||
@@ -11,13 +11,6 @@ ALLOWED_HOSTS = ["*"]
|
||||
|
||||
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:
|
||||
from .local import *
|
||||
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"]
|
||||
@@ -6,7 +6,6 @@ from wagtail import hooks
|
||||
from wagtail.admin.menu import MenuItem
|
||||
|
||||
from associations.models import AssociationIndex
|
||||
from events.models import EventIndex
|
||||
from news.models import NewsIndex
|
||||
|
||||
|
||||
@@ -15,15 +14,6 @@ def enable_additional_rich_text_features(features):
|
||||
features.default_features.extend(["h5", "h6", "blockquote"])
|
||||
|
||||
|
||||
@hooks.register("register_admin_menu_item")
|
||||
def register_events_menu_item():
|
||||
page = EventIndex.objects.first()
|
||||
events_url = "#"
|
||||
if page:
|
||||
events_url = reverse("wagtailadmin_explore", args=(quote(page.pk),))
|
||||
return MenuItem("Arrangementer", events_url, icon_name="date", order=1)
|
||||
|
||||
|
||||
@hooks.register("register_admin_menu_item")
|
||||
def register_associations_menu_item():
|
||||
page = AssociationIndex.objects.first()
|
||||
@@ -34,7 +24,7 @@ def register_associations_menu_item():
|
||||
|
||||
|
||||
@hooks.register("register_admin_menu_item")
|
||||
def register_associations_menu_item():
|
||||
def register_news_menu_item():
|
||||
page = NewsIndex.objects.first()
|
||||
news_url = "#"
|
||||
if page:
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext, gettext_lazy as _
|
||||
from django.utils.translation import ngettext
|
||||
from wagtail.admin.ui.tables import Column, DateColumn
|
||||
from wagtail.admin.ui.tables.pages import PageStatusColumn, PageTitleColumn
|
||||
from wagtail.admin.views.pages.choose_parent import ChooseParentView
|
||||
from wagtail.admin.views.pages.listing import IndexView
|
||||
from wagtail.admin.viewsets.pages import PageListingViewSet
|
||||
|
||||
from events.models import EventPage
|
||||
|
||||
|
||||
class EventDateColumn(Column):
|
||||
def get_value(self, instance):
|
||||
occurrences = list(instance.occurrences.order_by("start"))
|
||||
if not occurrences:
|
||||
return "—"
|
||||
if len(occurrences) == 1:
|
||||
local = timezone.localtime(occurrences[0].start)
|
||||
return local.strftime(gettext("%Y-%m-%d at %H:%M"))
|
||||
count = len(occurrences)
|
||||
return ngettext("%(count)d occurrence", "%(count)d occurrences", count) % {"count": count}
|
||||
|
||||
|
||||
class OrganizersColumn(Column):
|
||||
def get_value(self, instance):
|
||||
names = list(instance.organizers.values_list("name", flat=True))
|
||||
if not names:
|
||||
return "—"
|
||||
if len(names) == 1:
|
||||
return names[0]
|
||||
return f"{names[0]} (+{len(names) - 1})"
|
||||
|
||||
|
||||
class EventPageIndexView(IndexView):
|
||||
def annotate_queryset(self, pages):
|
||||
pages = super().annotate_queryset(pages)
|
||||
return pages.prefetch_related(
|
||||
"occurrences",
|
||||
"organizer_links__organizer",
|
||||
)
|
||||
|
||||
|
||||
class EventChooseParentView(ChooseParentView):
|
||||
"""Redirect newly-created EventPages back to the events listing."""
|
||||
|
||||
def _with_next(self, response):
|
||||
if response.status_code != 302:
|
||||
return response
|
||||
url = response["Location"]
|
||||
sep = "&" if "?" in url else "?"
|
||||
response["Location"] = f"{url}{sep}{urlencode({'next': reverse('events:index')})}"
|
||||
return response
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
return self._with_next(super().get(request, *args, **kwargs))
|
||||
|
||||
def form_valid(self, form):
|
||||
return self._with_next(super().form_valid(form))
|
||||
|
||||
|
||||
class EventPageListingViewSet(PageListingViewSet):
|
||||
model = EventPage
|
||||
index_view_class = EventPageIndexView
|
||||
choose_parent_view_class = EventChooseParentView
|
||||
icon = "date"
|
||||
menu_label = _("Events")
|
||||
menu_order = 1
|
||||
add_to_admin_menu = True
|
||||
ordering = "-latest_revision_created_at"
|
||||
|
||||
columns = [
|
||||
PageTitleColumn("title", label=_("Title"), sort_key="title", classname="title"),
|
||||
EventDateColumn("event_date", label=_("Date"), width="13%"),
|
||||
OrganizersColumn("organizers", label=_("Organizers"), width="12%"),
|
||||
DateColumn(
|
||||
"latest_revision_created_at",
|
||||
label=_("Updated"),
|
||||
sort_key="latest_revision_created_at",
|
||||
width="10%",
|
||||
),
|
||||
PageStatusColumn("status", label=_("Status"), sort_key="live", width="10%"),
|
||||
]
|
||||
|
||||
|
||||
event_page_listing_viewset = EventPageListingViewSet("events")
|
||||
@@ -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", "")],
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,17 @@
|
||||
# Generated by Django 6.0.5 on 2026-05-19 18:40
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('events', '0053_eventpage_lead'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='eventpage',
|
||||
options={'verbose_name': 'event', 'verbose_name_plural': 'events'},
|
||||
),
|
||||
]
|
||||
+84
-190
@@ -30,6 +30,7 @@ from wagtail.fields import RichTextField
|
||||
from wagtail.models import Orderable, Page, PageManager, PageQuerySet
|
||||
from wagtail.search import index
|
||||
from wagtail.snippets.models import register_snippet
|
||||
from wagtail_headless_preview.models import HeadlessMixin
|
||||
|
||||
from associations.widgets import AssociationChooserWidget
|
||||
from dnscms.fields import CommonStreamField
|
||||
@@ -39,7 +40,7 @@ from venues.models import VenuePage
|
||||
|
||||
|
||||
@register_singular_query_field("eventIndex")
|
||||
class EventIndex(Page):
|
||||
class EventIndex(HeadlessMixin, Page):
|
||||
max_count = 1
|
||||
subpage_types = ["events.EventPage"]
|
||||
|
||||
@@ -74,11 +75,12 @@ class EventCategory(models.Model):
|
||||
help_text=_("The name of the category as it will appear in URLs."),
|
||||
)
|
||||
show_in_filters = models.BooleanField(
|
||||
default=False, help_text="Skal denne kategorien være mulig å filtrere på i programmet?"
|
||||
default=False,
|
||||
help_text=_("Should this category be available as a filter in the event programme?"),
|
||||
)
|
||||
|
||||
PIG_CHOICES = [
|
||||
("", "Ingen"),
|
||||
("", _("None")),
|
||||
] + ALL_PIGS
|
||||
|
||||
pig = models.CharField(
|
||||
@@ -86,14 +88,14 @@ class EventCategory(models.Model):
|
||||
choices=PIG_CHOICES,
|
||||
default="",
|
||||
blank=True,
|
||||
help_text="Standardgris for arrangementer av denne typen.",
|
||||
help_text=_("Default pig for events of this kind."),
|
||||
)
|
||||
|
||||
panels = [
|
||||
TitleFieldPanel("name"),
|
||||
FieldPanel("slug"),
|
||||
FieldPanel("show_in_filters"),
|
||||
FieldPanel("pig", heading="Gris"),
|
||||
FieldPanel("pig", heading=_("Pig")),
|
||||
]
|
||||
|
||||
graphql_fields = [
|
||||
@@ -104,8 +106,8 @@ class EventCategory(models.Model):
|
||||
]
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Event category"
|
||||
verbose_name_plural = "Event categories"
|
||||
verbose_name = _("event category")
|
||||
verbose_name_plural = _("event categories")
|
||||
ordering = ["name"]
|
||||
|
||||
def __str__(self):
|
||||
@@ -133,8 +135,8 @@ class EventOrganizerLink(Orderable):
|
||||
return f"{self.organizer.name}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Arrangør"
|
||||
verbose_name_plural = "Arrangører"
|
||||
verbose_name = _("organizer")
|
||||
verbose_name_plural = _("organizers")
|
||||
constraints = [
|
||||
UniqueConstraint(
|
||||
"event", "organizer", name="event_organizer_link_event_organizer_unique"
|
||||
@@ -162,13 +164,13 @@ class EventOrganizer(ClusterableModel):
|
||||
blank=True,
|
||||
on_delete=models.PROTECT,
|
||||
related_name="organizers",
|
||||
help_text="Om en samfundsforening eller et utvalg står bak, velg det her.",
|
||||
help_text=_("If a DNS association or committee is behind it, choose it here."),
|
||||
)
|
||||
|
||||
external_url = models.URLField(
|
||||
blank=True,
|
||||
max_length=512,
|
||||
help_text="Lenke til nettstedet til ekstern arrangør",
|
||||
help_text=_("Link to the external organizer's website"),
|
||||
)
|
||||
|
||||
panels = [
|
||||
@@ -177,15 +179,15 @@ class EventOrganizer(ClusterableModel):
|
||||
FieldPanel(
|
||||
"association",
|
||||
widget=AssociationChooserWidget(linked_fields={"association": "#id_association"}),
|
||||
heading="Intern arrangør",
|
||||
heading=_("Internal organizer"),
|
||||
),
|
||||
MultiFieldPanel(
|
||||
heading="Ekstern arrangør",
|
||||
heading=_("External organizer"),
|
||||
children=[
|
||||
FieldPanel(
|
||||
"external_url",
|
||||
heading="Nettsted",
|
||||
help_text="La denne stå tom om arrangøren finnes i lista over.",
|
||||
heading=_("Website"),
|
||||
help_text=_("Leave this empty if the organizer exists in the list above."),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -199,8 +201,8 @@ class EventOrganizer(ClusterableModel):
|
||||
]
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Event organizer"
|
||||
verbose_name_plural = "Event organizers"
|
||||
verbose_name = _("event organizer")
|
||||
verbose_name_plural = _("event organizers")
|
||||
ordering = ["name"]
|
||||
|
||||
def __str__(self):
|
||||
@@ -209,15 +211,18 @@ class EventOrganizer(ClusterableModel):
|
||||
|
||||
class EventPageQuerySet(PageQuerySet):
|
||||
def future(self):
|
||||
today = timezone.localtime(timezone.now()).date()
|
||||
next_occurrence = Min("occurrences__start", filter=Q(occurrences__start__gte=today))
|
||||
return self.filter(occurrences__start__gte=today).annotate(next_occurrence=next_occurrence)
|
||||
now = timezone.now()
|
||||
today_start = timezone.localtime(now).replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
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)
|
||||
|
||||
|
||||
class EventPage(WPImportedPageMixin, Page):
|
||||
class EventPage(HeadlessMixin, WPImportedPageMixin, Page):
|
||||
subpage_types = []
|
||||
parent_page_types = ["events.EventIndex"]
|
||||
show_in_menus = False
|
||||
@@ -230,19 +235,19 @@ class EventPage(WPImportedPageMixin, Page):
|
||||
blank=True,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name="+",
|
||||
help_text=(
|
||||
"Velg et bilde til bruk i programmet og andre visningsflater. "
|
||||
"Bør være et bilde eller en illustrasjon uten tekst "
|
||||
"– ikke gjenbruk et Facebook-cover ukritisk!"
|
||||
help_text=_(
|
||||
"Choose an image for use in the programme and other surfaces. "
|
||||
"Should be a photo or an illustration without too much text "
|
||||
"– don't reuse a Facebook cover uncritically!"
|
||||
),
|
||||
)
|
||||
|
||||
subtitle = models.CharField(
|
||||
blank=True,
|
||||
max_length=128,
|
||||
help_text=(
|
||||
"En kort tekst som kommer rett under under tittelen. "
|
||||
"La denne gjerne stå tom om du fikk plass til det meste i tittelen."
|
||||
help_text=_(
|
||||
"A short text that appears right below the title. "
|
||||
"Feel free to leave it empty if you fit most of it in the main title."
|
||||
),
|
||||
)
|
||||
lead = RichTextField(features=["italic", "link"], blank=True)
|
||||
@@ -258,8 +263,8 @@ class EventPage(WPImportedPageMixin, Page):
|
||||
)
|
||||
|
||||
PIG_CHOICES = [
|
||||
("", "Ingen"),
|
||||
("automatic", "Automatisk"),
|
||||
("", _("None")),
|
||||
("automatic", _("Automatic")),
|
||||
] + ALL_PIGS
|
||||
|
||||
pig = models.CharField(
|
||||
@@ -267,21 +272,21 @@ class EventPage(WPImportedPageMixin, Page):
|
||||
choices=PIG_CHOICES,
|
||||
default="automatic",
|
||||
blank=True,
|
||||
help_text=(
|
||||
"Grisen som henger på arrangementssiden. "
|
||||
"Automatisk fører til at en velges basert på arrangementets kategori."
|
||||
help_text=_(
|
||||
"The pig that hangs out on the event page. "
|
||||
"Automatic causes one to be chosen based on the event's category."
|
||||
),
|
||||
)
|
||||
|
||||
ticket_url = models.URLField(
|
||||
blank=True,
|
||||
max_length=1024,
|
||||
help_text="Lenke direkte til billettkjøp, f.eks. TicketCo eller Ticketmaster",
|
||||
help_text=_("Direct link to ticket purchase, e.g. TicketCo, Billetto or Ticketmaster"),
|
||||
)
|
||||
facebook_url = models.URLField(
|
||||
blank=True,
|
||||
max_length=1024,
|
||||
help_text="Lenke direkte til arrangementet på Facebook",
|
||||
help_text=_("Direct link to the event on Facebook"),
|
||||
)
|
||||
|
||||
free = models.BooleanField(null=False, default=False)
|
||||
@@ -290,62 +295,65 @@ class EventPage(WPImportedPageMixin, Page):
|
||||
price_member = models.CharField(max_length=32, blank=True)
|
||||
|
||||
ticket_panels = [
|
||||
FieldPanel("free", heading="Gratis", help_text="Er dette arrangementet gratis for alle?"),
|
||||
FieldPanel("free", heading=_("Free"), help_text=_("Is this event free for everyone?")),
|
||||
MultiFieldPanel(
|
||||
children=[
|
||||
FieldRowPanel(
|
||||
children=[
|
||||
FieldPanel("price_regular", heading="Ordinær pris"),
|
||||
FieldPanel("price_student", heading="Pris for studenter"),
|
||||
FieldPanel("price_member", heading="Pris for medlemmer av DNS"),
|
||||
FieldPanel("price_regular", heading=_("Regular price")),
|
||||
FieldPanel("price_student", heading=_("Price for students")),
|
||||
FieldPanel("price_member", heading=_("Price for DNS members")),
|
||||
],
|
||||
help_text="",
|
||||
),
|
||||
HelpPanel(
|
||||
content=mark_safe(
|
||||
"Skriv <strong>0</strong> om gratis. Tomt felt skjuler priskategorien. Om mulig, skriv kun tall."
|
||||
_(
|
||||
"Write <strong>0</strong> for free. "
|
||||
"An empty field hides the price category. "
|
||||
"If possible, write digits only."
|
||||
)
|
||||
)
|
||||
),
|
||||
],
|
||||
attrs={"id": "specific_pricing_panel"},
|
||||
),
|
||||
FieldPanel("ticket_url", heading="Billettkjøpslenke"),
|
||||
FieldPanel("ticket_url", heading=_("Ticket purchase link")),
|
||||
]
|
||||
|
||||
content_panels = Page.content_panels + [
|
||||
FieldPanel("subtitle", heading="Undertittel"),
|
||||
FieldPanel("subtitle", heading=_("Subtitle")),
|
||||
FieldPanel("featured_image"),
|
||||
FieldPanel("lead", heading="Ingress"),
|
||||
FieldPanel("lead", heading=_("Lead")),
|
||||
FieldPanel("body"),
|
||||
FieldPanel("categories", widget=forms.CheckboxSelectMultiple),
|
||||
MultiFieldPanel(
|
||||
heading="Arrangører",
|
||||
heading=_("Organizers"),
|
||||
children=[
|
||||
HelpPanel(
|
||||
content=("Hvem står bak arrangementet?"),
|
||||
content=_("Who is behind the event?"),
|
||||
),
|
||||
MultipleChooserPanel(
|
||||
"organizer_links", chooser_field_name="organizer", label="Arrangør"
|
||||
"organizer_links", chooser_field_name="organizer", label=_("Organizer")
|
||||
),
|
||||
],
|
||||
),
|
||||
FieldPanel("pig", heading="Gris"),
|
||||
FieldPanel("pig", heading=_("Pig")),
|
||||
FieldPanel(
|
||||
"facebook_url",
|
||||
heading="Facebook-lenke",
|
||||
help_text="Lenke direkte til arrangementet på Facebook.",
|
||||
heading=_("Facebook link"),
|
||||
help_text=_("Direct link to the event on Facebook."),
|
||||
),
|
||||
MultiFieldPanel(heading="Priser og billettkjøp", children=ticket_panels),
|
||||
MultiFieldPanel(heading=_("Pricing and tickets"), children=ticket_panels),
|
||||
MultiFieldPanel(
|
||||
heading="Dato, tid og lokale",
|
||||
heading=_("Date, time and venue"),
|
||||
children=[
|
||||
HelpPanel(
|
||||
content=(
|
||||
"Om arrangementet går over flere dager, "
|
||||
"legg inn hver dag som en egen forekomst."
|
||||
content=_(
|
||||
"If the event spans several days, add each day as a separate occurrence."
|
||||
),
|
||||
),
|
||||
InlinePanel("occurrences", min_num=1, label="Forekomst"),
|
||||
InlinePanel("occurrences", min_num=1, label=_("Occurrence")),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -355,7 +363,7 @@ class EventPage(WPImportedPageMixin, Page):
|
||||
GraphQLImage("featured_image"),
|
||||
GraphQLRichText("lead"),
|
||||
GraphQLStreamfield("body"),
|
||||
GraphQLString("pig"),
|
||||
GraphQLString("pig", required=True),
|
||||
GraphQLString("ticket_url"),
|
||||
GraphQLString("facebook_url"),
|
||||
GraphQLBoolean("free"),
|
||||
@@ -387,6 +395,10 @@ class EventPage(WPImportedPageMixin, Page):
|
||||
|
||||
search_fields = Page.search_fields + [index.SearchField("body")]
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("event")
|
||||
verbose_name_plural = _("events")
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
|
||||
@@ -544,22 +556,24 @@ class EventOccurrence(Orderable):
|
||||
blank=True,
|
||||
max_length=128,
|
||||
help_text=mark_safe(
|
||||
"Bruk denne <em>om ingen av lokalene som kan velges til venstre</em> passer. "
|
||||
"F.eks. <em>Frederikkeplassen</em> eller <em>Sirkusteltet</em>."
|
||||
_(
|
||||
"Use this <em>if none of the venues that can be selected on the left</em> fit. "
|
||||
"E.g. <em>Frederikkeplassen</em> or <em>Sirkusteltet</em>."
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
panels = [
|
||||
FieldRowPanel(
|
||||
children=[
|
||||
FieldPanel("start", heading="Start"),
|
||||
FieldPanel("end", heading="Slutt"),
|
||||
FieldPanel("start", heading=_("Start")),
|
||||
FieldPanel("end", heading=_("End")),
|
||||
],
|
||||
),
|
||||
FieldRowPanel(
|
||||
children=[
|
||||
FieldPanel("venue", heading="Lokale"),
|
||||
FieldPanel("venue_custom", heading="Lokale som fritekst"),
|
||||
FieldPanel("venue", heading=_("Venue")),
|
||||
FieldPanel("venue_custom", heading=_("Venue as free text")),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -574,138 +588,18 @@ class EventOccurrence(Orderable):
|
||||
def clean(self):
|
||||
if self.venue and self.venue_custom:
|
||||
raise ValidationError(
|
||||
{"venue_custom": "Du kan ikke både velge et lokale og skrive noe i dette feltet."}
|
||||
{
|
||||
"venue_custom": _(
|
||||
"You can't both pick a venue and write something in this field."
|
||||
)
|
||||
}
|
||||
)
|
||||
if not self.venue and not self.venue_custom:
|
||||
raise ValidationError({"venue": "Lokale er påkrevd."})
|
||||
raise ValidationError({"venue": _("Venue is required.")})
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.start}--{self.end}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Forekomst"
|
||||
verbose_name_plural = "Forekomster"
|
||||
|
||||
|
||||
sample_legacy_event_json = """
|
||||
{
|
||||
"id": 64573,
|
||||
"date": "2023-12-27T11:28:34",
|
||||
"date_gmt": "2023-12-27T10:28:34",
|
||||
"guid": {
|
||||
"rendered": "https://studentersamfundet.no/?post_type=event&p=64573"
|
||||
},
|
||||
"modified": "2023-12-27T11:44:11",
|
||||
"modified_gmt": "2023-12-27T10:44:11",
|
||||
"slug": "quiz-147-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-3-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-3-2-2-2-2-2-2-2-2-2-2-2-2-2",
|
||||
"status": "publish",
|
||||
"type": "event",
|
||||
"link": "https://studentersamfundet.no/arrangement/quiz-147-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-3-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-3-2-2-2-2-2-2-2-2-2-2-2-2-2/",
|
||||
"title": {
|
||||
"rendered": "QUIZ",
|
||||
"decoded": "QUIZ"
|
||||
},
|
||||
"content": {
|
||||
"rendered": "\n<p>Det Norske Studentersamfund inviterer til quiz hver tirsdag kl. 19:00.</p>\n\n\n\n<p>Vi serverer 50 spørsmål som kan spenne seg fra one hit wonders fra 80-tallet, universets uendelighet, dyrelivets merkverdigheter og mye, mye mer!</p>\n\n\n\n<p>Quiz på Chateau Neuf er åpent for alle. Vinnere og “lucky losers” vil bli utnevnt hver kveld. Lag som er over seks personer er tillatt, men da trekkes dere for ett poeng per deltaker per runde.</p>\n\n\n\n<p>For de som ønsker å være med på sammenlagtkonkurransen for høsten vil den regnes ut for de tolv beste prestasjonene laget leverer. Så det vil fremdeles være god sjanse for å vinne sammenlagt selv dere må droppe en quiz eller to for eksamener eller andre forpliktelser.</p>\n\n\n\n<p>Velkommen quizglade mennesker!</p>\n\n\n\n<p>Gratis inngang!</p>\n",
|
||||
"protected": false
|
||||
},
|
||||
"excerpt": {
|
||||
"rendered": "<p>Det Norske Studentersamfund inviterer til quiz hver tirsdag kl. 19:00. Vi serverer 50 spørsmål som kan spenne seg fra one hit wonders fra 80-tallet, universets uendelighet, dyrelivets merkverdigheter og mye, mye mer! Quiz på Chateau Neuf er åpent for alle. Vinnere og “lucky losers” vil bli utnevnt hver kveld. Lag som er over seks personer […]</p>\n",
|
||||
"protected": false
|
||||
},
|
||||
"author": 2150,
|
||||
"featured_media": 64585,
|
||||
"template": "",
|
||||
"meta": [],
|
||||
"event_types": [13],
|
||||
"event_organizers": [390, 322],
|
||||
"facebook_url": "https://fb.me/e/2RDR5pZdr",
|
||||
"ticket_url": "",
|
||||
"price_regular": "",
|
||||
"price_member": "",
|
||||
"start_time": "2024-05-07T17:00:00+00:00",
|
||||
"end_time": "2024-05-07T20:00:00+00:00",
|
||||
"venue": "Glassbaren",
|
||||
"venue_id": "55063",
|
||||
"thumbnail": {
|
||||
"thumbnail": "https://studentersamfundet.no/wp/wp-content/uploads/2023/12/quiz-header-1-150x150.png",
|
||||
"medium": "https://studentersamfundet.no/wp/wp-content/uploads/2023/12/quiz-header-1-300x169.png",
|
||||
"medium_large": "https://studentersamfundet.no/wp/wp-content/uploads/2023/12/quiz-header-1-768x433.png",
|
||||
"large": "https://studentersamfundet.no/wp/wp-content/uploads/2023/12/quiz-header-1-1280x720.png",
|
||||
"1536x1536": "https://studentersamfundet.no/wp/wp-content/uploads/2023/12/quiz-header-1-1536x865.png",
|
||||
"2048x2048": "https://studentersamfundet.no/wp/wp-content/uploads/2023/12/quiz-header-1.png",
|
||||
"four-column": "https://studentersamfundet.no/wp/wp-content/uploads/2023/12/quiz-header-1-393x342.png",
|
||||
"six-column": "https://studentersamfundet.no/wp/wp-content/uploads/2023/12/quiz-header-1-608x342.png",
|
||||
"extra-large": "https://studentersamfundet.no/wp/wp-content/uploads/2023/12/quiz-header-1-1600x901.png",
|
||||
"newsletter-half": "https://studentersamfundet.no/wp/wp-content/uploads/2023/12/quiz-header-1-320x190.png",
|
||||
"newsletter-third": "https://studentersamfundet.no/wp/wp-content/uploads/2023/12/quiz-header-1-213x126.png",
|
||||
"featured": "https://studentersamfundet.no/wp/wp-content/uploads/2023/12/quiz-header-1-1200x480.png"
|
||||
},
|
||||
"_links": {
|
||||
"self": [
|
||||
{
|
||||
"href": "https://studentersamfundet.no/wp-json/wp/v2/events/64573"
|
||||
}
|
||||
],
|
||||
"collection": [
|
||||
{
|
||||
"href": "https://studentersamfundet.no/wp-json/wp/v2/events"
|
||||
}
|
||||
],
|
||||
"about": [
|
||||
{
|
||||
"href": "https://studentersamfundet.no/wp-json/wp/v2/types/event"
|
||||
}
|
||||
],
|
||||
"author": [
|
||||
{
|
||||
"embeddable": true,
|
||||
"href": "https://studentersamfundet.no/wp-json/wp/v2/users/2150"
|
||||
}
|
||||
],
|
||||
"version-history": [
|
||||
{
|
||||
"count": 1,
|
||||
"href": "https://studentersamfundet.no/wp-json/wp/v2/events/64573/revisions"
|
||||
}
|
||||
],
|
||||
"predecessor-version": [
|
||||
{
|
||||
"id": 64574,
|
||||
"href": "https://studentersamfundet.no/wp-json/wp/v2/events/64573/revisions/64574"
|
||||
}
|
||||
],
|
||||
"wp:featuredmedia": [
|
||||
{
|
||||
"embeddable": true,
|
||||
"href": "https://studentersamfundet.no/wp-json/wp/v2/media/64585"
|
||||
}
|
||||
],
|
||||
"wp:attachment": [
|
||||
{
|
||||
"href": "https://studentersamfundet.no/wp-json/wp/v2/media?parent=64573"
|
||||
}
|
||||
],
|
||||
"wp:term": [
|
||||
{
|
||||
"taxonomy": "event_type",
|
||||
"embeddable": true,
|
||||
"href": "https://studentersamfundet.no/wp-json/wp/v2/event_types?post=64573"
|
||||
},
|
||||
{
|
||||
"taxonomy": "event_organizer",
|
||||
"embeddable": true,
|
||||
"href": "https://studentersamfundet.no/wp-json/wp/v2/event_organizers?post=64573"
|
||||
}
|
||||
],
|
||||
"curies": [
|
||||
{
|
||||
"name": "wp",
|
||||
"href": "https://api.w.org/{rel}",
|
||||
"templated": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
"""
|
||||
verbose_name = _("occurrence")
|
||||
verbose_name_plural = _("occurrences")
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
{
|
||||
"id": 64573,
|
||||
"date": "2023-12-27T11:28:34",
|
||||
"date_gmt": "2023-12-27T10:28:34",
|
||||
"guid": {
|
||||
"rendered": "https://studentersamfundet.no/?post_type=event&p=64573"
|
||||
},
|
||||
"modified": "2023-12-27T11:44:11",
|
||||
"modified_gmt": "2023-12-27T10:44:11",
|
||||
"slug": "quiz-147-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-3-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-3-2-2-2-2-2-2-2-2-2-2-2-2-2",
|
||||
"status": "publish",
|
||||
"type": "event",
|
||||
"link": "https://studentersamfundet.no/arrangement/quiz-147-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-3-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-3-2-2-2-2-2-2-2-2-2-2-2-2-2/",
|
||||
"title": {
|
||||
"rendered": "QUIZ",
|
||||
"decoded": "QUIZ"
|
||||
},
|
||||
"content": {
|
||||
"rendered": "\n<p>Det Norske Studentersamfund inviterer til quiz hver tirsdag kl. 19:00.</p>\n\n\n\n<p>Vi serverer 50 spørsmål som kan spenne seg fra one hit wonders fra 80-tallet, universets uendelighet, dyrelivets merkverdigheter og mye, mye mer!</p>\n\n\n\n<p>Quiz på Chateau Neuf er åpent for alle. Vinnere og “lucky losers” vil bli utnevnt hver kveld. Lag som er over seks personer er tillatt, men da trekkes dere for ett poeng per deltaker per runde.</p>\n\n\n\n<p>For de som ønsker å være med på sammenlagtkonkurransen for høsten vil den regnes ut for de tolv beste prestasjonene laget leverer. Så det vil fremdeles være god sjanse for å vinne sammenlagt selv dere må droppe en quiz eller to for eksamener eller andre forpliktelser.</p>\n\n\n\n<p>Velkommen quizglade mennesker!</p>\n\n\n\n<p>Gratis inngang!</p>\n",
|
||||
"protected": false
|
||||
},
|
||||
"excerpt": {
|
||||
"rendered": "<p>Det Norske Studentersamfund inviterer til quiz hver tirsdag kl. 19:00. Vi serverer 50 spørsmål som kan spenne seg fra one hit wonders fra 80-tallet, universets uendelighet, dyrelivets merkverdigheter og mye, mye mer! Quiz på Chateau Neuf er åpent for alle. Vinnere og “lucky losers” vil bli utnevnt hver kveld. Lag som er over seks personer […]</p>\n",
|
||||
"protected": false
|
||||
},
|
||||
"author": 2150,
|
||||
"featured_media": 64585,
|
||||
"template": "",
|
||||
"meta": [],
|
||||
"event_types": [13],
|
||||
"event_organizers": [390, 322],
|
||||
"facebook_url": "https://fb.me/e/2RDR5pZdr",
|
||||
"ticket_url": "",
|
||||
"price_regular": "",
|
||||
"price_member": "",
|
||||
"start_time": "2024-05-07T17:00:00+00:00",
|
||||
"end_time": "2024-05-07T20:00:00+00:00",
|
||||
"venue": "Glassbaren",
|
||||
"venue_id": "55063",
|
||||
"thumbnail": {
|
||||
"thumbnail": "https://studentersamfundet.no/wp/wp-content/uploads/2023/12/quiz-header-1-150x150.png",
|
||||
"medium": "https://studentersamfundet.no/wp/wp-content/uploads/2023/12/quiz-header-1-300x169.png",
|
||||
"medium_large": "https://studentersamfundet.no/wp/wp-content/uploads/2023/12/quiz-header-1-768x433.png",
|
||||
"large": "https://studentersamfundet.no/wp/wp-content/uploads/2023/12/quiz-header-1-1280x720.png",
|
||||
"1536x1536": "https://studentersamfundet.no/wp/wp-content/uploads/2023/12/quiz-header-1-1536x865.png",
|
||||
"2048x2048": "https://studentersamfundet.no/wp/wp-content/uploads/2023/12/quiz-header-1.png",
|
||||
"four-column": "https://studentersamfundet.no/wp/wp-content/uploads/2023/12/quiz-header-1-393x342.png",
|
||||
"six-column": "https://studentersamfundet.no/wp/wp-content/uploads/2023/12/quiz-header-1-608x342.png",
|
||||
"extra-large": "https://studentersamfundet.no/wp/wp-content/uploads/2023/12/quiz-header-1-1600x901.png",
|
||||
"newsletter-half": "https://studentersamfundet.no/wp/wp-content/uploads/2023/12/quiz-header-1-320x190.png",
|
||||
"newsletter-third": "https://studentersamfundet.no/wp/wp-content/uploads/2023/12/quiz-header-1-213x126.png",
|
||||
"featured": "https://studentersamfundet.no/wp/wp-content/uploads/2023/12/quiz-header-1-1200x480.png"
|
||||
},
|
||||
"_links": {
|
||||
"self": [
|
||||
{
|
||||
"href": "https://studentersamfundet.no/wp-json/wp/v2/events/64573"
|
||||
}
|
||||
],
|
||||
"collection": [
|
||||
{
|
||||
"href": "https://studentersamfundet.no/wp-json/wp/v2/events"
|
||||
}
|
||||
],
|
||||
"about": [
|
||||
{
|
||||
"href": "https://studentersamfundet.no/wp-json/wp/v2/types/event"
|
||||
}
|
||||
],
|
||||
"author": [
|
||||
{
|
||||
"embeddable": true,
|
||||
"href": "https://studentersamfundet.no/wp-json/wp/v2/users/2150"
|
||||
}
|
||||
],
|
||||
"version-history": [
|
||||
{
|
||||
"count": 1,
|
||||
"href": "https://studentersamfundet.no/wp-json/wp/v2/events/64573/revisions"
|
||||
}
|
||||
],
|
||||
"predecessor-version": [
|
||||
{
|
||||
"id": 64574,
|
||||
"href": "https://studentersamfundet.no/wp-json/wp/v2/events/64573/revisions/64574"
|
||||
}
|
||||
],
|
||||
"wp:featuredmedia": [
|
||||
{
|
||||
"embeddable": true,
|
||||
"href": "https://studentersamfundet.no/wp-json/wp/v2/media/64585"
|
||||
}
|
||||
],
|
||||
"wp:attachment": [
|
||||
{
|
||||
"href": "https://studentersamfundet.no/wp-json/wp/v2/media?parent=64573"
|
||||
}
|
||||
],
|
||||
"wp:term": [
|
||||
{
|
||||
"taxonomy": "event_type",
|
||||
"embeddable": true,
|
||||
"href": "https://studentersamfundet.no/wp-json/wp/v2/event_types?post=64573"
|
||||
},
|
||||
{
|
||||
"taxonomy": "event_organizer",
|
||||
"embeddable": true,
|
||||
"href": "https://studentersamfundet.no/wp-json/wp/v2/event_organizers?post=64573"
|
||||
}
|
||||
],
|
||||
"curies": [
|
||||
{
|
||||
"name": "wp",
|
||||
"href": "https://api.w.org/{rel}",
|
||||
"templated": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
|
||||
# Create your tests here.
|
||||
@@ -1,3 +1,4 @@
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from wagtail.admin.viewsets.chooser import ChooserViewSet
|
||||
|
||||
|
||||
@@ -5,10 +6,10 @@ class EventOrganizerChooserViewSet(ChooserViewSet):
|
||||
model = "events.EventOrganizer"
|
||||
icon = "group"
|
||||
per_page = 30
|
||||
page_title = "Choose organizers"
|
||||
choose_one_text = "Choose an organizer"
|
||||
choose_another_text = "Choose another organizer"
|
||||
edit_item_text = "Edit this organizer"
|
||||
page_title = _("Choose organizers")
|
||||
choose_one_text = _("Choose an organizer")
|
||||
choose_another_text = _("Choose another organizer")
|
||||
edit_item_text = _("Edit this organizer")
|
||||
form_fields = ["name", "association", "external_url"]
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
from wagtail import hooks
|
||||
|
||||
from .admin import event_page_listing_viewset
|
||||
from .views import event_organizer_chooser_viewset
|
||||
|
||||
|
||||
@hooks.register("register_admin_viewset")
|
||||
def register_viewset():
|
||||
return event_organizer_chooser_viewset
|
||||
|
||||
|
||||
@hooks.register("register_admin_viewset")
|
||||
def register_event_page_listing_viewset():
|
||||
return event_page_listing_viewset
|
||||
|
||||
@@ -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.models import Page
|
||||
from wagtail.search import index
|
||||
from wagtail_headless_preview.models import HeadlessMixin
|
||||
|
||||
from dnscms.blocks import PageSectionBlock
|
||||
from dnscms.fields import BASE_BLOCKS
|
||||
from dnscms.options import ALL_PIGS
|
||||
|
||||
|
||||
class GenericPage(Page):
|
||||
class GenericPage(HeadlessMixin, Page):
|
||||
subpage_types = ["generic.GenericPage"]
|
||||
show_in_menus = True
|
||||
|
||||
|
||||
@@ -10,9 +10,10 @@ from wagtail.admin.panels import (
|
||||
PageChooserPanel,
|
||||
)
|
||||
from wagtail.models import Orderable, Page
|
||||
from wagtail_headless_preview.models import HeadlessMixin
|
||||
|
||||
|
||||
class HomePage(Page):
|
||||
class HomePage(HeadlessMixin, Page):
|
||||
max_count = 1
|
||||
|
||||
content_panels = Page.content_panels + [
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block body_class %}template-homepage{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
|
||||
{% comment %}
|
||||
Delete the line below if you're just getting started and want to remove the welcome screen!
|
||||
{% endcomment %}
|
||||
<link rel="stylesheet" href="{% static 'css/welcome_page.css' %}">
|
||||
{% endblock extra_css %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% comment %}
|
||||
Delete the line below if you're just getting started and want to remove the welcome screen!
|
||||
{% endcomment %}
|
||||
{% include 'home/welcome_page.html' %}
|
||||
|
||||
{% endblock content %}
|
||||
@@ -1,52 +0,0 @@
|
||||
{% load i18n wagtailcore_tags %}
|
||||
|
||||
<header class="header">
|
||||
<div class="logo">
|
||||
<a href="https://wagtail.org/">
|
||||
<svg class="figure-logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 342.5 126.2"><title>{% trans "Visit the Wagtail website" %}</title><path fill="#FFF" d="M84 1.9v5.7s-10.2-3.8-16.8 3.1c-4.8 5-5.2 10.6-3 18.1 21.6 0 25 12.1 25 12.1L87 27l6.8-8.3c0-9.8-8.1-16.3-9.8-16.8z"/><circle cx="85.9" cy="15.9" r="2.6"/><path d="M89.2 40.9s-3.3-16.6-24.9-12.1c-2.2-7.5-1.8-13 3-18.1C73.8 3.8 84 7.6 84 7.6V1.9C80.4.3 77 0 73.2 0 59.3 0 51.6 10.4 48.3 17.4L9.2 89.3l11-2.1-20.2 39 14.1-2.5L24.9 93c30.6 0 69.8-11 64.3-52.1z"/><path d="M102.4 27l-8.6-8.3L87 27z"/><path fill="#FFF" d="M30 84.1s1-.2 2.8-.6c1.8-.4 4.3-1 7.3-1.8 1.5-.4 3.1-.9 4.8-1.5 1.7-.6 3.5-1.2 5.2-2 1.8-.7 3.6-1.6 5.4-2.6 1.8-1 3.5-2.1 5.1-3.4.4-.3.8-.6 1.2-1l1.2-1c.7-.7 1.5-1.4 2.2-2.2.7-.7 1.3-1.5 1.9-2.3l.9-1.2.4-.6.4-.6c.2-.4.5-.8.7-1.2.2-.4.4-.8.7-1.2l.3-.6.3-.6c.2-.4.4-.8.5-1.2l.9-2.4c.2-.8.5-1.6.7-2.3.2-.7.3-1.5.5-2.1.1-.7.2-1.3.3-2 .1-.6.2-1.2.2-1.7.1-.5.1-1 .2-1.5.1-1.8.1-2.8.1-2.8l1.6.1s-.1 1.1-.2 2.9c-.1.5-.1 1-.2 1.5-.1.6-.1 1.2-.3 1.8-.1.6-.3 1.3-.4 2-.2.7-.4 1.4-.6 2.2-.2.8-.5 1.5-.8 2.4-.3.8-.6 1.6-1 2.5l-.6 1.2-.3.6-.3.6c-.2.4-.5.8-.7 1.3-.3.4-.5.8-.8 1.2-.1.2-.3.4-.4.6l-.4.6-.9 1.2c-.7.8-1.3 1.6-2.1 2.3-.7.8-1.5 1.4-2.3 2.2l-1.2 1c-.4.3-.8.6-1.3.9-1.7 1.2-3.5 2.3-5.3 3.3-1.8.9-3.7 1.8-5.5 2.5-1.8.7-3.6 1.3-5.3 1.8-1.7.5-3.3 1-4.9 1.3-3 .7-5.6 1.3-7.4 1.6-1.6.6-2.6.8-2.6.8z"/><g fill="#231F20"><path d="M127 83.9h-8.8l-12.6-36.4h7.9l9 27.5 9-27.5h7.9l9 27.5 9-27.5h7.9L153 83.9h-8.8L135.6 59 127 83.9zM200.1 83.9h-7V79c-3 3.6-7 5.4-12.1 5.4-3.8 0-6.9-1.1-9.4-3.2s-3.7-5-3.7-8.6c0-3.6 1.3-6.3 4-8 2.6-1.8 6.2-2.7 10.7-2.7h9.9v-1.4c0-4.8-2.7-7.3-8.1-7.3-3.4 0-6.9 1.2-10.5 3.7l-3.4-4.8c4.4-3.5 9.4-5.3 15.1-5.3 4.3 0 7.8 1.1 10.5 3.2 2.7 2.2 4.1 5.6 4.1 10.2v23.7zm-7.7-13.6v-3.1h-8.6c-5.5 0-8.3 1.7-8.3 5.2 0 1.8.7 3.1 2.1 4.1 1.4.9 3.3 1.4 5.7 1.4 2.4 0 4.6-.7 6.4-2.1 1.8-1.3 2.7-3.1 2.7-5.5zM241.7 47.5v31.7c0 6.4-1.7 11.3-5.2 14.5-3.5 3.2-8 4.8-13.4 4.8-5.5 0-10.4-1.7-14.8-5.1l3.6-5.8c3.6 2.7 7.1 4 10.8 4 3.6 0 6.5-.9 8.6-2.8 2.1-1.9 3.2-4.9 3.2-9v-4.7c-1.1 2.1-2.8 3.9-4.9 5.1-2.1 1.3-4.5 1.9-7.1 1.9-4.8 0-8.8-1.7-11.9-5.1-3.1-3.4-4.7-7.6-4.7-12.6s1.6-9.2 4.7-12.6c3.1-3.4 7.1-5.1 11.9-5.1 4.8 0 8.7 2 11.7 6v-5.4h7.5zm-28.4 16.8c0 3 .9 5.6 2.8 7.7 1.8 2.2 4.3 3.2 7.5 3.2 3.1 0 5.7-1 7.6-3.1 1.9-2.1 2.9-4.7 2.9-7.8 0-3.1-1-5.8-2.9-7.9-2-2.2-4.5-3.2-7.6-3.2-3.1 0-5.6 1.1-7.4 3.4-2 2.1-2.9 4.7-2.9 7.7zM260.9 53.6v18.5c0 1.7.5 3.1 1.4 4.1.9 1 2.2 1.5 3.8 1.5 1.6 0 3.2-.8 4.7-2.4l3.1 5.4c-2.7 2.4-5.7 3.6-8.9 3.6-3.3 0-6-1.1-8.3-3.4-2.3-2.3-3.5-5.3-3.5-9.1V53.6h-4.6v-6.2h4.6V36.1h7.7v11.4h9.6v6.2h-9.6zM309.5 83.9h-7V79c-3 3.6-7 5.4-12.1 5.4-3.8 0-6.9-1.1-9.4-3.2s-3.7-5-3.7-8.6c0-3.6 1.3-6.3 4-8 2.6-1.8 6.2-2.7 10.7-2.7h9.9v-1.4c0-4.8-2.7-7.3-8.1-7.3-3.4 0-6.9 1.2-10.5 3.7l-3.4-4.8c4.4-3.5 9.4-5.3 15.1-5.3 4.3 0 7.8 1.1 10.5 3.2 2.7 2.2 4.1 5.6 4.1 10.2v23.7zm-7.7-13.6v-3.1h-8.6c-5.5 0-8.3 1.7-8.3 5.2 0 1.8.7 3.1 2.1 4.1 1.4.9 3.3 1.4 5.7 1.4 2.4 0 4.6-.7 6.4-2.1 1.8-1.3 2.7-3.1 2.7-5.5zM319.3 40.2c-1-1-1.4-2.1-1.4-3.4 0-1.3.5-2.5 1.4-3.4 1-1 2.1-1.4 3.4-1.4 1.3 0 2.5.5 3.4 1.4 1 1 1.4 2.1 1.4 3.4 0 1.3-.5 2.5-1.4 3.4s-2.1 1.4-3.4 1.4c-1.3.1-2.4-.4-3.4-1.4zm7.2 43.7h-7.7V47.5h7.7v36.4zM342.5 83.9h-7.7V33.1h7.7v50.8z"/></g></svg>
|
||||
</a>
|
||||
</div>
|
||||
<div class="header-link">
|
||||
{% comment %}
|
||||
This works for all cases but prerelease versions:
|
||||
{% endcomment %}
|
||||
<a href="{% wagtail_documentation_path %}/releases/{% wagtail_release_notes_path %}">
|
||||
{% trans "View the release notes" %}
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
<main class="main">
|
||||
<div class="figure">
|
||||
<svg class="figure-space" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300" aria-hidden="true">
|
||||
<path class="egg" fill="currentColor" d="M150 250c-42.741 0-75-32.693-75-90s42.913-110 75-110c32.088 0 75 52.693 75 110s-32.258 90-75 90z"/>
|
||||
<ellipse fill="#ddd" cx="150" cy="270" rx="40" ry="7"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="main-text">
|
||||
<h1>{% trans "Welcome to your new Wagtail site!" %}</h1>
|
||||
<p>{% trans 'Please feel free to <a href="https://github.com/wagtail/wagtail/wiki/Slack">join our community on Slack</a>, or get started with one of the links below.' %}</p>
|
||||
</div>
|
||||
</main>
|
||||
<footer class="footer" role="contentinfo">
|
||||
<a class="option option-one" href="{% wagtail_documentation_path %}/">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true"><path d="M9 21c0 .5.4 1 1 1h4c.6 0 1-.5 1-1v-1H9v1zm3-19C8.1 2 5 5.1 5 9c0 2.4 1.2 4.5 3 5.7V17c0 .5.4 1 1 1h6c.6 0 1-.5 1-1v-2.3c1.8-1.3 3-3.4 3-5.7 0-3.9-3.1-7-7-7zm2.9 11.1l-.9.6V16h-4v-2.3l-.9-.6C7.8 12.2 7 10.6 7 9c0-2.8 2.2-5 5-5s5 2.2 5 5c0 1.6-.8 3.2-2.1 4.1z"/></svg>
|
||||
<div>
|
||||
<h2>{% trans "Wagtail Documentation" %}</h2>
|
||||
<p>{% trans "Topics, references, & how-tos" %}</p>
|
||||
</div>
|
||||
</a>
|
||||
<a class="option option-two" href="{% wagtail_documentation_path %}/getting_started/tutorial.html">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z"/></svg>
|
||||
<div>
|
||||
<h2>{% trans "Tutorial" %}</h2>
|
||||
<p>{% trans "Build your first Wagtail site" %}</p>
|
||||
</div>
|
||||
</a>
|
||||
<a class="option option-three" href="{% url 'wagtailadmin_home' %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true"><path d="M0 0h24v24H0z" fill="none"/><path d="M16.5 13c-1.2 0-3.07.34-4.5 1-1.43-.67-3.3-1-4.5-1C5.33 13 1 14.08 1 16.25V19h22v-2.75c0-2.17-4.33-3.25-6.5-3.25zm-4 4.5h-10v-1.25c0-.54 2.56-1.75 5-1.75s5 1.21 5 1.75v1.25zm9 0H14v-1.25c0-.46-.2-.86-.52-1.22.88-.3 1.96-.53 3.02-.53 2.44 0 5 1.21 5 1.75v1.25zM7.5 12c1.93 0 3.5-1.57 3.5-3.5S9.43 5 7.5 5 4 6.57 4 8.5 5.57 12 7.5 12zm0-5.5c1.1 0 2 .9 2 2s-.9 2-2 2-2-.9-2-2 .9-2 2-2zm9 5.5c1.93 0 3.5-1.57 3.5-3.5S18.43 5 16.5 5 13 6.57 13 8.5s1.57 3.5 3.5 3.5zm0-5.5c1.1 0 2 .9 2 2s-.9 2-2 2-2-.9-2-2 .9-2 2-2z"/></svg>
|
||||
<div>
|
||||
<h2>{% trans "Admin Interface" %}</h2>
|
||||
<p>{% trans "Create your superuser first!" %}</p>
|
||||
</div>
|
||||
</a>
|
||||
</footer>
|
||||
Binary file not shown.
@@ -0,0 +1,308 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: dnscms\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-05-19 21:17+0200\n"
|
||||
"Language: nb\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: events/admin.py:23
|
||||
msgid "%Y-%m-%d at %H:%M"
|
||||
msgstr "%Y-%m-%d kl %H:%M"
|
||||
|
||||
#: events/admin.py:25
|
||||
#, python-format
|
||||
msgid "%(count)d occurrence"
|
||||
msgid_plural "%(count)d occurrences"
|
||||
msgstr[0] "%(count)d forekomst"
|
||||
msgstr[1] "%(count)d forekomster"
|
||||
|
||||
#: events/admin.py:70
|
||||
msgid "Events"
|
||||
msgstr "Arrangementer"
|
||||
|
||||
#: events/admin.py:76
|
||||
msgid "Title"
|
||||
msgstr "Tittel"
|
||||
|
||||
#: events/admin.py:77
|
||||
msgid "Date"
|
||||
msgstr "Dato"
|
||||
|
||||
#: events/admin.py:78 events/models.py:333
|
||||
msgid "Organizers"
|
||||
msgstr "Arrangører"
|
||||
|
||||
#: events/admin.py:81
|
||||
msgid "Updated"
|
||||
msgstr "Oppdatert"
|
||||
|
||||
#: events/admin.py:85
|
||||
msgid "Status"
|
||||
msgstr "Status"
|
||||
|
||||
#: events/models.py:73 events/models.py:156
|
||||
msgid "slug"
|
||||
msgstr "permalenke"
|
||||
|
||||
#: events/models.py:75
|
||||
msgid "The name of the category as it will appear in URLs."
|
||||
msgstr "Navnet på kategorien slik det vil vises i URL-er."
|
||||
|
||||
#: events/models.py:79
|
||||
msgid "Should this category be available as a filter in the event programme?"
|
||||
msgstr "Skal denne kategorien være mulig å filtrere på i programmet?"
|
||||
|
||||
#: events/models.py:83 events/models.py:266
|
||||
msgid "None"
|
||||
msgstr "Ingen"
|
||||
|
||||
#: events/models.py:91
|
||||
msgid "Default pig for events of this kind."
|
||||
msgstr "Standardgris for arrangementer av denne typen."
|
||||
|
||||
#: events/models.py:98 events/models.py:343
|
||||
msgid "Pig"
|
||||
msgstr "Gris"
|
||||
|
||||
#: events/models.py:109
|
||||
msgid "event category"
|
||||
msgstr "arrangementskategori"
|
||||
|
||||
#: events/models.py:110
|
||||
msgid "event categories"
|
||||
msgstr "arrangementskategorier"
|
||||
|
||||
#: events/models.py:138
|
||||
msgid "organizer"
|
||||
msgstr "arrangør"
|
||||
|
||||
#: events/models.py:139
|
||||
msgid "organizers"
|
||||
msgstr "arrangører"
|
||||
|
||||
#: events/models.py:158
|
||||
msgid "The name of the organizer as it will appear in URLs."
|
||||
msgstr "Navnet på arrangøren slik det vil vises i URL-er."
|
||||
|
||||
#: events/models.py:167
|
||||
msgid "If a DNS association or committee is behind it, choose it here."
|
||||
msgstr "Om en samfundsforening eller -utvalg står bak, velg det her."
|
||||
|
||||
#: events/models.py:173
|
||||
msgid "Link to the external organizer's website"
|
||||
msgstr "Lenke til nettstedet til ekstern arrangør"
|
||||
|
||||
#: events/models.py:182
|
||||
msgid "Internal organizer"
|
||||
msgstr "Intern arrangør"
|
||||
|
||||
#: events/models.py:185
|
||||
msgid "External organizer"
|
||||
msgstr "Ekstern arrangør"
|
||||
|
||||
#: events/models.py:189
|
||||
msgid "Website"
|
||||
msgstr "Nettsted"
|
||||
|
||||
#: events/models.py:190
|
||||
msgid "Leave this empty if the organizer exists in the list above."
|
||||
msgstr "La denne stå tom om arrangøren finnes i lista over."
|
||||
|
||||
#: events/models.py:204
|
||||
msgid "event organizer"
|
||||
msgstr "arrangør"
|
||||
|
||||
#: events/models.py:205
|
||||
msgid "event organizers"
|
||||
msgstr "arrangører"
|
||||
|
||||
#: events/models.py:239
|
||||
msgid ""
|
||||
"Choose an image for use in the programme and other surfaces. Should be a "
|
||||
"photo or an illustration without too much text – don't reuse a Facebook "
|
||||
"cover uncritically!"
|
||||
msgstr ""
|
||||
"Velg et bilde til bruk i programmet og andre visningsflater. Bør være et "
|
||||
"bilde eller en illustrasjon uten for mye tekst – ikke gjenbruk et Facebook-"
|
||||
"cover ukritisk!"
|
||||
|
||||
#: events/models.py:249
|
||||
msgid ""
|
||||
"A short text that appears right below the title. Feel free to leave it empty "
|
||||
"if you fit most of it in the main title."
|
||||
msgstr ""
|
||||
"En kort tekst som kommer rett under tittelen. La denne gjerne stå tom om du "
|
||||
"fikk plass til det meste i hovedtittelen."
|
||||
|
||||
#: events/models.py:267
|
||||
msgid "Automatic"
|
||||
msgstr "Automatisk"
|
||||
|
||||
#: events/models.py:276
|
||||
msgid ""
|
||||
"The pig that hangs out on the event page. Automatic causes one to be chosen "
|
||||
"based on the event's category."
|
||||
msgstr ""
|
||||
"Grisen som henger på arrangementssiden. Automatisk fører til at en velges "
|
||||
"basert på arrangementets kategori."
|
||||
|
||||
#: events/models.py:284
|
||||
msgid "Direct link to ticket purchase, e.g. TicketCo, Billetto or Ticketmaster"
|
||||
msgstr "Lenke direkte til billettkjøp, f.eks. TicketCo, Billetto eller Ticketmaster"
|
||||
|
||||
#: events/models.py:289
|
||||
msgid "Direct link to the event on Facebook"
|
||||
msgstr "Lenke direkte til arrangementet på Facebook"
|
||||
|
||||
#: events/models.py:299
|
||||
msgid "Free"
|
||||
msgstr "Gratis"
|
||||
|
||||
#: events/models.py:299
|
||||
msgid "Is this event free for everyone?"
|
||||
msgstr "Er dette arrangementet gratis for alle?"
|
||||
|
||||
#: events/models.py:305
|
||||
msgid "Regular price"
|
||||
msgstr "Ordinær pris"
|
||||
|
||||
#: events/models.py:306
|
||||
msgid "Price for students"
|
||||
msgstr "Pris for studenter"
|
||||
|
||||
#: events/models.py:307
|
||||
msgid "Price for DNS members"
|
||||
msgstr "Pris for medlemmer av DNS"
|
||||
|
||||
#: events/models.py:314
|
||||
msgid ""
|
||||
"Write <strong>0</strong> for free. An empty field hides the price category. "
|
||||
"If possible, write digits only."
|
||||
msgstr ""
|
||||
"Skriv <strong>0</strong> om gratis. Tomt felt skjuler priskategorien. Om "
|
||||
"mulig, skriv kun tall."
|
||||
|
||||
#: events/models.py:323
|
||||
msgid "Ticket purchase link"
|
||||
msgstr "Billettkjøpslenke"
|
||||
|
||||
#: events/models.py:327
|
||||
msgid "Subtitle"
|
||||
msgstr "Undertittel"
|
||||
|
||||
#: events/models.py:329
|
||||
msgid "Lead"
|
||||
msgstr "Ingress"
|
||||
|
||||
#: events/models.py:336
|
||||
msgid "Who is behind the event?"
|
||||
msgstr "Hvem står bak arrangementet?"
|
||||
|
||||
#: events/models.py:339
|
||||
msgid "Organizer"
|
||||
msgstr "Arrangør"
|
||||
|
||||
#: events/models.py:346
|
||||
msgid "Facebook link"
|
||||
msgstr "Facebook-lenke"
|
||||
|
||||
#: events/models.py:347
|
||||
msgid "Direct link to the event on Facebook."
|
||||
msgstr "Lenke direkte til arrangementet på Facebook."
|
||||
|
||||
#: events/models.py:349
|
||||
msgid "Pricing and tickets"
|
||||
msgstr "Priser og billettkjøp"
|
||||
|
||||
#: events/models.py:351
|
||||
msgid "Date, time and venue"
|
||||
msgstr "Dato, tid og lokale"
|
||||
|
||||
#: events/models.py:355
|
||||
msgid "If the event spans several days, add each day as a separate occurrence."
|
||||
msgstr "Om arrangementet går over flere dager, legg inn hver dag som en egen forekomst."
|
||||
|
||||
#: events/models.py:359
|
||||
msgid "Occurrence"
|
||||
msgstr "Forekomst"
|
||||
|
||||
#: events/models.py:402
|
||||
msgid "event"
|
||||
msgstr "arrangement"
|
||||
|
||||
#: events/models.py:403
|
||||
msgid "events"
|
||||
msgstr "arrangementer"
|
||||
|
||||
#: events/models.py:563
|
||||
msgid ""
|
||||
"Use this <em>if none of the venues that can be selected on the left</em> "
|
||||
"fit. E.g. <em>Frederikkeplassen</em> or <em>Sirkusteltet</em>."
|
||||
msgstr ""
|
||||
"Bruk denne <em>om ingen av lokalene som kan velges til venstre</em> passer. "
|
||||
"F.eks. <em>Frederikkeplassen</em> eller <em>Sirkusteltet</em>."
|
||||
|
||||
#: events/models.py:572
|
||||
msgid "Start"
|
||||
msgstr "Start"
|
||||
|
||||
#: events/models.py:573
|
||||
msgid "End"
|
||||
msgstr "Slutt"
|
||||
|
||||
#: events/models.py:578
|
||||
msgid "Venue"
|
||||
msgstr "Lokale"
|
||||
|
||||
#: events/models.py:579
|
||||
msgid "Venue as free text"
|
||||
msgstr "Lokale som fritekst"
|
||||
|
||||
#: events/models.py:596
|
||||
msgid "You can't both pick a venue and write something in this field."
|
||||
msgstr "Du kan ikke både velge et lokale og skrive noe i dette feltet."
|
||||
|
||||
#: events/models.py:601
|
||||
msgid "Venue is required."
|
||||
msgstr "Lokale er påkrevd."
|
||||
|
||||
#: events/models.py:607
|
||||
msgid "occurrence"
|
||||
msgstr "forekomst"
|
||||
|
||||
#: events/models.py:608
|
||||
msgid "occurrences"
|
||||
msgstr "forekomster"
|
||||
|
||||
#: events/views.py:9
|
||||
msgid "Choose organizers"
|
||||
msgstr "Velg arrangører"
|
||||
|
||||
#: events/views.py:10
|
||||
msgid "Choose an organizer"
|
||||
msgstr "Velg en arrangør"
|
||||
|
||||
#: events/views.py:11
|
||||
msgid "Choose another organizer"
|
||||
msgstr "Velg en annen arrangør"
|
||||
|
||||
#: events/views.py:12
|
||||
msgid "Edit this organizer"
|
||||
msgstr "Rediger denne arrangøren"
|
||||
|
||||
#: images/models.py:40
|
||||
msgid "image"
|
||||
msgstr "bilde"
|
||||
|
||||
#: images/models.py:41
|
||||
msgid "images"
|
||||
msgstr "bilder"
|
||||
+8
-3
@@ -1,3 +1,8 @@
|
||||
[tools]
|
||||
python = "3.14"
|
||||
uv = "latest"
|
||||
# Translation tasks
|
||||
[tasks.compilemessages]
|
||||
description = "Compile translation files for Norwegian Bokmal"
|
||||
run = "uv run manage.py compilemessages -l nb"
|
||||
|
||||
[tasks.makemessages]
|
||||
description = "Extract translation strings for Norwegian Bokmal"
|
||||
run = "uv run manage.py makemessages -l nb"
|
||||
|
||||
@@ -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.models import Page
|
||||
from wagtail.search import index
|
||||
from wagtail_headless_preview.models import HeadlessMixin
|
||||
|
||||
from dnscms.fields import CommonStreamField
|
||||
from dnscms.wordpress.models import WPImportedPageMixin
|
||||
|
||||
|
||||
@register_singular_query_field("newsIndex")
|
||||
class NewsIndex(Page):
|
||||
class NewsIndex(HeadlessMixin, Page):
|
||||
max_count = 1
|
||||
subpage_types = ["news.NewsPage"]
|
||||
|
||||
@@ -28,7 +29,7 @@ class NewsIndex(Page):
|
||||
search_fields = []
|
||||
|
||||
|
||||
class NewsPage(WPImportedPageMixin, Page):
|
||||
class NewsPage(HeadlessMixin, WPImportedPageMixin, Page):
|
||||
subpage_types = []
|
||||
parent_page_types = ["news.NewsIndex"]
|
||||
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 = ""
|
||||
authors = [{ name = "EDB", email = "edb@neuf.no" }]
|
||||
requires-python = ">=3.14, <3.15"
|
||||
readme = "README.md"
|
||||
dependencies = [
|
||||
"wagtail>=7.3.1",
|
||||
"wagtail-grapple>=0.29.0",
|
||||
"django>=6.0.3",
|
||||
"django-extensions>=4.1",
|
||||
"psycopg2-binary>=2.9.11,<3",
|
||||
"gunicorn>=25.1.0",
|
||||
"whitenoise>=6.12.0",
|
||||
"wagtail>=7.4.1,<8",
|
||||
"wagtail-grapple>=0.31.0,<0.32",
|
||||
"wagtail-headless-preview>=0.8,<0.9",
|
||||
"django>=6.0.5,<7",
|
||||
"django-extensions>=4.1,<5",
|
||||
"psycopg2-binary>=2.9.12,<3",
|
||||
"gunicorn>=26.0.0,<27",
|
||||
"whitenoise>=6.12.0,<7",
|
||||
]
|
||||
|
||||
[dependency-groups]
|
||||
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]
|
||||
@@ -34,3 +38,19 @@ line-length = 99
|
||||
select = ["F", "E", "W", "Q", "UP", "DJ"]
|
||||
ignore = []
|
||||
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.models import Page
|
||||
from wagtail.search import index
|
||||
from wagtail_headless_preview.models import HeadlessMixin
|
||||
|
||||
from dnscms.blocks import BASE_BLOCKS
|
||||
|
||||
@@ -34,7 +35,7 @@ class SponsorBlock(blocks.StructBlock):
|
||||
|
||||
|
||||
@register_singular_query_field("sponsorsPage")
|
||||
class SponsorsPage(Page):
|
||||
class SponsorsPage(HeadlessMixin, Page):
|
||||
max_count = 1
|
||||
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,446 @@
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import pytest
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils import timezone
|
||||
|
||||
from events.admin import EventDateColumn, OrganizersColumn
|
||||
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")
|
||||
|
||||
|
||||
def test_event_date_column_no_occurrences(event_index):
|
||||
event = EventPageFactory(parent=event_index)
|
||||
column = EventDateColumn("event_date")
|
||||
|
||||
assert column.get_value(event) == "—"
|
||||
|
||||
|
||||
def test_event_date_column_single_occurrence(event_index):
|
||||
event = EventPageFactory(parent=event_index)
|
||||
start = timezone.make_aware(datetime(2025, 7, 22, 19, 30))
|
||||
EventOccurrence.objects.create(event=event, start=start, venue_custom="X")
|
||||
column = EventDateColumn("event_date")
|
||||
|
||||
assert column.get_value(event) == "2025-07-22 kl 19:30"
|
||||
|
||||
|
||||
def test_event_date_column_multiple_occurrences_shows_count(event_index):
|
||||
event = EventPageFactory(parent=event_index)
|
||||
now = timezone.now()
|
||||
EventOccurrence.objects.create(event=event, start=now, venue_custom="X")
|
||||
EventOccurrence.objects.create(event=event, start=now + timedelta(days=1), venue_custom="X")
|
||||
EventOccurrence.objects.create(event=event, start=now + timedelta(days=2), venue_custom="X")
|
||||
column = EventDateColumn("event_date")
|
||||
|
||||
assert column.get_value(event) == "3 forekomster"
|
||||
|
||||
|
||||
def test_organizers_column_no_organizers(event_index):
|
||||
event = EventPageFactory(parent=event_index)
|
||||
column = OrganizersColumn("organizers")
|
||||
|
||||
assert column.get_value(event) == "—"
|
||||
|
||||
|
||||
def test_organizers_column_single_organizer_shows_name(event_index):
|
||||
org = EventOrganizer.objects.create(name="Forening A", slug="forening-a")
|
||||
event = EventPageFactory(parent=event_index)
|
||||
EventOrganizerLink.objects.create(event=event, organizer=org)
|
||||
column = OrganizersColumn("organizers")
|
||||
|
||||
assert column.get_value(event) == "Forening A"
|
||||
|
||||
|
||||
def test_organizers_column_multiple_organizers_truncates_with_count(event_index):
|
||||
org_a = EventOrganizer.objects.create(name="Forening A", slug="forening-a")
|
||||
org_b = EventOrganizer.objects.create(name="Forening B", slug="forening-b")
|
||||
org_c = EventOrganizer.objects.create(name="Forening C", slug="forening-c")
|
||||
event = EventPageFactory(parent=event_index)
|
||||
EventOrganizerLink.objects.create(event=event, organizer=org_a, sort_order=0)
|
||||
EventOrganizerLink.objects.create(event=event, organizer=org_b, sort_order=1)
|
||||
EventOrganizerLink.objects.create(event=event, organizer=org_c, sort_order=2)
|
||||
column = OrganizersColumn("organizers")
|
||||
|
||||
assert column.get_value(event) == "Forening A (+2)"
|
||||
|
||||
|
||||
@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" },
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "defusedxml"
|
||||
version = "0.7.1"
|
||||
@@ -73,16 +121,16 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "django"
|
||||
version = "6.0.3"
|
||||
version = "6.0.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "asgiref" },
|
||||
{ name = "sqlparse" },
|
||||
{ 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 = [
|
||||
{ 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]]
|
||||
@@ -111,14 +159,14 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "django-modelcluster"
|
||||
version = "6.4.1"
|
||||
version = "6.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ 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 = [
|
||||
{ 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]]
|
||||
@@ -174,14 +222,14 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "django-treebeard"
|
||||
version = "4.7.1"
|
||||
version = "5.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ 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 = [
|
||||
{ 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]]
|
||||
@@ -207,27 +255,39 @@ dependencies = [
|
||||
{ name = "psycopg2-binary" },
|
||||
{ name = "wagtail" },
|
||||
{ name = "wagtail-grapple" },
|
||||
{ name = "wagtail-headless-preview" },
|
||||
{ name = "whitenoise" },
|
||||
]
|
||||
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "pytest" },
|
||||
{ name = "pytest-cov" },
|
||||
{ name = "pytest-django" },
|
||||
{ name = "ruff" },
|
||||
{ name = "wagtail-factories" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "django", specifier = ">=6.0.3" },
|
||||
{ name = "django-extensions", specifier = ">=4.1" },
|
||||
{ name = "gunicorn", specifier = ">=25.1.0" },
|
||||
{ name = "psycopg2-binary", specifier = ">=2.9.11,<3" },
|
||||
{ name = "wagtail", specifier = ">=7.3.1" },
|
||||
{ name = "wagtail-grapple", specifier = ">=0.29.0" },
|
||||
{ name = "whitenoise", specifier = ">=6.12.0" },
|
||||
{ name = "django", specifier = ">=6.0.5,<7" },
|
||||
{ name = "django-extensions", specifier = ">=4.1,<5" },
|
||||
{ name = "gunicorn", specifier = ">=26.0.0,<27" },
|
||||
{ name = "psycopg2-binary", specifier = ">=2.9.12,<3" },
|
||||
{ name = "wagtail", specifier = ">=7.4.1,<8" },
|
||||
{ name = "wagtail-grapple", specifier = ">=0.31.0,<0.32" },
|
||||
{ name = "wagtail-headless-preview", specifier = ">=0.8,<0.9" },
|
||||
{ name = "whitenoise", specifier = ">=6.12.0,<7" },
|
||||
]
|
||||
|
||||
[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]]
|
||||
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" },
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "filetype"
|
||||
version = "1.2.0"
|
||||
@@ -311,14 +395,14 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "gunicorn"
|
||||
version = "25.1.0"
|
||||
version = "26.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ 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 = [
|
||||
{ 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]]
|
||||
@@ -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" },
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "laces"
|
||||
version = "0.1.2"
|
||||
@@ -344,15 +437,15 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "modelsearch"
|
||||
version = "1.1.1"
|
||||
version = "1.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "django" },
|
||||
{ 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 = [
|
||||
{ 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]]
|
||||
@@ -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" },
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "promise"
|
||||
version = "2.3"
|
||||
@@ -435,21 +537,72 @@ sdist = { url = "https://files.pythonhosted.org/packages/cf/9c/fb5d48abfe5d791cd
|
||||
|
||||
[[package]]
|
||||
name = "psycopg2-binary"
|
||||
version = "2.9.11"
|
||||
version = "2.9.12"
|
||||
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 = [
|
||||
{ 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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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]]
|
||||
@@ -481,27 +634,27 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.15.1"
|
||||
version = "0.15.13"
|
||||
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 = [
|
||||
{ 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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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]]
|
||||
@@ -578,7 +731,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "wagtail"
|
||||
version = "7.3.1"
|
||||
version = "7.4.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyascii" },
|
||||
@@ -600,23 +753,36 @@ dependencies = [
|
||||
{ name = "telepath" },
|
||||
{ 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 = [
|
||||
{ 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]]
|
||||
name = "wagtail-grapple"
|
||||
version = "0.29.0"
|
||||
version = "0.31.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "graphene-django" },
|
||||
{ name = "wagtail" },
|
||||
{ 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 = [
|
||||
{ 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]]
|
||||
|
||||
@@ -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.models import Page
|
||||
from wagtail.search import index
|
||||
from wagtail_headless_preview.models import HeadlessMixin
|
||||
|
||||
from dnscms.blocks import ImageSliderBlock
|
||||
from dnscms.fields import CommonStreamField
|
||||
@@ -18,7 +19,7 @@ from dnscms.wordpress.models import WPImportedPageMixin
|
||||
|
||||
|
||||
@register_singular_query_field("venueIndex")
|
||||
class VenueIndex(Page):
|
||||
class VenueIndex(HeadlessMixin, Page):
|
||||
# there can only be one venue index page
|
||||
max_count = 1
|
||||
subpage_types = ["venues.VenuePage"]
|
||||
@@ -35,7 +36,7 @@ class VenueIndex(Page):
|
||||
|
||||
|
||||
@register_singular_query_field("venueRentalIndex")
|
||||
class VenueRentalIndex(Page):
|
||||
class VenueRentalIndex(HeadlessMixin, Page):
|
||||
# there can only be one venue index page
|
||||
max_count = 1
|
||||
subpage_types = []
|
||||
@@ -51,7 +52,7 @@ class VenueRentalIndex(Page):
|
||||
graphql_fields = [GraphQLRichText("lead"), GraphQLStreamfield("body")]
|
||||
|
||||
|
||||
class VenuePage(WPImportedPageMixin, Page):
|
||||
class VenuePage(HeadlessMixin, WPImportedPageMixin, Page):
|
||||
# no children
|
||||
subpage_types = []
|
||||
parent_page_types = ["venues.VenueIndex"]
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
[tools]
|
||||
python = "3.14"
|
||||
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
|
||||
|
||||
@@ -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";
|
||||
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 = {
|
||||
schema: process.env.GRAPHQL_ENDPOINT,
|
||||
schema: graphqlEndpoint,
|
||||
documents: ["src/**/*.tsx", "src/**/*.ts"],
|
||||
ignoreNoDocuments: true, // for better experience with the watcher
|
||||
generates: {
|
||||
"./src/gql/": {
|
||||
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: "**",
|
||||
},
|
||||
],
|
||||
dangerouslyAllowLocalIP: process.env.NODE_ENV === "development",
|
||||
},
|
||||
turbopack: {
|
||||
root: __dirname,
|
||||
|
||||
Generated
+1236
-1906
File diff suppressed because it is too large
Load Diff
+13
-14
@@ -10,34 +10,33 @@
|
||||
"codegen": "graphql-codegen"
|
||||
},
|
||||
"dependencies": {
|
||||
"@graphql-codegen/cli": "^6.1.2",
|
||||
"@graphql-codegen/client-preset": "^5.2.3",
|
||||
"@graphql-codegen/cli": "^7.0.0",
|
||||
"@graphql-codegen/client-preset": "^6.0.0",
|
||||
"@parcel/watcher": "^2.5.6",
|
||||
"@sindresorhus/slugify": "^3.0.0",
|
||||
"@urql/next": "^2.0.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"date-fns-tz": "^3.2.0",
|
||||
"graphql": "^16.13.1",
|
||||
"next": "^16.1.6",
|
||||
"graphql": "^16.14.0",
|
||||
"next": "^16.2.6",
|
||||
"nuqs": "^2.8.9",
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4",
|
||||
"react": "19.2.6",
|
||||
"react-dom": "19.2.6",
|
||||
"react-intersection-observer": "^10.0.3",
|
||||
"react-photo-sphere-viewer": "^6.2.3",
|
||||
"sass": "^1.97.3",
|
||||
"sass": "^1.99.0",
|
||||
"sharp": "^0.34.5",
|
||||
"swiper": "^12.1.2",
|
||||
"urql": "^5.0.1",
|
||||
"use-debounce": "^10.1.0"
|
||||
"swiper": "^12.1.4",
|
||||
"urql": "^5.0.2",
|
||||
"use-debounce": "^10.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24",
|
||||
"@types/react": "19.2.14",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"baseline-browser-mapping": "^2.10.0",
|
||||
"baseline-browser-mapping": "^2.10.29",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "16.1.6",
|
||||
"typescript": "^5"
|
||||
"eslint-config-next": "16.2.6",
|
||||
"typescript": "^6"
|
||||
},
|
||||
"overrides": {
|
||||
"@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 { GenericFragment } from "@/gql/graphql";
|
||||
import { getClient } from "@/app/client";
|
||||
import { Metadata, ResolvingMetadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
import { PageHeader } from "@/components/general/PageHeader";
|
||||
import { PageContent } from "@/components/general/PageContent";
|
||||
import { BgPig } from "@/components/general/BgPig";
|
||||
import {
|
||||
GenericPageView,
|
||||
loadGenericPageProps,
|
||||
} from "@/components/general/GenericPageView";
|
||||
import { getSeoMetadata } from "@/lib/seo";
|
||||
|
||||
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 {
|
||||
// for the page /foo/bar we need to look for `/home/foo/bar/`
|
||||
return `/home/${url.join("/")}/`;
|
||||
@@ -78,46 +52,14 @@ export async function generateMetadata(
|
||||
parent: ResolvingMetadata
|
||||
): Promise<Metadata | null> {
|
||||
const { url } = await params;
|
||||
const urlPath = getWagtailUrlPath(url);
|
||||
const { data, error } = await getClient().query(genericPageByUrlPathQuery, {
|
||||
urlPath: urlPath,
|
||||
});
|
||||
|
||||
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;
|
||||
const props = await loadGenericPageProps({ urlPath: getWagtailUrlPath(url) });
|
||||
if (!props) return null;
|
||||
return getSeoMetadata(props.page, parent);
|
||||
}
|
||||
|
||||
export default async function Page({ params }: { params: Params }) {
|
||||
const { url } = await params;
|
||||
const urlPath = getWagtailUrlPath(url);
|
||||
const { data, error } = await getClient().query(genericPageByUrlPathQuery, {
|
||||
urlPath: urlPath,
|
||||
});
|
||||
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" />}
|
||||
</>
|
||||
);
|
||||
const props = await loadGenericPageProps({ urlPath: getWagtailUrlPath(url) });
|
||||
if (!props) return notFound();
|
||||
return <GenericPageView {...props} />;
|
||||
}
|
||||
|
||||
@@ -1,24 +1,13 @@
|
||||
import { Metadata, ResolvingMetadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getClient } from "@/app/client";
|
||||
import { Breadcrumb } from "@/components/general/Breadcrumb";
|
||||
import { ImageFigure } from "@/components/general/Image";
|
||||
import { PageContent } from "@/components/general/PageContent";
|
||||
import {
|
||||
NewsPageView,
|
||||
loadNewsPageProps,
|
||||
} from "@/components/news/NewsPageView";
|
||||
import { graphql } from "@/gql";
|
||||
import { NewsFragment } from "@/gql/graphql";
|
||||
import { formatDate } from "@/lib/date";
|
||||
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() {
|
||||
const allNewsSlugsQuery = graphql(`
|
||||
query allNewsSlugs {
|
||||
@@ -51,62 +40,14 @@ export async function generateMetadata(
|
||||
parent: ResolvingMetadata
|
||||
): Promise<Metadata | null> {
|
||||
const { slug } = await params;
|
||||
const { data, error } = await getClient().query(newsBySlugQuery, {
|
||||
slug,
|
||||
});
|
||||
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;
|
||||
const props = await loadNewsPageProps({ slug });
|
||||
if (!props) return null;
|
||||
return getSeoMetadata(props.news, parent);
|
||||
}
|
||||
|
||||
export default async function Page({ params }: { params: Params }) {
|
||||
const { slug } = await params;
|
||||
const { data, error } = await getClient().query(newsBySlugQuery, {
|
||||
slug,
|
||||
});
|
||||
|
||||
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>
|
||||
);
|
||||
const props = await loadNewsPageProps({ slug });
|
||||
if (!props) return notFound();
|
||||
return <NewsPageView {...props} />;
|
||||
}
|
||||
|
||||
@@ -1,39 +1,19 @@
|
||||
import { getClient } from "@/app/client";
|
||||
import { NewsList } from "@/components/news/NewsList";
|
||||
import { Metadata, ResolvingMetadata } from "next";
|
||||
import { PageHeader } from "@/components/general/PageHeader";
|
||||
import { newsQuery, NewsFragment, NewsIndexFragment } from "@/lib/news";
|
||||
import {
|
||||
NewsIndexView,
|
||||
loadNewsIndexProps,
|
||||
} from "@/components/news/NewsIndexView";
|
||||
import { getSeoMetadata } from "@/lib/seo";
|
||||
|
||||
export async function generateMetadata(
|
||||
{ params }: { params: Promise<{}> },
|
||||
_: unknown,
|
||||
parent: ResolvingMetadata
|
||||
): Promise<Metadata | null> {
|
||||
const { data, error } = await getClient().query(newsQuery, {});
|
||||
if (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
if (!data?.index) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const index = data.index as NewsIndexFragment;
|
||||
const metadata = await getSeoMetadata(index, parent);
|
||||
return metadata;
|
||||
const { index } = await loadNewsIndexProps();
|
||||
return getSeoMetadata(index, parent);
|
||||
}
|
||||
|
||||
export default async function Page() {
|
||||
const { data, error } = await getClient().query(newsQuery, {});
|
||||
if (error) {
|
||||
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>
|
||||
);
|
||||
const props = await loadNewsIndexProps();
|
||||
return <NewsIndexView {...props} />;
|
||||
}
|
||||
|
||||
@@ -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 { notFound } from "next/navigation";
|
||||
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 {
|
||||
EventPageView,
|
||||
loadEventPageProps,
|
||||
} from "@/components/events/EventPageView";
|
||||
import { graphql } from "@/gql";
|
||||
import { EventFragment } from "@/gql/graphql";
|
||||
import { getEventPig } from "@/lib/event";
|
||||
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() {
|
||||
const allEventSlugsQuery = graphql(`
|
||||
query allEventSlugs {
|
||||
@@ -52,52 +40,14 @@ export async function generateMetadata(
|
||||
parent: ResolvingMetadata
|
||||
): Promise<Metadata | null> {
|
||||
const { slug } = await params;
|
||||
const { data, error } = await getClient().query(eventBySlugQuery, {
|
||||
slug,
|
||||
});
|
||||
|
||||
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;
|
||||
const props = await loadEventPageProps({ slug });
|
||||
if (!props) return null;
|
||||
return getSeoMetadata(props.event, parent);
|
||||
}
|
||||
|
||||
export default async function Page({ params }: { params: Params }) {
|
||||
const { slug } = await params;
|
||||
const { data, error } = await getClient().query(eventBySlugQuery, {
|
||||
slug,
|
||||
});
|
||||
|
||||
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" />}
|
||||
</>
|
||||
);
|
||||
const props = await loadEventPageProps({ slug });
|
||||
if (!props) return notFound();
|
||||
return <EventPageView {...props} />;
|
||||
}
|
||||
|
||||
@@ -1,68 +1,24 @@
|
||||
import { Suspense } from "react";
|
||||
import { Metadata, ResolvingMetadata } from "next";
|
||||
import { getClient } from "@/app/client";
|
||||
import { EventContainer } from "@/components/events/EventContainer";
|
||||
import {
|
||||
eventsOverviewQuery,
|
||||
eventIndexMetadataQuery,
|
||||
EventFragment,
|
||||
EventCategory,
|
||||
EventOrganizer,
|
||||
} from "@/lib/event";
|
||||
import { PageHeader } from "@/components/general/PageHeader";
|
||||
import { EventIndexFragment, VenueFragment } from "@/gql/graphql";
|
||||
EventIndexView,
|
||||
loadEventIndexProps,
|
||||
} from "@/components/events/EventIndexView";
|
||||
import { EventIndexFragment } from "@/gql/graphql";
|
||||
import { eventIndexMetadataQuery } from "@/lib/event";
|
||||
import { getSeoMetadata } from "@/lib/seo";
|
||||
|
||||
export async function generateMetadata(
|
||||
{ params }: { params: Promise<{}> },
|
||||
_: unknown,
|
||||
parent: ResolvingMetadata
|
||||
): Promise<Metadata | null> {
|
||||
const { data, error } = await getClient().query(eventIndexMetadataQuery, {});
|
||||
|
||||
if (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
if (!data?.index) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const index = data.index as EventIndexFragment;
|
||||
const metadata = await getSeoMetadata(index, parent);
|
||||
return metadata;
|
||||
if (error) throw new Error(error.message);
|
||||
if (!data?.index) return null;
|
||||
return getSeoMetadata(data.index as EventIndexFragment, parent);
|
||||
}
|
||||
|
||||
export default async function Page() {
|
||||
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 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>
|
||||
);
|
||||
const props = await loadEventIndexProps();
|
||||
return <EventIndexView {...props} />;
|
||||
}
|
||||
|
||||
@@ -3,9 +3,15 @@ import "server-only";
|
||||
import { cacheExchange, createClient, fetchExchange } from "@urql/core";
|
||||
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 = () => {
|
||||
return createClient({
|
||||
url: process.env.GRAPHQL_ENDPOINT ?? "",
|
||||
url: graphqlEndpoint,
|
||||
exchanges: [cacheExchange, fetchExchange],
|
||||
// requestPolicy: "network-only",
|
||||
fetchOptions: { next: { revalidate: 0 } },
|
||||
|
||||
@@ -1,25 +1,13 @@
|
||||
import { Metadata, ResolvingMetadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getClient } from "@/app/client";
|
||||
import { AssociationHeader } from "@/components/associations/AssociationHeader";
|
||||
import { PageContent } from "@/components/general/PageContent";
|
||||
import {
|
||||
AssociationPageView,
|
||||
loadAssociationPageProps,
|
||||
} from "@/components/associations/AssociationPageView";
|
||||
import { graphql } from "@/gql";
|
||||
import { AssociationFragment } from "@/gql/graphql";
|
||||
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 }>;
|
||||
|
||||
export async function generateMetadata(
|
||||
@@ -27,20 +15,9 @@ export async function generateMetadata(
|
||||
parent: ResolvingMetadata
|
||||
): Promise<Metadata | null> {
|
||||
const { slug } = await params;
|
||||
const { data, error } = await getClient().query(associationBySlugQuery, {
|
||||
slug,
|
||||
});
|
||||
|
||||
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;
|
||||
const props = await loadAssociationPageProps({ slug });
|
||||
if (!props) return null;
|
||||
return getSeoMetadata(props.association, parent);
|
||||
}
|
||||
|
||||
export async function generateStaticParams() {
|
||||
@@ -70,23 +47,7 @@ export async function generateStaticParams() {
|
||||
|
||||
export default async function Page({ params }: { params: Params }) {
|
||||
const { slug } = await params;
|
||||
const { data, error } = await getClient().query(associationBySlugQuery, {
|
||||
slug,
|
||||
});
|
||||
|
||||
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>
|
||||
);
|
||||
const props = await loadAssociationPageProps({ slug });
|
||||
if (!props) return notFound();
|
||||
return <AssociationPageView {...props} />;
|
||||
}
|
||||
|
||||
@@ -1,106 +1,19 @@
|
||||
import { Metadata, ResolvingMetadata } from "next";
|
||||
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";
|
||||
import {
|
||||
AssociationIndexView,
|
||||
loadAssociationIndexProps,
|
||||
} from "@/components/associations/AssociationIndexView";
|
||||
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(
|
||||
{ params }: { params: Promise<{}> },
|
||||
_: unknown,
|
||||
parent: ResolvingMetadata
|
||||
): Promise<Metadata | null> {
|
||||
const { data, error } = await getClient().query(allAssociationsQuery, {});
|
||||
|
||||
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 { index } = await loadAssociationIndexProps();
|
||||
return getSeoMetadata(index, parent);
|
||||
}
|
||||
|
||||
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() {
|
||||
const { data, error } = await getClient().query(allAssociationsQuery, {});
|
||||
|
||||
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>
|
||||
);
|
||||
const props = await loadAssociationIndexProps();
|
||||
return <AssociationIndexView {...props} />;
|
||||
}
|
||||
|
||||
@@ -1,72 +1,19 @@
|
||||
import { Metadata, ResolvingMetadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
import { graphql } from "@/gql";
|
||||
import { ContactIndexFragment } from "@/gql/graphql";
|
||||
import { getClient } from "@/app/client";
|
||||
import { PageHeader } from "@/components/general/PageHeader";
|
||||
import { PageContent } from "@/components/general/PageContent";
|
||||
import { GeneralContactBlock } from "@/components/blocks/GeneralContactBlock";
|
||||
import {
|
||||
ContactIndexView,
|
||||
loadContactIndexProps,
|
||||
} from "@/components/contact/ContactIndexView";
|
||||
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(
|
||||
{ params }: { params: Promise<{}> },
|
||||
_: unknown,
|
||||
parent: ResolvingMetadata
|
||||
): Promise<Metadata | null> {
|
||||
const { data, error } = await getClient().query(contactQuery, {});
|
||||
|
||||
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;
|
||||
const { index } = await loadContactIndexProps();
|
||||
return getSeoMetadata(index, parent);
|
||||
}
|
||||
|
||||
export default async function Page() {
|
||||
const { data, error } = await getClient().query(contactQuery, {});
|
||||
|
||||
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>
|
||||
);
|
||||
const props = await loadContactIndexProps();
|
||||
return <ContactIndexView {...props} />;
|
||||
}
|
||||
|
||||
@@ -1,25 +1,13 @@
|
||||
import { Metadata, ResolvingMetadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getClient } from "@/app/client";
|
||||
import { ImageSliderBlock } from "@/components/blocks/ImageSliderBlock";
|
||||
import { Breadcrumb } from "@/components/general/Breadcrumb";
|
||||
import { PageContent } from "@/components/general/PageContent";
|
||||
import { NeufMap } from "@/components/venues/NeufMap";
|
||||
import { VenueInfo } from "@/components/venues/VenueInfo";
|
||||
import {
|
||||
VenuePageView,
|
||||
loadVenuePageProps,
|
||||
} from "@/components/venues/VenuePageView";
|
||||
import { graphql } from "@/gql";
|
||||
import { VenueFragment } from "@/gql/graphql";
|
||||
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 }>;
|
||||
|
||||
export async function generateMetadata(
|
||||
@@ -27,20 +15,9 @@ export async function generateMetadata(
|
||||
parent: ResolvingMetadata
|
||||
): Promise<Metadata | null> {
|
||||
const { slug } = await params;
|
||||
const { data, error } = await getClient().query(venueBySlugQuery, {
|
||||
slug,
|
||||
});
|
||||
|
||||
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;
|
||||
const props = await loadVenuePageProps({ slug });
|
||||
if (!props) return null;
|
||||
return getSeoMetadata(props.venue, parent);
|
||||
}
|
||||
|
||||
export async function generateStaticParams() {
|
||||
@@ -70,32 +47,7 @@ export async function generateStaticParams() {
|
||||
|
||||
export default async function Page({ params }: { params: Params }) {
|
||||
const { slug } = await params;
|
||||
const { data, error } = await getClient().query(venueBySlugQuery, {
|
||||
slug,
|
||||
});
|
||||
|
||||
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>
|
||||
);
|
||||
const props = await loadVenuePageProps({ slug });
|
||||
if (!props) return notFound();
|
||||
return <VenuePageView {...props} />;
|
||||
}
|
||||
|
||||
@@ -1,111 +1,19 @@
|
||||
import { Metadata, ResolvingMetadata } from "next";
|
||||
import { graphql } from "@/gql";
|
||||
import { VenueFragment, VenueIndexFragment } from "@/gql/graphql";
|
||||
import { getClient } from "@/app/client";
|
||||
import { VenueList } from "@/components/venues/VenueList";
|
||||
import { PageHeader } from "@/components/general/PageHeader";
|
||||
import { PageContent } from "@/components/general/PageContent";
|
||||
import {
|
||||
VenueIndexView,
|
||||
loadVenueIndexProps,
|
||||
} from "@/components/venues/VenueIndexView";
|
||||
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(
|
||||
{ params }: { params: Promise<{}> },
|
||||
_: unknown,
|
||||
parent: ResolvingMetadata
|
||||
): Promise<Metadata | null> {
|
||||
const { data, error } = await getClient().query(venueIndexQuery, {});
|
||||
|
||||
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;
|
||||
const { index } = await loadVenueIndexProps();
|
||||
return getSeoMetadata(index, parent);
|
||||
}
|
||||
|
||||
export default async function Page() {
|
||||
const { data, error } = await getClient().query(venueIndexQuery, {});
|
||||
|
||||
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>
|
||||
);
|
||||
const props = await loadVenueIndexProps();
|
||||
return <VenueIndexView {...props} />;
|
||||
}
|
||||
|
||||
+6
-94
@@ -1,97 +1,9 @@
|
||||
import { graphql } from "@/gql";
|
||||
import { EventFragment } from "@/lib/event";
|
||||
import { NewsFragment } from "@/lib/news";
|
||||
import { HomeFragment } from "@/gql/graphql";
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
import {
|
||||
HomePageView,
|
||||
loadHomePageProps,
|
||||
} from "@/components/home/HomePageView";
|
||||
|
||||
export default async function Home() {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
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 />
|
||||
</>
|
||||
);
|
||||
const props = await loadHomePageProps();
|
||||
return <HomePageView {...props} />;
|
||||
}
|
||||
|
||||
@@ -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 { graphql } from "@/gql";
|
||||
import { SponsorsPage, SponsorBlock } from "@/gql/graphql";
|
||||
import { getClient } from "@/app/client";
|
||||
import { PageHeader } from "@/components/general/PageHeader";
|
||||
import { PageContent } from "@/components/general/PageContent";
|
||||
import {
|
||||
SponsorsPageView,
|
||||
loadSponsorsPageProps,
|
||||
} from "@/components/sponsor/SponsorsPageView";
|
||||
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(
|
||||
{ params }: { params: Promise<{}> },
|
||||
_: unknown,
|
||||
parent: ResolvingMetadata
|
||||
): Promise<Metadata | null> {
|
||||
const { data, error } = await getClient().query(sponsorsPageQuery, {});
|
||||
|
||||
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 { page } = await loadSponsorsPageProps();
|
||||
return getSeoMetadata(page, parent);
|
||||
}
|
||||
|
||||
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() {
|
||||
const { data, error } = await getClient().query(sponsorsPageQuery, {});
|
||||
|
||||
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>
|
||||
);
|
||||
const props = await loadSponsorsPageProps();
|
||||
return <SponsorsPageView {...props} />;
|
||||
}
|
||||
|
||||
@@ -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 { graphql } from "@/gql";
|
||||
import { VenueFragment, VenueRentalIndexFragment } from "@/gql/graphql";
|
||||
import { getClient } from "@/app/client";
|
||||
import { VenueList } from "@/components/venues/VenueList";
|
||||
import { PageHeader } from "@/components/general/PageHeader";
|
||||
import { BgPig } from "@/components/general/BgPig";
|
||||
import { PageContent } from "@/components/general/PageContent";
|
||||
import {
|
||||
VenueRentalIndexView,
|
||||
loadVenueRentalIndexProps,
|
||||
} from "@/components/venues/VenueRentalIndexView";
|
||||
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(
|
||||
{ params }: { params: Promise<{}> },
|
||||
_: unknown,
|
||||
parent: ResolvingMetadata
|
||||
): Promise<Metadata | null> {
|
||||
const { data, error } = await getClient().query(venueRentalIndexQuery, {});
|
||||
|
||||
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;
|
||||
const { index } = await loadVenueRentalIndexProps();
|
||||
return getSeoMetadata(index, parent);
|
||||
}
|
||||
|
||||
export default async function Page() {
|
||||
const { data, error } = await getClient().query(venueRentalIndexQuery, {});
|
||||
|
||||
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" />
|
||||
</>
|
||||
);
|
||||
const props = await loadVenueRentalIndexProps();
|
||||
return <VenueRentalIndexView {...props} />;
|
||||
}
|
||||
|
||||
@@ -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 { Accordion } from "@/components/general/Accordion";
|
||||
|
||||
const AccordionBlockFragmentDefinition = graphql(`
|
||||
fragment AccordionBlock on AccordionBlock {
|
||||
heading
|
||||
body {
|
||||
id
|
||||
blockType
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export const AccordionBlock = ({
|
||||
block,
|
||||
}: {
|
||||
block: AccordionBlockType;
|
||||
block: AccordionBlockFragment;
|
||||
}) => {
|
||||
return (
|
||||
<Accordion heading={block.heading}>
|
||||
<Blocks blocks={block.body} />
|
||||
</Accordion>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -6,7 +6,6 @@ import { FeaturedBlock } from "./FeaturedBlock";
|
||||
import { AccordionBlock } from "./AccordionBlock";
|
||||
import { EmbedBlock } from "./EmbedBlock";
|
||||
import { FactBoxBlock } from "./FactBoxBlock";
|
||||
import { PhotoSphereBlock } from "./PhotoSphereBlock";
|
||||
import { PageSectionBlock, PageSectionNavigationBlock } from "./PageSection";
|
||||
import { ContactSectionBlock, ContactSubsectionBlock } from "./ContactSection";
|
||||
import { ContactListBlock } from "./ContactListBlock";
|
||||
@@ -45,9 +44,6 @@ export const Blocks = ({ blocks, pageContent }: { blocks: any, pageContent?: boo
|
||||
case "FactBoxBlock":
|
||||
return <FactBoxBlock key={block.id} block={block} />;
|
||||
break;
|
||||
case "PhotoSphereBlock":
|
||||
return <PhotoSphereBlock key={block.id} block={block} />;
|
||||
break;
|
||||
case "PageSectionBlock":
|
||||
return <PageSectionBlock key={block.id} block={block} />;
|
||||
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 { formatNorwegianPhoneNumber, formatPhoneE164 } from "@/lib/common";
|
||||
import {
|
||||
ContactEntityFragmentDefinition,
|
||||
ImageFragmentDefinition,
|
||||
formatNorwegianPhoneNumber,
|
||||
formatPhoneE164,
|
||||
} from "@/lib/common";
|
||||
import { Icon } from "../general/Icon";
|
||||
import { Image } from "../general/Image";
|
||||
|
||||
const ContactEntityBlockFragmentDefinition = graphql(`
|
||||
fragment ContactEntityBlock on ContactEntityBlock {
|
||||
contactEntity {
|
||||
...ContactEntity
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export const ContactEntityBlock = ({
|
||||
block,
|
||||
}: {
|
||||
block: ContactEntityBlockType;
|
||||
block: ContactEntityBlockFragment;
|
||||
}) => {
|
||||
const contact = block?.contactEntity;
|
||||
const contact = unmaskFragment(
|
||||
ContactEntityFragmentDefinition,
|
||||
block?.contactEntity
|
||||
);
|
||||
const image = unmaskFragment(ImageFragmentDefinition, contact?.image);
|
||||
|
||||
if (!contact) {
|
||||
return <></>;
|
||||
@@ -21,18 +39,18 @@ export const ContactEntityBlock = ({
|
||||
return (
|
||||
<li className={styles.contactItem}>
|
||||
<div className={styles.image}>
|
||||
{!contact.image && (
|
||||
{!image && (
|
||||
<img
|
||||
src="/assets/graphics/portrait-pig.svg"
|
||||
className={styles.portraitPlaceholder}
|
||||
/>
|
||||
)}
|
||||
{contact.image && (
|
||||
{image && (
|
||||
<Image
|
||||
src={contact.image.url}
|
||||
alt={contact.image.alt ?? ""}
|
||||
width={contact.image.width}
|
||||
height={contact.image.height}
|
||||
src={image.url}
|
||||
alt={image.alt ?? ""}
|
||||
width={image.width}
|
||||
height={image.height}
|
||||
sizes="25vw"
|
||||
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 { Blocks } from "./Blocks";
|
||||
|
||||
const ContactListBlockFragmentDefinition = graphql(`
|
||||
fragment ContactListBlock on ContactListBlock {
|
||||
items {
|
||||
blockType
|
||||
... on ContactEntityBlock {
|
||||
contactEntity {
|
||||
...ContactEntity
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export const ContactListBlock = ({
|
||||
block,
|
||||
}: {
|
||||
block: ContactListBlockType;
|
||||
block: ContactListBlockFragment;
|
||||
}) => {
|
||||
return (
|
||||
<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 { 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 = ({
|
||||
block,
|
||||
}: {
|
||||
block: ContactSectionBlockType;
|
||||
block: ContactSectionBlockFragment;
|
||||
}) => {
|
||||
return (
|
||||
<section className={styles.contactSection}>
|
||||
@@ -24,7 +50,7 @@ export const ContactSectionBlock = ({
|
||||
export const ContactSubsectionBlock = ({
|
||||
block,
|
||||
}: {
|
||||
block: ContactSectionBlockType;
|
||||
block: ContactSubsectionBlockFragment;
|
||||
}) => {
|
||||
return (
|
||||
<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";
|
||||
|
||||
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) {
|
||||
return <></>;
|
||||
}
|
||||
@@ -18,7 +27,7 @@ export const EmbedBlock = ({ block }: { block: EmbedBlockType }) => {
|
||||
*/
|
||||
let embedData: any = {};
|
||||
try {
|
||||
embedData = JSON.parse(block.rawEmbed);
|
||||
embedData = block.rawEmbed ? JSON.parse(block.rawEmbed) : {};
|
||||
} catch (e) {
|
||||
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";
|
||||
|
||||
type FactBoxBlockTypeWithAlias = FactBoxBlockType & {
|
||||
factBoxBody?: string;
|
||||
};
|
||||
const FactBoxBlockFragmentDefinition = graphql(`
|
||||
fragment FactBoxBlock on FactBoxBlock {
|
||||
backgroundColor
|
||||
factBoxBody: body
|
||||
}
|
||||
`);
|
||||
|
||||
export const FactBoxBlock = ({
|
||||
block,
|
||||
}: {
|
||||
block: FactBoxBlockTypeWithAlias;
|
||||
block: FactBoxBlockFragment;
|
||||
}) => {
|
||||
if (!block.factBoxBody) {
|
||||
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 { Image } from "@/components/general/Image";
|
||||
import { ImageFragmentDefinition } from "@/lib/common";
|
||||
import styles from "./featuredBlock.module.scss";
|
||||
|
||||
// the 'text' field has been aliased to 'featuredBlockText' and i'm
|
||||
// using codegen the wrong way. let's specify the field here and move on
|
||||
type FeaturedBlockTypeWithAlias = FeaturedBlockType & {
|
||||
featuredBlockText: string;
|
||||
};
|
||||
const FeaturedBlockFragmentDefinition = graphql(`
|
||||
fragment FeaturedBlock on FeaturedBlock {
|
||||
title
|
||||
featuredBlockText: text
|
||||
linkText
|
||||
imagePosition
|
||||
backgroundColor
|
||||
featuredPage {
|
||||
contentType
|
||||
pageType
|
||||
url
|
||||
... on EventPage {
|
||||
featuredImage {
|
||||
...Image
|
||||
}
|
||||
}
|
||||
... on NewsPage {
|
||||
featuredImage {
|
||||
...Image
|
||||
}
|
||||
}
|
||||
}
|
||||
featuredImageOverride {
|
||||
...Image
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export const FeaturedBlock = ({
|
||||
block,
|
||||
}: {
|
||||
block: FeaturedBlockTypeWithAlias;
|
||||
block: FeaturedBlockFragment;
|
||||
}) => {
|
||||
const image = !!block.featuredImageOverride
|
||||
? block.featuredImageOverride
|
||||
: null;
|
||||
const image = unmaskFragment(
|
||||
ImageFragmentDefinition,
|
||||
block.featuredImageOverride
|
||||
);
|
||||
// TODO: fetch image from target page
|
||||
|
||||
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 styles from "./horizontalRuleBlock.module.scss";
|
||||
|
||||
const HorizontalRuleBlockFragmentDefinition = graphql(`
|
||||
fragment HorizontalRuleBlock on HorizontalRuleBlock {
|
||||
color
|
||||
}
|
||||
`);
|
||||
|
||||
export const HorizontalRuleBlock = ({
|
||||
block,
|
||||
}: {
|
||||
block: HorizontalRuleBlockType;
|
||||
block: HorizontalRuleBlockFragment;
|
||||
}) => {
|
||||
const knownColors = [
|
||||
"deepBrick",
|
||||
|
||||
@@ -1,8 +1,30 @@
|
||||
"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 { ImageFragmentDefinition } from "@/lib/common";
|
||||
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, SwiperSlide } from "swiper/react";
|
||||
import { Pagination, Navigation } from "swiper/modules";
|
||||
@@ -11,17 +33,42 @@ import "swiper/css/pagination";
|
||||
import "swiper/css/navigation";
|
||||
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 = ({
|
||||
block,
|
||||
hero,
|
||||
pageContent
|
||||
pageContent,
|
||||
}: {
|
||||
block: ImageSliderBlockType | any;
|
||||
block: ImageSliderBlockFragment;
|
||||
hero?: boolean;
|
||||
pageContent?: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<div className={styles.imageSliderBlock} data-hero={hero} data-pagecontent={pageContent}>
|
||||
<div
|
||||
className={styles.imageSliderBlock}
|
||||
data-hero={hero}
|
||||
data-pagecontent={pageContent}
|
||||
>
|
||||
<Swiper
|
||||
pagination={{
|
||||
type: "fraction",
|
||||
@@ -30,21 +77,16 @@ export const ImageSliderBlock = ({
|
||||
modules={[Pagination, Navigation]}
|
||||
className="mySwiper"
|
||||
>
|
||||
{block.images &&
|
||||
block.images.map((imageItem: any, index: number) => (
|
||||
{block.images?.map((item, index) => {
|
||||
if (item?.__typename !== "ImageSliderItemBlock") {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<SwiperSlide key={index}>
|
||||
<ImageFigure
|
||||
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"
|
||||
/>
|
||||
<Slide item={item} />
|
||||
</SwiperSlide>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</Swiper>
|
||||
</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 { ImageFragmentDefinition } from "@/lib/common";
|
||||
|
||||
const ImageWithTextBlockFragmentDefinition = graphql(`
|
||||
fragment ImageWithTextBlock on ImageWithTextBlock {
|
||||
image {
|
||||
...Image
|
||||
}
|
||||
imageFormat
|
||||
text
|
||||
}
|
||||
`);
|
||||
|
||||
export function ImageWithTextBlock({
|
||||
block,
|
||||
}: {
|
||||
block: ImageWithTextBlockType;
|
||||
block: ImageWithTextBlockFragment;
|
||||
}) {
|
||||
const image = unmaskFragment(ImageFragmentDefinition, block.image);
|
||||
return (
|
||||
<ImageFigure
|
||||
src={block.image.url}
|
||||
alt={block.image.alt ?? ""}
|
||||
width={block.image.width}
|
||||
height={block.image.height}
|
||||
attribution={block.image.attribution}
|
||||
src={image.url}
|
||||
alt={image.alt ?? ""}
|
||||
width={image.width}
|
||||
height={image.height}
|
||||
attribution={image.attribution}
|
||||
caption={block.text}
|
||||
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 { Blocks } from "./Blocks";
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
import { DecorativeIcon } from "../general/Icon";
|
||||
|
||||
const PageSectionBlockFragmentDefinition = graphql(`
|
||||
fragment PageSectionBlock on PageSectionBlock {
|
||||
title
|
||||
backgroundColor
|
||||
icon
|
||||
body {
|
||||
id
|
||||
blockType
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export const PageSectionBlock = ({
|
||||
block,
|
||||
}: {
|
||||
block: PageSectionBlockType;
|
||||
block: PageSectionBlockFragment;
|
||||
}) => {
|
||||
const anchor = slugify(block.title);
|
||||
|
||||
@@ -31,7 +44,7 @@ export const PageSectionBlock = ({
|
||||
export const PageSectionNavigationBlock = ({
|
||||
sections,
|
||||
}: {
|
||||
sections: PageSectionBlockType[];
|
||||
sections: PageSectionBlockFragment[];
|
||||
}) => {
|
||||
if (!sections.length) {
|
||||
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";
|
||||
|
||||
export const RichTextBlock = ({ block }: any) => {
|
||||
const RichTextBlockFragmentDefinition = graphql(`
|
||||
fragment RichTextBlock on RichTextBlock {
|
||||
rawValue
|
||||
value
|
||||
}
|
||||
`);
|
||||
|
||||
export const RichTextBlock = ({ block }: { block: RichTextBlockFragment }) => {
|
||||
return (
|
||||
<div
|
||||
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";
|
||||
import { EventItem } from "./EventItem";
|
||||
import { EventFilter, EventFilterExplained } from "./EventFilter";
|
||||
import { unmaskFragment } from "@/gql";
|
||||
import {
|
||||
EventCategoryFragmentDefinition,
|
||||
EventOrganizerFragmentDefinition,
|
||||
EventFragment,
|
||||
EventCategory,
|
||||
SingularEvent,
|
||||
@@ -48,11 +51,11 @@ export const EventContainer = ({
|
||||
}) => {
|
||||
const [mode, setMode] = useQueryState(
|
||||
"mode",
|
||||
parseAsStringLiteral(["list", "calendar"]).withDefault("list")
|
||||
parseAsStringLiteral(["list", "calendar"]).withDefault("list"),
|
||||
);
|
||||
const [categories, setCategories] = useQueryState(
|
||||
"category",
|
||||
parseAsArrayOf(parseAsString, ",")
|
||||
parseAsArrayOf(parseAsString, ","),
|
||||
);
|
||||
const [organizer, setOrganizer] = useQueryState("organizer", parseAsString);
|
||||
const [venue, setVenue] = useQueryState("venue", parseAsString);
|
||||
@@ -75,13 +78,15 @@ export const EventContainer = ({
|
||||
Filtering on an organizer with no upcoming events will work,
|
||||
and in that case it's included in the dropdown
|
||||
*/
|
||||
const allOrganizers = unmaskFragment(
|
||||
EventOrganizerFragmentDefinition,
|
||||
events.flatMap((x) => x.organizers),
|
||||
);
|
||||
const uniqueOrganizers: string[] = unique(
|
||||
events
|
||||
.map((x) => x.organizers)
|
||||
.flat()
|
||||
allOrganizers
|
||||
.filter((x) => x.__typename === "EventOrganizer")
|
||||
.map((x) => x.slug)
|
||||
.filter((x) => typeof x === "string" && x !== "")
|
||||
.filter((x) => typeof x === "string" && x !== ""),
|
||||
);
|
||||
const filterableOrganizers = uniqueOrganizers
|
||||
.map((slug) => eventOrganizers.find((haystack) => haystack.slug === slug))
|
||||
@@ -118,11 +123,11 @@ export const EventContainer = ({
|
||||
.flat()
|
||||
.filter((x) => x.venue?.__typename === "VenuePage")
|
||||
.map((x) => x.venue?.slug)
|
||||
.filter((x) => typeof x === "string")
|
||||
.filter((x) => typeof x === "string"),
|
||||
);
|
||||
const filterableVenues = venues
|
||||
.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))
|
||||
.filter((x) => x !== undefined) as VenueFragment[];
|
||||
@@ -134,27 +139,32 @@ export const EventContainer = ({
|
||||
}
|
||||
}, [venues, venue]);
|
||||
|
||||
const filteredEvents = events
|
||||
.filter(
|
||||
(x) =>
|
||||
!organizer ||
|
||||
x.organizers.map((organizer) => organizer.slug).includes(organizer)
|
||||
)
|
||||
.filter(
|
||||
(x) =>
|
||||
!categories ||
|
||||
x.categories
|
||||
.map((eventCategory) => eventCategory.slug)
|
||||
.filter((x) => categories.includes(x)).length !== 0
|
||||
)
|
||||
.filter(
|
||||
(x) =>
|
||||
!venue ||
|
||||
x.occurrences
|
||||
.map((occurrence) => occurrence.venue?.slug)
|
||||
.filter((x) => typeof x === "string")
|
||||
.includes(venue)
|
||||
);
|
||||
const filteredEvents = events.filter((event) => {
|
||||
if (organizer) {
|
||||
const organizers = unmaskFragment(
|
||||
EventOrganizerFragmentDefinition,
|
||||
event.organizers,
|
||||
);
|
||||
if (!organizers.some((o) => o.slug === organizer)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (categories) {
|
||||
const eventCategories = unmaskFragment(
|
||||
EventCategoryFragmentDefinition,
|
||||
event.categories,
|
||||
);
|
||||
if (!eventCategories.some((c) => categories.includes(c.slug))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (venue) {
|
||||
if (!event.occurrences.some((occ) => occ.venue?.slug === venue)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
const [showFilter, setShowFilter] = useState(false);
|
||||
function toggleFilter() {
|
||||
@@ -302,12 +312,12 @@ function maybeYear(yearMonthString: string) {
|
||||
|
||||
const EventCalendar = ({ events }: { events: EventFragment[] }) => {
|
||||
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 yearMonths = Object.keys(eventsByDate);
|
||||
const [visibleYearMonths, setVisibleYearMonths] = useState(
|
||||
yearMonths.slice(0, 2)
|
||||
yearMonths.slice(0, 2),
|
||||
);
|
||||
|
||||
const toggleYearMonth = (yearMonth: string) => {
|
||||
@@ -327,9 +337,9 @@ const EventCalendar = ({ events }: { events: EventFragment[] }) => {
|
||||
yearMonthSum +
|
||||
Object.values(week).reduce(
|
||||
(weekSum, day) => weekSum + day.length,
|
||||
0
|
||||
0,
|
||||
),
|
||||
0
|
||||
0,
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user