39 Commits
Author SHA1 Message Date
ponas 1853e6c470 web: fix dupe year in event calendar view
web / ci (push) Successful in 1m2s
2026-08-06 16:48:39 +02:00
ponas 54f45820ad web: add gitea workflow
web / ci (push) Successful in 2m11s
2026-08-04 03:53:50 +02:00
ponas 031d61e1fe dnscms: add gitea workflow
dnscms / ci (push) Successful in 1m22s
2026-08-04 03:51:54 +02:00
ponas 952a8bca13 web: add tests for lib/ + revalidation api 2026-08-04 03:32:45 +02:00
ponas 6370373f0d web: move to vitest 2026-08-04 03:02:09 +02:00
ponas eabcd47bfd web: give the pig a hat during STUDiO 2026 2026-08-03 00:50:34 +02:00
ponas 848829f3b9 dnscms: fix linting issues 2026-08-03 00:47:34 +02:00
ponas 669a3cce8d dnscms: bump deps 2026-08-03 00:47:16 +02:00
ponas b231f40cdc fix audio capability field in venue info 2026-07-13 13:30:52 +02:00
ponas 3892624382 use next/link for internal links, even if absolute from the cms 2026-07-10 03:24:00 +02:00
ponas ca397070ea clean up block typing 2026-07-10 03:23:09 +02:00
ponas 09207e57e2 hide the studio shortcut after the festival 2026-07-10 02:30:30 +02:00
ponas 9a3cd0032f web: bump deps, allow some install scripts 2026-07-09 22:02:33 +02:00
elisejakob 81c32719c8 style schedule block 2026-07-09 21:34:57 +02:00
elisejakob 7a784953a8 add shine to studio shortcut 2026-07-09 21:13:18 +02:00
elisejakob 6092fc32fd adjust studio shortcut line height 2026-07-09 21:05:39 +02:00
elisejakob 6ee3695074 adjust studio shortcut font size 2026-07-09 21:04:36 +02:00
elisejakob 75282104f6 add studio shortcut to front page 2026-07-09 20:59:28 +02:00
elisejakob 01ce1c76f3 switch out section header and footer link arrows 2026-07-09 20:32:03 +02:00
elisejakob 38d332b574 style studio banner, add more decorations and instagram icon 2026-07-09 14:28:11 +02:00
ponas e600d0a663 cache queries and revalidate with incremental static regeneration 2026-07-07 02:32:21 +02:00
ponas d63b14b2e5 show slugs in event organizer index 2026-07-05 17:56:57 +02:00
ponas 36c01902a1 improve event organizer search 2026-07-05 17:53:33 +02:00
ponas e57eb48c88 add event organizer merge tool 2026-07-05 17:28:00 +02:00
ponas 7ac332e352 only show the smaller logo in header bar visible while scrolling 2026-07-05 14:40:30 +02:00
ponas acf70b99d3 set tabIndex -1 on header bar links 2026-07-05 13:09:30 +02:00
ponas 93956435cf improve full menu animation 2026-07-05 13:02:22 +02:00
ponas 39364b5023 avoid layout shifts on event index page 2026-07-05 12:53:53 +02:00
ponas b46320ea94 add a duplicate button to event occurrences 2026-07-05 12:12:48 +02:00
ponas 9e503d8165 nicer text placement in pills 2026-07-05 02:35:43 +02:00
ponas 7b537c9ffa remove test button 2026-07-05 02:26:24 +02:00
ponas 8ebd17c99c improve text placement in buttons and text inputs on non-apple devices 2026-07-05 02:23:03 +02:00
ponas 0e239d06d8 dnscms: bump deps 2026-07-05 00:53:58 +02:00
ponas 6f1d531d5d replace button link block with a separate button link tool inside draftail 2026-07-05 00:51:30 +02:00
ponas b0698247b6 add a button link component 2026-07-02 23:44:53 +02:00
ponas 31453af238 add schedule block 2026-07-02 17:23:08 +02:00
elisejakob 348a51f2ea adjust studio icon size and placement 2026-07-02 16:55:53 +02:00
elisejakob 90bc6917e8 add studio decorative icon svgs 2026-07-02 16:39:57 +02:00
ponas 73be8c23f2 dnscms: add studio icons 2026-07-02 16:31:27 +02:00
132 changed files with 6091 additions and 1188 deletions
+27
View File
@@ -0,0 +1,27 @@
name: dnscms
on:
push:
paths:
- "dnscms/**"
- ".gitea/workflows/dnscms.yaml"
pull_request:
paths:
- "dnscms/**"
- ".gitea/workflows/dnscms.yaml"
defaults:
run:
working-directory: dnscms
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v7
- run: uv sync --locked
- run: uv run ruff check .
- run: uv run ruff format --check .
- run: uv run manage.py makemigrations --check --dry-run --settings=dnscms.settings.test
- run: uv run pytest
+29
View File
@@ -0,0 +1,29 @@
name: web
on:
push:
paths:
- "web/**"
- ".gitea/workflows/web.yaml"
pull_request:
paths:
- "web/**"
- ".gitea/workflows/web.yaml"
defaults:
run:
working-directory: web
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: npm
cache-dependency-path: web/package-lock.json
- run: npm ci
- run: npx tsc --noEmit
- run: npm test
+17 -1
View File
@@ -24,13 +24,29 @@ npm install
npm run dev # http://localhost:3000 npm run dev # http://localhost:3000
npm run codegen # regenerate GraphQL types (needs the backend running) npm run codegen # regenerate GraphQL types (needs the backend running)
npm run build npm run build
npm test # vitest (npm run test:watch for watch mode)
``` ```
## Pre-commit hooks ## 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). [prek](https://github.com/j178/prek) runs ruff lint + format on `dnscms/**/*.py`, vitest on `web/` changes, plus a few sanity hooks. Hooks are configured in [prek.toml](prek.toml).
```bash ```bash
prek install # registers the git hook prek install # registers the git hook
prek run --all-files # run on everything prek run --all-files # run on everything
``` ```
## Caching and revalidation
The frontend caches all pages and GraphQL responses (ISR) under a single `cms` cache tag.
Wagtail purges it via a webhook on any publish/unpublish/move/delete (see `dnscms/dnscms/revalidation.py`).
Date-sensitive queries (`futureEvents`, opening hours) additionally expire at Oslo midnight.
`REVALIDATE_WEBHOOK_SECRET` must be set to the same value on both sides
(`web/.env.local` and `dnscms/dnscms/settings/local.py` or env); unset disables the webhook.
```bash
# manual purge
curl -X POST -H "X-Revalidate-Secret: $SECRET" https://<frontend>/api/revalidate
```
Note: ISR is off under `npm run dev`; use `npm run build && npm run start` to test caching.
@@ -0,0 +1,24 @@
# Generated by Django 6.0.5 on 2026-07-02 14:53
import wagtail.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('associations', '0026_alter_association_options'),
]
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), ('schedule', 33), ('embed', 34), ('raw_html', 35)], 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.blocks.CharBlock', (), {'help_text': 'Overskrift for timeplanen (valgfritt)', 'label': 'Tittel', 'max_length': 64, 'required': False}), 28: ('wagtail.blocks.CharBlock', (), {'help_text': 'Fritekst, f.eks. "13:00", "20:00 23:00" eller "23.06"', 'label': 'Tidspunkt/dato', 'max_length': 64, 'required': True}), 29: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link'], 'help_text': 'Kort tittel på én linje. Du kan legge inn lenker.', 'label': 'Tittel', 'required': True}), 30: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link'], 'help_text': 'Valgfri tilleggsinformasjon.', 'label': 'Beskrivelse', 'required': False}), 31: ('wagtail.blocks.StructBlock', [[('time', 28), ('title', 29), ('description', 30)]], {}), 32: ('wagtail.blocks.ListBlock', (31,), {'label': 'Punkter', 'min_num': 1}), 33: ('wagtail.blocks.StructBlock', [[('title', 27), ('items', 32)]], {}), 34: ('wagtail.embeds.blocks.EmbedBlock', (), {}), 35: ('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), ('schedule', 33), ('embed', 34), ('raw_html', 35)], 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.blocks.CharBlock', (), {'help_text': 'Overskrift for timeplanen (valgfritt)', 'label': 'Tittel', 'max_length': 64, 'required': False}), 28: ('wagtail.blocks.CharBlock', (), {'help_text': 'Fritekst, f.eks. "13:00", "20:00 23:00" eller "23.06"', 'label': 'Tidspunkt/dato', 'max_length': 64, 'required': True}), 29: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link'], 'help_text': 'Kort tittel på én linje. Du kan legge inn lenker.', 'label': 'Tittel', 'required': True}), 30: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link'], 'help_text': 'Valgfri tilleggsinformasjon.', 'label': 'Beskrivelse', 'required': False}), 31: ('wagtail.blocks.StructBlock', [[('time', 28), ('title', 29), ('description', 30)]], {}), 32: ('wagtail.blocks.ListBlock', (31,), {'label': 'Punkter', 'min_num': 1}), 33: ('wagtail.blocks.StructBlock', [[('title', 27), ('items', 32)]], {}), 34: ('wagtail.embeds.blocks.EmbedBlock', (), {}), 35: ('wagtail.blocks.RawHTMLBlock', (), {})}, default=[('paragraph', '')]),
),
]
File diff suppressed because one or more lines are too long
@@ -0,0 +1,24 @@
# Generated by Django 6.0.5 on 2026-07-04 22:29
import wagtail.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('associations', '0028_alter_associationindex_body_and_more'),
]
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), ('schedule', 33), ('embed', 34), ('raw_html', 35)], 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.blocks.CharBlock', (), {'help_text': 'Overskrift for timeplanen (valgfritt)', 'label': 'Tittel', 'max_length': 64, 'required': False}), 28: ('wagtail.blocks.CharBlock', (), {'help_text': 'Fritekst, f.eks. "13:00", "20:00 23:00" eller "23.06"', 'label': 'Tidspunkt/dato', 'max_length': 64, 'required': True}), 29: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link'], 'help_text': 'Kort tittel på én linje. Du kan legge inn lenker.', 'label': 'Tittel', 'required': True}), 30: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link'], 'help_text': 'Valgfri tilleggsinformasjon.', 'label': 'Beskrivelse', 'required': False}), 31: ('wagtail.blocks.StructBlock', [[('time', 28), ('title', 29), ('description', 30)]], {}), 32: ('wagtail.blocks.ListBlock', (31,), {'label': 'Punkter', 'min_num': 1}), 33: ('wagtail.blocks.StructBlock', [[('title', 27), ('items', 32)]], {}), 34: ('wagtail.embeds.blocks.EmbedBlock', (), {}), 35: ('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), ('schedule', 33), ('embed', 34), ('raw_html', 35)], 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.blocks.CharBlock', (), {'help_text': 'Overskrift for timeplanen (valgfritt)', 'label': 'Tittel', 'max_length': 64, 'required': False}), 28: ('wagtail.blocks.CharBlock', (), {'help_text': 'Fritekst, f.eks. "13:00", "20:00 23:00" eller "23.06"', 'label': 'Tidspunkt/dato', 'max_length': 64, 'required': True}), 29: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link'], 'help_text': 'Kort tittel på én linje. Du kan legge inn lenker.', 'label': 'Tittel', 'required': True}), 30: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link'], 'help_text': 'Valgfri tilleggsinformasjon.', 'label': 'Beskrivelse', 'required': False}), 31: ('wagtail.blocks.StructBlock', [[('time', 28), ('title', 29), ('description', 30)]], {}), 32: ('wagtail.blocks.ListBlock', (31,), {'label': 'Punkter', 'min_num': 1}), 33: ('wagtail.blocks.StructBlock', [[('title', 27), ('items', 32)]], {}), 34: ('wagtail.embeds.blocks.EmbedBlock', (), {}), 35: ('wagtail.blocks.RawHTMLBlock', (), {})}, default=[('paragraph', '')]),
),
]
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+3
View File
@@ -6,3 +6,6 @@ class DnsCmsConfig(AppConfig):
def ready(self): def ready(self):
from dnscms import signals # noqa: F401 from dnscms import signals # noqa: F401
from dnscms.revalidation import register_signal_handlers
register_signal_handlers()
+62
View File
@@ -225,6 +225,62 @@ class FactBoxBlock(blocks.StructBlock):
label = "Faktaboks" label = "Faktaboks"
@register_streamfield_block
class ScheduleItemBlock(blocks.StructBlock):
time = blocks.CharBlock(
label="Tidspunkt/dato",
required=True,
max_length=64,
help_text='Fritekst, f.eks. "13:00", "20:00 23:00" eller "23.06"',
)
title = blocks.RichTextBlock(
features=["bold", "italic", "link"],
label="Tittel",
required=True,
help_text="Kort tittel på én linje. Du kan legge inn lenker.",
)
description = blocks.RichTextBlock(
features=["bold", "italic", "link"],
label="Beskrivelse",
required=False,
help_text="Valgfri tilleggsinformasjon.",
)
graphql_fields = [
GraphQLString("time", required=True),
GraphQLRichText("title", required=True),
GraphQLRichText("description"),
]
class Meta:
icon = "time"
label = "Punkt"
@register_streamfield_block
class ScheduleBlock(blocks.StructBlock):
title = blocks.CharBlock(
max_length=64,
required=False,
label="Tittel",
help_text="Overskrift for timeplanen (valgfritt)",
)
items = blocks.ListBlock(
ScheduleItemBlock(),
min_num=1,
label="Punkter",
)
graphql_fields = [
GraphQLString("title"),
GraphQLStreamfield("items", required=True),
]
class Meta:
icon = "time"
label = "Timeplan"
BASE_BLOCKS = [ BASE_BLOCKS = [
("paragraph", blocks.RichTextBlock(label="Rik tekst")), ("paragraph", blocks.RichTextBlock(label="Rik tekst")),
("image", ImageWithTextBlock()), ("image", ImageWithTextBlock()),
@@ -234,6 +290,7 @@ BASE_BLOCKS = [
("page_section_navigation", PageSectionNavigationBlock()), ("page_section_navigation", PageSectionNavigationBlock()),
("accordion", AccordionBlock()), ("accordion", AccordionBlock()),
("fact_box", FactBoxBlock()), ("fact_box", FactBoxBlock()),
("schedule", ScheduleBlock()),
("embed", EmbedBlock()), ("embed", EmbedBlock()),
("raw_html", blocks.RawHTMLBlock()), ("raw_html", blocks.RawHTMLBlock()),
] ]
@@ -253,6 +310,11 @@ class PageSectionBlock(blocks.StructBlock):
("clock", "Klokke"), ("clock", "Klokke"),
("parking", "Parkering"), ("parking", "Parkering"),
("coins", "Mynter"), ("coins", "Mynter"),
("studioNote", "Note (STUDiO)"),
("studioCalendar", "Kalender (STUDiO)"),
("studioGuitar", "Gitar (STUDiO)"),
("studioDisco", "Discokule (STUDiO)"),
("studioMole", "Muldvarpen (STUDiO)"),
) )
title = blocks.CharBlock(max_length=64, required=True, label="Tittel") title = blocks.CharBlock(max_length=64, required=True, label="Tittel")
+97
View File
@@ -0,0 +1,97 @@
"""Purge the frontend cache when content changes.
The frontend caches all GraphQL responses under a single "cms" cache tag;
any content change POSTs to its /api/revalidate endpoint for a global purge.
"""
import logging
import threading
import urllib.request
from django.conf import settings
from django.db import transaction
from django.db.models.signals import post_delete, post_save
logger = logging.getLogger(__name__)
WEBHOOK_TIMEOUT_SECONDS = 5
# Non-page models exposed over GraphQL; pages are covered by wagtail signals
REVALIDATING_MODELS = [
"contacts.ContactEntity",
"events.EventCategory",
"events.EventOrganizer",
"images.CustomImage",
"openinghours.OpeningHoursSet",
]
_warned_unconfigured = False
def _send_webhook(url, secret, reason):
request = urllib.request.Request(
url, data=b"", method="POST", headers={"X-Revalidate-Secret": secret}
)
try:
with urllib.request.urlopen(request, timeout=WEBHOOK_TIMEOUT_SECONDS) as response:
logger.info("frontend revalidation ok (%s): HTTP %s", reason, response.status)
except Exception:
logger.exception("frontend revalidation failed (%s)", reason)
def trigger_frontend_revalidation(reason):
global _warned_unconfigured
secret = settings.REVALIDATE_WEBHOOK_SECRET
if not secret:
if not _warned_unconfigured:
logger.warning("REVALIDATE_WEBHOOK_SECRET is not set, skipping frontend revalidation")
_warned_unconfigured = True
return
url = f"{settings.FRONTEND_BASE_URL}/api/revalidate"
# After commit so the frontend can't re-render from pre-commit data,
# in a thread so publishing never blocks on the frontend
transaction.on_commit(
lambda: threading.Thread(
target=_send_webhook, args=(url, secret, reason), daemon=True
).start()
)
def _on_page_published(sender, instance, **kwargs):
trigger_frontend_revalidation(f"page_published: {instance.slug}")
def _on_page_unpublished(sender, instance, **kwargs):
trigger_frontend_revalidation(f"page_unpublished: {instance.slug}")
def _on_page_moved(sender, instance, **kwargs):
trigger_frontend_revalidation(f"post_page_move: {instance.slug}")
def _on_page_deleted(sender, instance, **kwargs):
trigger_frontend_revalidation(f"page_deleted: {instance.slug}")
def _on_model_changed(sender, instance, **kwargs):
trigger_frontend_revalidation(f"{sender._meta.label}: {instance}")
def register_signal_handlers():
from wagtail.models import Page
from wagtail.signals import page_published, page_unpublished, post_page_move
page_published.connect(_on_page_published, dispatch_uid="revalidation.page_published")
page_unpublished.connect(_on_page_unpublished, dispatch_uid="revalidation.page_unpublished")
post_page_move.connect(_on_page_moved, dispatch_uid="revalidation.post_page_move")
# Wagtail sends no signal on page deletion; MTI deletes of any page type
# also emit post_delete for the base Page row
post_delete.connect(_on_page_deleted, sender=Page, dispatch_uid="revalidation.page_deleted")
for label in REVALIDATING_MODELS:
post_save.connect(
_on_model_changed, sender=label, dispatch_uid=f"revalidation.save.{label}"
)
post_delete.connect(
_on_model_changed, sender=label, dispatch_uid=f"revalidation.delete.{label}"
)
+92
View File
@@ -0,0 +1,92 @@
"""Inline "button" link for Draftail rich text fields.
Stored as `<a linktype="button" data-href="...">label</a>`. The linktype (not a
plain href) avoids colliding with the built-in link feature, and lets
expand_db_html rewrite it to `<a class="button" href="...">` for the front end.
Wired up in wagtail_hooks.py.
"""
from django.urls import reverse_lazy
from django.utils.html import escape
from draftjs_exporter.dom import DOM
from wagtail.admin.rich_text.converters.html_to_contentstate import (
InlineEntityElementHandler,
)
from wagtail.admin.rich_text.editors.draftail import features as draftail_features
from wagtail.rich_text import LinkHandler
FEATURE_NAME = "button-link"
ENTITY_TYPE = "BUTTON"
def button_link_entity_decorator(props):
"""ContentState to DB HTML (keeps just the chosen URL)."""
return DOM.create_element(
"a",
{"linktype": "button", "data-href": props.get("url", "")},
props["children"],
)
class ButtonLinkEntityElementHandler(InlineEntityElementHandler):
"""DB HTML to ContentState."""
mutability = "MUTABLE"
def get_attribute_data(self, attrs):
return {"url": attrs.get("data-href", "")}
class ButtonLinkRichTextHandler(LinkHandler):
"""DB HTML to front-end HTML (expand_db_html)."""
identifier = "button"
@classmethod
def expand_db_attributes(cls, attrs):
href = escape(attrs.get("data-href", ""))
return f'<a class="button" href="{href}">'
def register_button_link_feature(features):
features.register_editor_plugin(
"draftail",
FEATURE_NAME,
draftail_features.EntityFeature(
{
"type": ENTITY_TYPE,
"icon": "arrow-right-full",
"description": "Knapp",
# Reuse the link chooser modal + payload, so buttons work like links.
"attributes": ["url", "id", "parentId"],
"allowlist": {"href": "^(http:|https:|mailto:|tel:|#|/|undefined$)"},
"chooserUrls": {
"pageChooser": reverse_lazy("wagtailadmin_choose_page"),
"externalLinkChooser": reverse_lazy("wagtailadmin_choose_page_external_link"),
"emailLinkChooser": reverse_lazy("wagtailadmin_choose_page_email_link"),
"phoneLinkChooser": reverse_lazy("wagtailadmin_choose_page_phone_link"),
"anchorLinkChooser": reverse_lazy("wagtailadmin_choose_page_anchor_link"),
},
},
js=[
# Gives LinkModalWorkflowSource its PAGE_CHOOSER_MODAL_ONLOAD_HANDLERS.
"wagtailadmin/js/page-chooser-modal.js",
"js/draftail-button-link.js",
],
css={"all": ["css/draftail-button-link.css"]},
),
)
features.register_converter_rule(
"contentstate",
FEATURE_NAME,
{
"from_database_format": {
'a[linktype="button"]': ButtonLinkEntityElementHandler(ENTITY_TYPE),
},
"to_database_format": {
"entity_decorators": {ENTITY_TYPE: button_link_entity_decorator},
},
},
)
features.register_link_type(ButtonLinkRichTextHandler)
features.default_features.append(FEATURE_NAME)
+4
View File
@@ -200,6 +200,10 @@ BASE_URL = WAGTAIL_BASE_URL
# redirect "View Live" clicks on the CMS host over to the headless frontend. # 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("/") FRONTEND_BASE_URL = os.environ.get("FRONTEND_BASE_URL", "http://localhost:3000").rstrip("/")
# Shared secret for the frontend cache purge webhook (see dnscms/revalidation.py).
# Unset means no webhooks are sent.
REVALIDATE_WEBHOOK_SECRET = os.environ.get("REVALIDATE_WEBHOOK_SECRET", "")
WAGTAIL_HEADLESS_PREVIEW = { WAGTAIL_HEADLESS_PREVIEW = {
"CLIENT_URLS": {"default": f"{FRONTEND_BASE_URL}/api/preview"}, "CLIENT_URLS": {"default": f"{FRONTEND_BASE_URL}/api/preview"},
"SERVE_BASE_URL": FRONTEND_BASE_URL, "SERVE_BASE_URL": FRONTEND_BASE_URL,
+2 -2
View File
@@ -1,4 +1,4 @@
from .base import * from .base import * # noqa: F403
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = True
@@ -12,6 +12,6 @@ ALLOWED_HOSTS = ["*"]
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
try: try:
from .local import * from .local import * # noqa: F403
except ImportError: except ImportError:
pass pass
+2 -2
View File
@@ -1,8 +1,8 @@
from .base import * from .base import * # noqa: F403
DEBUG = False DEBUG = False
try: try:
from .local import * from .local import * # noqa: F403
except ImportError: except ImportError:
pass pass
@@ -0,0 +1,17 @@
/* Styles the button-link entity as a button in the editor. Front end uses the global .button class. */
.button-link-entity .TooltipEntity {
display: inline-flex;
align-items: center;
gap: 0.25em;
padding: 0.1em 0.7em;
border: 1.5px solid currentColor;
border-radius: 999px;
font-weight: 600;
text-decoration: none;
line-height: 1.4;
}
.button-link-entity .TooltipEntity__icon {
width: 0.85em;
height: 0.85em;
}
@@ -0,0 +1,40 @@
/*
* Draftail plugin for the "button-link" feature (see dnscms/rich_text.py).
* Reuses Wagtail's link chooser (source) and edit/remove tooltip (decorator),
* so buttons insert and edit like normal links. No build step: uses Wagtail's
* globals and React.createElement.
*/
(function () {
"use strict";
var draftail = window.draftail;
var React = window.React;
var Icon = window.wagtail.components.Icon;
var TooltipEntity = draftail.TooltipEntity;
// Wrap TooltipEntity so CSS can style it as a button
function ButtonLink(props) {
var url = props.contentState.getEntity(props.entityKey).getData().url || null;
return React.createElement(
"span",
{ className: "button-link-entity" },
React.createElement(
TooltipEntity,
Object.assign({}, props, {
icon: React.createElement(Icon, { name: "arrow-right-full" }),
label: url || "",
url: url,
})
)
);
}
draftail.registerPlugin(
{
type: "BUTTON",
source: draftail.LinkModalWorkflowSource,
decorator: ButtonLink,
},
"entityTypes"
);
})();
+70
View File
@@ -16,6 +16,76 @@ function registerPricingPanelToggler() {
} }
} }
// Adds a "Duplicate" button to each event occurrence ("Forekomst") row, copying
// its date, time and venue. Wagtail's InlinePanel has no native duplicate.
function registerOccurrenceCloning() {
const FORMSET_PREFIX = "occurrences";
const container = document.getElementById(`id_${FORMSET_PREFIX}-FORMS`);
if (!container) return; // not an event page
const FIELDS = ["start", "end", "venue", "venue_custom"];
const duplicateIcon =
'<svg class="icon icon-copy" aria-hidden="true"><use href="#icon-copy"></use></svg>';
function duplicateRow(sourceChild) {
const addButton = document.getElementById(`id_${FORMSET_PREFIX}-ADD`);
const totalForms = document.getElementById(
`id_${FORMSET_PREFIX}-TOTAL_FORMS`
);
if (!addButton || !totalForms) return;
// `inline_child_occurrences-0` -> form prefix `occurrences-0`, used by field ids.
const sourcePrefix = sourceChild.id.replace(/^inline_child_/, "");
const values = {};
FIELDS.forEach((name) => {
const field = document.getElementById(`id_${sourcePrefix}-${name}`);
values[name] = field ? field.value : "";
});
// The new form takes the current TOTAL_FORMS index. Click Wagtail's real
// "Add" so it appends and initialises the row (e.g. date/time pickers).
const newIndex = parseInt(totalForms.value, 10);
addButton.click();
const newPrefix = `${FORMSET_PREFIX}-${newIndex}`;
FIELDS.forEach((name) => {
const field = document.getElementById(`id_${newPrefix}-${name}`);
if (!field) return;
field.value = values[name];
field.dispatchEvent(new Event("input", { bubbles: true }));
field.dispatchEvent(new Event("change", { bubbles: true }));
});
}
function addDuplicateButton(child) {
const controls = child.querySelector("[data-panel-controls]");
if (!controls || controls.querySelector("[data-duplicate-occurrence]")) return;
const button = document.createElement("button");
button.type = "button";
button.className = "button button--icon text-replace white";
button.title = "Duplicate";
button.setAttribute("data-duplicate-occurrence", "");
button.innerHTML = duplicateIcon;
button.addEventListener("click", () => duplicateRow(child));
// Sit just before the delete button, matching the native control order.
const deleteButton = controls.querySelector('[id$="-DELETE-button"]');
controls.insertBefore(button, deleteButton);
}
function refresh() {
container
.querySelectorAll("[data-inline-panel-child]")
.forEach((child) => addDuplicateButton(child));
}
// Cover existing rows plus any added later (via "Add" or "Duplicate").
refresh();
new MutationObserver(() => refresh()).observe(container, { childList: true });
}
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
registerPricingPanelToggler(); registerPricingPanelToggler();
registerOccurrenceCloning();
}); });
+7
View File
@@ -5,12 +5,19 @@ from wagtail import hooks
from wagtail.models import Page from wagtail.models import Page
from wagtail.search.backends import get_search_backend from wagtail.search.backends import get_search_backend
from dnscms.rich_text import register_button_link_feature
@hooks.register("register_rich_text_features") @hooks.register("register_rich_text_features")
def enable_additional_rich_text_features(features): def enable_additional_rich_text_features(features):
features.default_features.extend(["h5", "h6", "blockquote"]) features.default_features.extend(["h5", "h6", "blockquote"])
@hooks.register("register_rich_text_features")
def register_button_link(features):
register_button_link_feature(features)
@hooks.register("register_schema_query") @hooks.register("register_schema_query")
def override_search_resolver(query_mixins): def override_search_resolver(query_mixins):
""" """
+221
View File
@@ -0,0 +1,221 @@
from django import forms
from django.contrib import messages
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.db import transaction
from django.shortcuts import redirect
from django.urls import reverse
from django.utils.http import urlencode
from django.utils.translation import gettext_lazy as _
from django.views.generic import FormView
from wagtail.admin.views.generic.base import WagtailAdminTemplateMixin
from wagtail.models import ReferenceIndex, Revision
from dnscms.utils import slugify
from events.models import EventOrganizer, EventOrganizerLink, EventPage
from events.views import event_organizer_chooser_viewset
def _check_relations_are_handled():
"""Refuse to merge if EventOrganizer has gained a relation this module doesn't handle."""
for relation in EventOrganizer._meta.related_objects:
if relation.many_to_many and relation.through is EventOrganizerLink:
continue
if relation.related_model is EventOrganizerLink and relation.field.name == "organizer":
continue
raise NotImplementedError(
f"merge_event_organizers does not handle the {relation.name!r} relation"
)
def _repoint_links(survivor, loser):
loser_links = EventOrganizerLink.objects.filter(organizer=loser)
affected_event_ids = set(loser_links.values_list("event_id", flat=True))
# drop the loser's link where the event links both, to respect the unique constraint
doubly_linked = EventOrganizerLink.objects.filter(
organizer=survivor, event_id__in=affected_event_ids
).values_list("event_id", flat=True)
loser_links.filter(event_id__in=doubly_linked).delete()
loser_links.update(organizer=survivor)
return affected_event_ids
def _rewrite_revisions(survivor, loser):
"""Rewrite organizer ids in revision JSON, which would otherwise dangle after the delete."""
affected_event_ids = set()
event_content_type = ContentType.objects.get_for_model(EventPage)
for revision in Revision.objects.filter(content_type=event_content_type).iterator():
links = revision.content.get("organizer_links") or []
if not any(link.get("organizer") == loser.pk for link in links):
continue
seen_organizer_ids = set()
rewritten = []
for link in links:
if link.get("organizer") == loser.pk:
link = {**link, "organizer": survivor.pk}
if link.get("organizer") in seen_organizer_ids:
continue
seen_organizer_ids.add(link.get("organizer"))
rewritten.append(link)
revision.content["organizer_links"] = rewritten
revision.save(update_fields=["content"])
affected_event_ids.add(int(revision.object_id))
return affected_event_ids
@transaction.atomic
def merge_event_organizers(organizer_a, organizer_b, *, name=None, slug=None):
"""
Merge two organizers, keeping the one with the lowest primary key. Event
links and event page revisions are repointed before the other organizer is
deleted. ``name``/``slug`` override the survivor's fields; a blank
``association``/``external_url`` is filled in from the deleted organizer.
"""
if organizer_a.pk == organizer_b.pk:
raise ValueError("Cannot merge an organizer with itself.")
_check_relations_are_handled()
survivor, loser = sorted([organizer_a, organizer_b], key=lambda organizer: organizer.pk)
affected_event_ids = _repoint_links(survivor, loser)
affected_event_ids |= _rewrite_revisions(survivor, loser)
if name:
survivor.name = name
if slug:
survivor.slug = slug
if not survivor.association_id:
survivor.association_id = loser.association_id
if not survivor.external_url:
survivor.external_url = loser.external_url
survivor.save()
loser.delete()
# the bulk link update bypasses the save-time reference index maintenance
for event in EventPage.objects.filter(pk__in=affected_event_ids):
ReferenceIndex.create_or_update_for_object(event)
return survivor
CUSTOM_VALUE = "__custom__"
class MergeOrganizersSelectForm(forms.Form):
organizer_a = forms.ModelChoiceField(
label=_("First organizer"),
queryset=EventOrganizer.objects.all(),
widget=event_organizer_chooser_viewset.widget_class(),
)
organizer_b = forms.ModelChoiceField(
label=_("Second organizer"),
queryset=EventOrganizer.objects.all(),
widget=event_organizer_chooser_viewset.widget_class(),
)
def clean(self):
cleaned = super().clean()
organizer_a = cleaned.get("organizer_a")
organizer_b = cleaned.get("organizer_b")
if organizer_a and organizer_b and organizer_a.pk == organizer_b.pk:
raise ValidationError(_("Pick two different organizers."))
return cleaned
class MergeOrganizersConfirmForm(forms.Form):
name = forms.ChoiceField(label=_("Name to keep"), widget=forms.RadioSelect)
name_custom = forms.CharField(label=_("Custom name"), required=False, max_length=100)
slug = forms.ChoiceField(label=_("Slug to keep"), widget=forms.RadioSelect)
slug_custom = forms.CharField(label=_("Custom slug"), required=False, max_length=255)
def __init__(self, *args, survivor, loser, **kwargs):
super().__init__(*args, **kwargs)
self.fields["name"].choices = self._choices(survivor.name, loser.name)
self.fields["name"].initial = survivor.name
self.fields["slug"].choices = self._choices(survivor.slug, loser.slug)
self.fields["slug"].initial = survivor.slug
@staticmethod
def _choices(*values):
unique_values = dict.fromkeys(values)
return [(value, value) for value in unique_values] + [
(CUSTOM_VALUE, _("Something else (enter below)"))
]
def _resolve(self, cleaned, field):
choice = cleaned.get(field)
custom = (cleaned.get(f"{field}_custom") or "").strip()
if choice == CUSTOM_VALUE:
if not custom:
self.add_error(
f"{field}_custom", _("Enter a value, or pick an existing one above.")
)
return custom
return choice
def clean(self):
cleaned = super().clean()
cleaned["final_name"] = self._resolve(cleaned, "name")
cleaned["final_slug"] = slugify(self._resolve(cleaned, "slug") or "")
return cleaned
class MergeOrganizersSelectView(WagtailAdminTemplateMixin, FormView):
template_name = "events/merge_organizers_select.html"
form_class = MergeOrganizersSelectForm
page_title = _("Merge organizers")
header_icon = "group"
def form_valid(self, form):
params = urlencode(
{
"organizer_a": form.cleaned_data["organizer_a"].pk,
"organizer_b": form.cleaned_data["organizer_b"].pk,
}
)
return redirect(f"{reverse('events_merge_organizers_confirm')}?{params}")
class MergeOrganizersConfirmView(WagtailAdminTemplateMixin, FormView):
template_name = "events/merge_organizers_confirm.html"
form_class = MergeOrganizersConfirmForm
page_title = _("Merge organizers")
header_icon = "group"
def dispatch(self, request, *args, **kwargs):
try:
pks = {int(request.GET.get(param, "")) for param in ("organizer_a", "organizer_b")}
except ValueError:
pks = set()
organizers = list(EventOrganizer.objects.filter(pk__in=pks).order_by("pk"))
if len(organizers) != 2:
messages.error(request, _("Pick two different organizers to merge."))
return redirect("events_merge_organizers")
self.survivor, self.loser = organizers
return super().dispatch(request, *args, **kwargs)
def get_form_kwargs(self):
return {**super().get_form_kwargs(), "survivor": self.survivor, "loser": self.loser}
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["survivor"] = self.survivor
context["loser"] = self.loser
context["survivor_event_count"] = self.survivor.organized_events.count()
context["loser_event_count"] = self.loser.organized_events.count()
return context
def form_valid(self, form):
survivor = merge_event_organizers(
self.survivor,
self.loser,
name=form.cleaned_data["final_name"],
slug=form.cleaned_data["final_slug"],
)
messages.success(
self.request,
_('Merged "%(loser)s" into "%(survivor)s".')
% {"loser": self.loser.name, "survivor": survivor.name},
)
return redirect(reverse("wagtailsnippets_events_eventorganizer:list"))
@@ -0,0 +1,19 @@
# Generated by Django 6.0.5 on 2026-07-02 14:53
import wagtail.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('events', '0055_alter_eventcategory_options_and_more'),
]
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), ('schedule', 33), ('embed', 34), ('raw_html', 35)], 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.blocks.CharBlock', (), {'help_text': 'Overskrift for timeplanen (valgfritt)', 'label': 'Tittel', 'max_length': 64, 'required': False}), 28: ('wagtail.blocks.CharBlock', (), {'help_text': 'Fritekst, f.eks. "13:00", "20:00 23:00" eller "23.06"', 'label': 'Tidspunkt/dato', 'max_length': 64, 'required': True}), 29: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link'], 'help_text': 'Kort tittel på én linje. Du kan legge inn lenker.', 'label': 'Tittel', 'required': True}), 30: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link'], 'help_text': 'Valgfri tilleggsinformasjon.', 'label': 'Beskrivelse', 'required': False}), 31: ('wagtail.blocks.StructBlock', [[('time', 28), ('title', 29), ('description', 30)]], {}), 32: ('wagtail.blocks.ListBlock', (31,), {'label': 'Punkter', 'min_num': 1}), 33: ('wagtail.blocks.StructBlock', [[('title', 27), ('items', 32)]], {}), 34: ('wagtail.embeds.blocks.EmbedBlock', (), {}), 35: ('wagtail.blocks.RawHTMLBlock', (), {})}, default=[('paragraph', '')]),
),
]
File diff suppressed because one or more lines are too long
@@ -0,0 +1,19 @@
# Generated by Django 6.0.5 on 2026-07-04 22:29
import wagtail.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('events', '0057_alter_eventpage_body'),
]
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), ('schedule', 33), ('embed', 34), ('raw_html', 35)], 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.blocks.CharBlock', (), {'help_text': 'Overskrift for timeplanen (valgfritt)', 'label': 'Tittel', 'max_length': 64, 'required': False}), 28: ('wagtail.blocks.CharBlock', (), {'help_text': 'Fritekst, f.eks. "13:00", "20:00 23:00" eller "23.06"', 'label': 'Tidspunkt/dato', 'max_length': 64, 'required': True}), 29: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link'], 'help_text': 'Kort tittel på én linje. Du kan legge inn lenker.', 'label': 'Tittel', 'required': True}), 30: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link'], 'help_text': 'Valgfri tilleggsinformasjon.', 'label': 'Beskrivelse', 'required': False}), 31: ('wagtail.blocks.StructBlock', [[('time', 28), ('title', 29), ('description', 30)]], {}), 32: ('wagtail.blocks.ListBlock', (31,), {'label': 'Punkter', 'min_num': 1}), 33: ('wagtail.blocks.StructBlock', [[('title', 27), ('items', 32)]], {}), 34: ('wagtail.embeds.blocks.EmbedBlock', (), {}), 35: ('wagtail.blocks.RawHTMLBlock', (), {})}, default=[('paragraph', '')]),
),
]
+1 -1
View File
@@ -164,7 +164,7 @@ class EventOrganizerLink(Orderable):
] ]
@register_snippet # registered as a snippet via EventOrganizerSnippetViewSet in wagtail_hooks.py
@register_query_field("eventOrganizer", "eventOrganizers") @register_query_field("eventOrganizer", "eventOrganizers")
class EventOrganizer(index.Indexed, ClusterableModel): class EventOrganizer(index.Indexed, ClusterableModel):
objects = WPAwareManager() objects = WPAwareManager()
@@ -0,0 +1,55 @@
{% extends "wagtailadmin/generic/base.html" %}
{% load i18n wagtailadmin_tags %}
{% block main_content %}
<table class="listing">
<thead>
<tr>
<th>{% trans "Outcome" %}</th>
<th>{% trans "ID" %}</th>
<th>{% trans "Name" %}</th>
<th>{% trans "Slug" %}</th>
<th>{% trans "Association" %}</th>
<th>{% trans "External URL" %}</th>
<th>{% trans "Events" %}</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>{% trans "Survives" %}</strong></td>
<td>{{ survivor.pk }}</td>
<td>{{ survivor.name }}</td>
<td>{{ survivor.slug }}</td>
<td>{{ survivor.association|default:"—" }}</td>
<td>{{ survivor.external_url|default:"—" }}</td>
<td>{{ survivor_event_count }}</td>
</tr>
<tr>
<td><strong>{% trans "Is deleted" %}</strong></td>
<td>{{ loser.pk }}</td>
<td>{{ loser.name }}</td>
<td>{{ loser.slug }}</td>
<td>{{ loser.association|default:"—" }}</td>
<td>{{ loser.external_url|default:"—" }}</td>
<td>{{ loser_event_count }}</td>
</tr>
</tbody>
</table>
<p class="help-block help-warning">
{% blocktrans with loser_name=loser.name survivor_name=survivor.name trimmed %}
Events organized by “{{ loser_name }}” will be moved to “{{ survivor_name }}”,
and “{{ loser_name }}” will be deleted. This cannot be undone.
{% endblocktrans %}
</p>
<form method="post" novalidate>
{% csrf_token %}
<ul class="fields">
{% for field in form.visible_fields %}
<li>{% formattedfield field %}</li>
{% endfor %}
</ul>
<button type="submit" class="button serious">{% trans "Merge organizers" %}</button>
</form>
{% endblock %}
@@ -0,0 +1,30 @@
{% extends "wagtailadmin/generic/base.html" %}
{% load i18n wagtailadmin_tags %}
{% block main_content %}
<p>
{% trans "Pick the two organizers to merge. The one with the lowest ID survives; the other one is deleted after its events have been moved over." %}
</p>
<form method="post" novalidate>
{% csrf_token %}
{% if form.non_field_errors %}
<div class="help-block help-critical">{{ form.non_field_errors }}</div>
{% endif %}
<ul class="fields">
{% for field in form.visible_fields %}
<li>{% formattedfield field %}</li>
{% endfor %}
</ul>
<button type="submit" class="button">{% trans "Continue" %}</button>
</form>
{% endblock %}
{% block extra_js %}
{{ block.super }}
{{ form.media.js }}
{% endblock %}
{% block extra_css %}
{{ block.super }}
{{ form.media.css }}
{% endblock %}
+46
View File
@@ -1,11 +1,55 @@
from django import forms
from django.db.models import Q
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from wagtail.admin.forms import WagtailAdminModelForm from wagtail.admin.forms import WagtailAdminModelForm
from wagtail.admin.forms.choosers import BaseFilterForm
from wagtail.admin.ui.tables import UpdatedAtColumn
from wagtail.admin.views.generic.chooser import ChooseResultsView, ChooseView
from wagtail.admin.viewsets.chooser import ChooserViewSet from wagtail.admin.viewsets.chooser import ChooserViewSet
from wagtail.snippets.views.snippets import SnippetViewSet
from dnscms.utils import slugify from dnscms.utils import slugify
from events.models import EventOrganizer from events.models import EventOrganizer
class EventOrganizerSearchForm(BaseFilterForm):
"""Substring search on name/slug"""
q = forms.CharField(
label=_("Search term"),
widget=forms.TextInput(attrs={"placeholder": _("Search")}),
required=False,
)
def filter(self, objects):
objects = super().filter(objects)
search_query = self.cleaned_data.get("q")
if search_query:
objects = objects.filter(
Q(name__icontains=search_query) | Q(slug__icontains=search_query)
)
self.is_searching = True
self.search_query = search_query
return objects
class EventOrganizerChooseView(ChooseView):
filter_form_class = EventOrganizerSearchForm
class EventOrganizerChooseResultsView(ChooseResultsView):
filter_form_class = EventOrganizerSearchForm
class EventOrganizerSnippetViewSet(SnippetViewSet):
model = EventOrganizer
icon = "group"
list_display = ["name", "slug", UpdatedAtColumn()]
# unset the search backend so the listing search filters on icontains instead
search_backend_name = None
search_fields = ["name", "slug"]
class EventOrganizerCreationForm(WagtailAdminModelForm): class EventOrganizerCreationForm(WagtailAdminModelForm):
class Meta: class Meta:
model = EventOrganizer model = EventOrganizer
@@ -29,6 +73,8 @@ class EventOrganizerChooserViewSet(ChooserViewSet):
choose_another_text = _("Choose another organizer") choose_another_text = _("Choose another organizer")
edit_item_text = _("Edit this organizer") edit_item_text = _("Edit this organizer")
creation_form_class = EventOrganizerCreationForm creation_form_class = EventOrganizerCreationForm
choose_view_class = EventOrganizerChooseView
choose_results_view_class = EventOrganizerChooseResultsView
event_organizer_chooser_viewset = EventOrganizerChooserViewSet("event_organizer_chooser") event_organizer_chooser_viewset = EventOrganizerChooserViewSet("event_organizer_chooser")
+42 -1
View File
@@ -1,7 +1,48 @@
from django.urls import path, reverse_lazy
from django.utils.translation import gettext_lazy as _
from wagtail import hooks from wagtail import hooks
from wagtail.admin.auth import user_passes_test
from wagtail.admin.menu import MenuItem
from wagtail.snippets.models import register_snippet
from .admin import event_sidebar_viewset, event_explorer_viewset from .admin import event_sidebar_viewset, event_explorer_viewset
from .views import event_organizer_chooser_viewset from .merge_organizers import MergeOrganizersConfirmView, MergeOrganizersSelectView
from .views import EventOrganizerSnippetViewSet, event_organizer_chooser_viewset
register_snippet(EventOrganizerSnippetViewSet)
superuser_only = user_passes_test(lambda user: user.is_superuser)
class SuperuserMenuItem(MenuItem):
def is_shown(self, request):
return request.user.is_superuser
@hooks.register("register_admin_urls")
def register_merge_organizers_urls():
return [
path(
"merge-organizers/",
superuser_only(MergeOrganizersSelectView.as_view()),
name="events_merge_organizers",
),
path(
"merge-organizers/confirm/",
superuser_only(MergeOrganizersConfirmView.as_view()),
name="events_merge_organizers_confirm",
),
]
@hooks.register("register_settings_menu_item")
def register_merge_organizers_menu_item():
return SuperuserMenuItem(
_("Merge organizers"),
reverse_lazy("events_merge_organizers"),
icon_name="group",
order=1100,
)
@hooks.register("register_admin_viewset") @hooks.register("register_admin_viewset")
@@ -0,0 +1,19 @@
# Generated by Django 6.0.5 on 2026-07-02 14:30
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), ('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'), ('studioNote', 'Note (STUDiO)'), ('studioCalendar', 'Kalender (STUDiO)'), ('studioGuitar', 'Gitar (STUDiO)'), ('studioDisco', 'Discokule (STUDiO)'), ('studioMole', 'Muldvarpen (STUDiO)')], '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)]], {})}),
),
]
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,19 @@
# Generated by Django 6.0.5 on 2026-07-02 14:53
import wagtail.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('news', '0019_alter_news_options'),
]
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), ('schedule', 33), ('embed', 34), ('raw_html', 35)], 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.blocks.CharBlock', (), {'help_text': 'Overskrift for timeplanen (valgfritt)', 'label': 'Tittel', 'max_length': 64, 'required': False}), 28: ('wagtail.blocks.CharBlock', (), {'help_text': 'Fritekst, f.eks. "13:00", "20:00 23:00" eller "23.06"', 'label': 'Tidspunkt/dato', 'max_length': 64, 'required': True}), 29: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link'], 'help_text': 'Kort tittel på én linje. Du kan legge inn lenker.', 'label': 'Tittel', 'required': True}), 30: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link'], 'help_text': 'Valgfri tilleggsinformasjon.', 'label': 'Beskrivelse', 'required': False}), 31: ('wagtail.blocks.StructBlock', [[('time', 28), ('title', 29), ('description', 30)]], {}), 32: ('wagtail.blocks.ListBlock', (31,), {'label': 'Punkter', 'min_num': 1}), 33: ('wagtail.blocks.StructBlock', [[('title', 27), ('items', 32)]], {}), 34: ('wagtail.embeds.blocks.EmbedBlock', (), {}), 35: ('wagtail.blocks.RawHTMLBlock', (), {})}, default=[('paragraph', '')]),
),
]
File diff suppressed because one or more lines are too long
@@ -0,0 +1,19 @@
# Generated by Django 6.0.5 on 2026-07-04 22:29
import wagtail.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('news', '0021_alter_newspage_body'),
]
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), ('schedule', 33), ('embed', 34), ('raw_html', 35)], 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.blocks.CharBlock', (), {'help_text': 'Overskrift for timeplanen (valgfritt)', 'label': 'Tittel', 'max_length': 64, 'required': False}), 28: ('wagtail.blocks.CharBlock', (), {'help_text': 'Fritekst, f.eks. "13:00", "20:00 23:00" eller "23.06"', 'label': 'Tidspunkt/dato', 'max_length': 64, 'required': True}), 29: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link'], 'help_text': 'Kort tittel på én linje. Du kan legge inn lenker.', 'label': 'Tittel', 'required': True}), 30: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link'], 'help_text': 'Valgfri tilleggsinformasjon.', 'label': 'Beskrivelse', 'required': False}), 31: ('wagtail.blocks.StructBlock', [[('time', 28), ('title', 29), ('description', 30)]], {}), 32: ('wagtail.blocks.ListBlock', (31,), {'label': 'Punkter', 'min_num': 1}), 33: ('wagtail.blocks.StructBlock', [[('title', 27), ('items', 32)]], {}), 34: ('wagtail.embeds.blocks.EmbedBlock', (), {}), 35: ('wagtail.blocks.RawHTMLBlock', (), {})}, default=[('paragraph', '')]),
),
]
-2
View File
@@ -13,8 +13,6 @@ from wagtail.snippets.models import register_snippet
@register_streamfield_block @register_streamfield_block
class OpeningHoursRangeBlock(blocks.StructBlock): class OpeningHoursRangeBlock(blocks.StructBlock):
# closed = blocks.BooleanBlock(required=False, help_text="Kryss av her om lokalet er stengt.")
# blocks.RegexBlock(regex=r'^\d\d:\d\d$', error_messages={'invalid': 'Må være på formatet HH:MM'}, help_text="Tidspunkt på formatet HH:MM")
time_from = blocks.TimeBlock(required=False, label="Åpner") time_from = blocks.TimeBlock(required=False, label="Åpner")
time_to = blocks.TimeBlock(required=False, label="Stenger") time_to = blocks.TimeBlock(required=False, label="Stenger")
custom = blocks.CharBlock( custom = blocks.CharBlock(
-2
View File
@@ -1,3 +1 @@
from django.shortcuts import render
# Create your views here. # Create your views here.
+7 -7
View File
@@ -5,10 +5,10 @@ description = ""
authors = [{ name = "EDB", email = "edb@neuf.no" }] authors = [{ name = "EDB", email = "edb@neuf.no" }]
requires-python = ">=3.14, <3.15" requires-python = ">=3.14, <3.15"
dependencies = [ dependencies = [
"wagtail>=7.4.1,<8", "wagtail>=7.4.2,<8",
"wagtail-grapple>=0.31.0,<0.32", "wagtail-grapple>=0.31.0,<0.32",
"wagtail-headless-preview>=0.8,<0.9", "wagtail-headless-preview>=0.9,<0.10",
"django>=6.0.5,<7", "django>=6.0.6,<7",
"django-extensions>=4.1,<5", "django-extensions>=4.1,<5",
"psycopg2-binary>=2.9.12,<3", "psycopg2-binary>=2.9.12,<3",
"gunicorn>=26.0.0,<27", "gunicorn>=26.0.0,<27",
@@ -17,11 +17,11 @@ dependencies = [
[dependency-groups] [dependency-groups]
dev = [ dev = [
"ruff>=0.15.13,<0.16", "ruff>=0.16.1,<0.17",
"pytest>=9.0.3,<10", "pytest>=9.1.1,<10",
"pytest-cov>=7.0.0,<8", "pytest-cov>=7.0.0,<8",
"pytest-django>=4.12.0,<5", "pytest-django>=4.12.0,<5",
"wagtail-factories>=4.4.0,<5", "wagtail-factories>=4.5.0,<5",
] ]
[tool.uv] [tool.uv]
@@ -33,11 +33,11 @@ build-backend = "hatchling.build"
[tool.ruff] [tool.ruff]
line-length = 99 line-length = 99
exclude = ["**/migrations/*.py"]
[tool.ruff.lint] [tool.ruff.lint]
select = ["F", "E", "W", "Q", "UP", "DJ"] select = ["F", "E", "W", "Q", "UP", "DJ"]
ignore = [] ignore = []
exclude = ["**/migrations/*.py"]
[tool.pytest.ini_options] [tool.pytest.ini_options]
DJANGO_SETTINGS_MODULE = "dnscms.settings.test" DJANGO_SETTINGS_MODULE = "dnscms.settings.test"
+2 -2
View File
@@ -2,5 +2,5 @@ from django.apps import AppConfig
class SponsorsConfig(AppConfig): class SponsorsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField' default_auto_field = "django.db.models.BigAutoField"
name = 'sponsors' name = "sponsors"
@@ -0,0 +1,19 @@
# Generated by Django 6.0.5 on 2026-07-02 14:53
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), ('schedule', 33), ('embed', 34), ('raw_html', 35)], 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.blocks.CharBlock', (), {'help_text': 'Overskrift for timeplanen (valgfritt)', 'label': 'Tittel', 'max_length': 64, 'required': False}), 28: ('wagtail.blocks.CharBlock', (), {'help_text': 'Fritekst, f.eks. "13:00", "20:00 23:00" eller "23.06"', 'label': 'Tidspunkt/dato', 'max_length': 64, 'required': True}), 29: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link'], 'help_text': 'Kort tittel på én linje. Du kan legge inn lenker.', 'label': 'Tittel', 'required': True}), 30: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link'], 'help_text': 'Valgfri tilleggsinformasjon.', 'label': 'Beskrivelse', 'required': False}), 31: ('wagtail.blocks.StructBlock', [[('time', 28), ('title', 29), ('description', 30)]], {}), 32: ('wagtail.blocks.ListBlock', (31,), {'label': 'Punkter', 'min_num': 1}), 33: ('wagtail.blocks.StructBlock', [[('title', 27), ('items', 32)]], {}), 34: ('wagtail.embeds.blocks.EmbedBlock', (), {}), 35: ('wagtail.blocks.RawHTMLBlock', (), {})}),
),
]
File diff suppressed because one or more lines are too long
@@ -0,0 +1,19 @@
# Generated by Django 6.0.5 on 2026-07-04 22:29
import wagtail.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('sponsors', '0003_alter_sponsorspage_body'),
]
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), ('schedule', 33), ('embed', 34), ('raw_html', 35)], 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.blocks.CharBlock', (), {'help_text': 'Overskrift for timeplanen (valgfritt)', 'label': 'Tittel', 'max_length': 64, 'required': False}), 28: ('wagtail.blocks.CharBlock', (), {'help_text': 'Fritekst, f.eks. "13:00", "20:00 23:00" eller "23.06"', 'label': 'Tidspunkt/dato', 'max_length': 64, 'required': True}), 29: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link'], 'help_text': 'Kort tittel på én linje. Du kan legge inn lenker.', 'label': 'Tittel', 'required': True}), 30: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link'], 'help_text': 'Valgfri tilleggsinformasjon.', 'label': 'Beskrivelse', 'required': False}), 31: ('wagtail.blocks.StructBlock', [[('time', 28), ('title', 29), ('description', 30)]], {}), 32: ('wagtail.blocks.ListBlock', (31,), {'label': 'Punkter', 'min_num': 1}), 33: ('wagtail.blocks.StructBlock', [[('title', 27), ('items', 32)]], {}), 34: ('wagtail.embeds.blocks.EmbedBlock', (), {}), 35: ('wagtail.blocks.RawHTMLBlock', (), {})}),
),
]
@@ -0,0 +1,19 @@
# Generated by Django 6.0.5 on 2026-07-02 14:30
import wagtail.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('studio', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='studiopage',
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), ('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'), ('studioNote', 'Note (STUDiO)'), ('studioCalendar', 'Kalender (STUDiO)'), ('studioGuitar', 'Gitar (STUDiO)'), ('studioDisco', 'Discokule (STUDiO)'), ('studioMole', 'Muldvarpen (STUDiO)')], '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)]], {})}),
),
]
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+29
View File
@@ -4,6 +4,7 @@ import pytest
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import connection from django.db import connection
from django.test.utils import CaptureQueriesContext from django.test.utils import CaptureQueriesContext
from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from events.admin import EventDateColumn, OrganizersColumn from events.admin import EventDateColumn, OrganizersColumn
@@ -572,3 +573,31 @@ def test_graphql_event_index_returns_all_fields_for_comprehensive_event(
assert datetime.fromisoformat(venue_occ["end"]) == venue_occ_db.end 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["start"]) == custom_occ_db.start
assert datetime.fromisoformat(custom_occ["end"]) == custom_occ_db.end assert datetime.fromisoformat(custom_occ["end"]) == custom_occ_db.end
@pytest.fixture
def searchable_organizers(db):
return [
EventOrganizer.objects.create(name="Arrangementsutvalget", slug="arrangementsutvalget"),
EventOrganizer.objects.create(name="Cinema Neuf", slug="kino"),
]
@pytest.mark.parametrize(
"url_name",
["event_organizer_chooser:choose", "wagtailsnippets_events_eventorganizer:list"],
)
def test_organizer_search_matches_substrings_of_name_and_slug(
admin_client, searchable_organizers, url_name
):
url = reverse(url_name)
# mid-word match inside a compound name
content = admin_client.get(url, {"q": "utvalget"}).content.decode()
assert "Arrangementsutvalget" in content
assert "Cinema Neuf" not in content
# slug-only match
content = admin_client.get(url, {"q": "kino"}).content.decode()
assert "Cinema Neuf" in content
assert "Arrangementsutvalget" not in content
+275
View File
@@ -0,0 +1,275 @@
import pytest
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from django.urls import reverse
from wagtail.models import ReferenceIndex
from events.merge_organizers import CUSTOM_VALUE, merge_event_organizers
from events.models import EventOrganizer, EventOrganizerLink, EventPage
from tests.conftest import AssociationPageFactory, EventPageFactory
pytestmark = pytest.mark.django_db
@pytest.fixture
def organizers(db):
keeper = EventOrganizer.objects.create(name="Kulturutvalget", slug="kulturutvalget")
goner = EventOrganizer.objects.create(name="KU", slug="ku")
return keeper, goner
@pytest.fixture
def editor_client(client, django_user_model):
user = django_user_model.objects.create_user(username="editor", password="pw")
user.user_permissions.add(Permission.objects.get(codename="access_admin"))
client.force_login(user)
return client
def test_merge_repoints_event_links_and_deletes_loser(event_index, organizers):
keeper, goner = organizers
event = EventPageFactory(parent=event_index)
EventOrganizerLink.objects.create(event=event, organizer=goner)
merged = merge_event_organizers(keeper, goner)
assert merged.pk == keeper.pk
assert not EventOrganizer.objects.filter(pk=goner.pk).exists()
assert list(event.organizer_links.values_list("organizer_id", flat=True)) == [keeper.pk]
def test_lowest_pk_survives_regardless_of_argument_order(organizers):
keeper, goner = organizers
merged = merge_event_organizers(goner, keeper)
assert merged.pk == keeper.pk
assert not EventOrganizer.objects.filter(pk=goner.pk).exists()
def test_merge_applies_chosen_name_and_slug(organizers):
keeper, goner = organizers
merged = merge_event_organizers(keeper, goner, name="KU", slug="ku")
assert merged.name == "KU"
assert merged.slug == "ku"
def test_merge_keeps_survivor_name_and_slug_by_default(organizers):
keeper, goner = organizers
merged = merge_event_organizers(keeper, goner)
assert merged.name == "Kulturutvalget"
assert merged.slug == "kulturutvalget"
def test_merge_collapses_duplicate_links_on_same_event(event_index, organizers):
keeper, goner = organizers
event = EventPageFactory(parent=event_index)
EventOrganizerLink.objects.create(event=event, organizer=keeper)
EventOrganizerLink.objects.create(event=event, organizer=goner)
merge_event_organizers(keeper, goner)
assert list(event.organizer_links.values_list("organizer_id", flat=True)) == [keeper.pk]
def test_merge_fills_blank_fields_from_loser(association_index, organizers):
keeper, goner = organizers
association = AssociationPageFactory(parent=association_index)
goner.association = association
goner.external_url = "https://example.com"
goner.save()
merged = merge_event_organizers(keeper, goner)
assert merged.association_id == association.pk
assert merged.external_url == "https://example.com"
def test_merge_keeps_survivor_fields_when_set(organizers):
keeper, goner = organizers
keeper.external_url = "https://keeper.example.com"
keeper.save()
goner.external_url = "https://goner.example.com"
goner.save()
merged = merge_event_organizers(keeper, goner)
assert merged.external_url == "https://keeper.example.com"
def test_merge_rewrites_event_page_revisions(event_index, organizers):
keeper, goner = organizers
event = EventPageFactory(parent=event_index)
EventOrganizerLink.objects.create(event=event, organizer=goner)
revision = EventPage.objects.get(pk=event.pk).save_revision()
assert [link["organizer"] for link in revision.content["organizer_links"]] == [goner.pk]
merge_event_organizers(keeper, goner)
revision.refresh_from_db()
assert [link["organizer"] for link in revision.content["organizer_links"]] == [keeper.pk]
def test_merge_dedupes_organizers_within_revisions(event_index, organizers):
keeper, goner = organizers
event = EventPageFactory(parent=event_index)
EventOrganizerLink.objects.create(event=event, organizer=keeper)
EventOrganizerLink.objects.create(event=event, organizer=goner)
revision = EventPage.objects.get(pk=event.pk).save_revision()
merge_event_organizers(keeper, goner)
revision.refresh_from_db()
assert [link["organizer"] for link in revision.content["organizer_links"]] == [keeper.pk]
def test_merge_updates_reference_index(event_index, organizers):
keeper, goner = organizers
event = EventPageFactory(parent=event_index)
EventOrganizerLink.objects.create(event=event, organizer=goner)
ReferenceIndex.create_or_update_for_object(EventPage.objects.get(pk=event.pk))
organizer_ct = ContentType.objects.get_for_model(EventOrganizer)
assert ReferenceIndex.objects.filter(
to_content_type=organizer_ct, to_object_id=str(goner.pk)
).exists()
merge_event_organizers(keeper, goner)
assert not ReferenceIndex.objects.filter(
to_content_type=organizer_ct, to_object_id=str(goner.pk)
).exists()
assert ReferenceIndex.objects.filter(
to_content_type=organizer_ct, to_object_id=str(keeper.pk)
).exists()
def test_merge_refuses_same_organizer(organizers):
keeper, _ = organizers
with pytest.raises(ValueError):
merge_event_organizers(keeper, keeper)
def test_merge_views_require_superuser(editor_client, organizers):
keeper, goner = organizers
confirm_url = reverse("events_merge_organizers_confirm")
urls = [
reverse("events_merge_organizers"),
f"{confirm_url}?organizer_a={keeper.pk}&organizer_b={goner.pk}",
]
for url in urls:
response = editor_client.get(url)
assert response.status_code == 302
assert response.url == reverse("wagtailadmin_home")
assert EventOrganizer.objects.filter(pk=goner.pk).exists()
def test_select_view_redirects_to_confirm(admin_client, organizers):
keeper, goner = organizers
response = admin_client.post(
reverse("events_merge_organizers"),
{"organizer_a": keeper.pk, "organizer_b": goner.pk},
)
assert response.status_code == 302
confirm_url = reverse("events_merge_organizers_confirm")
assert response.url == f"{confirm_url}?organizer_a={keeper.pk}&organizer_b={goner.pk}"
def test_select_view_rejects_same_organizer_twice(admin_client, organizers):
keeper, _ = organizers
response = admin_client.post(
reverse("events_merge_organizers"),
{"organizer_a": keeper.pk, "organizer_b": keeper.pk},
)
assert response.status_code == 200
assert "Pick two different organizers." in response.content.decode()
def test_confirm_view_shows_both_organizers(admin_client, organizers):
keeper, goner = organizers
confirm_url = reverse("events_merge_organizers_confirm")
response = admin_client.get(f"{confirm_url}?organizer_a={goner.pk}&organizer_b={keeper.pk}")
content = response.content.decode()
assert response.status_code == 200
assert "Kulturutvalget" in content
assert "KU" in content
def test_confirm_view_rejects_missing_or_equal_organizers(admin_client, organizers):
keeper, _ = organizers
confirm_url = reverse("events_merge_organizers_confirm")
queries = [
"",
f"?organizer_a={keeper.pk}&organizer_b={keeper.pk}",
f"?organizer_a=abc&organizer_b={keeper.pk}",
f"?organizer_a={keeper.pk}&organizer_b=999999",
]
for query in queries:
response = admin_client.get(confirm_url + query)
assert response.status_code == 302
assert response.url == reverse("events_merge_organizers")
def test_confirm_view_merges_with_picked_values(admin_client, event_index, organizers):
keeper, goner = organizers
event = EventPageFactory(parent=event_index)
EventOrganizerLink.objects.create(event=event, organizer=goner)
confirm_url = reverse("events_merge_organizers_confirm")
response = admin_client.post(
f"{confirm_url}?organizer_a={keeper.pk}&organizer_b={goner.pk}",
{"name": goner.name, "name_custom": "", "slug": goner.slug, "slug_custom": ""},
)
assert response.status_code == 302
assert response.url == reverse("wagtailsnippets_events_eventorganizer:list")
keeper.refresh_from_db()
assert keeper.name == "KU"
assert keeper.slug == "ku"
assert not EventOrganizer.objects.filter(pk=goner.pk).exists()
assert list(event.organizer_links.values_list("organizer_id", flat=True)) == [keeper.pk]
def test_confirm_view_merges_with_custom_values(admin_client, organizers):
keeper, goner = organizers
confirm_url = reverse("events_merge_organizers_confirm")
response = admin_client.post(
f"{confirm_url}?organizer_a={keeper.pk}&organizer_b={goner.pk}",
{
"name": CUSTOM_VALUE,
"name_custom": "Kulturutvalget (KU)",
"slug": CUSTOM_VALUE,
"slug_custom": "Kulturutvalget KU",
},
)
assert response.status_code == 302
keeper.refresh_from_db()
assert keeper.name == "Kulturutvalget (KU)"
assert keeper.slug == "kulturutvalget-ku"
def test_confirm_view_requires_custom_value_when_picked(admin_client, organizers):
keeper, goner = organizers
confirm_url = reverse("events_merge_organizers_confirm")
response = admin_client.post(
f"{confirm_url}?organizer_a={keeper.pk}&organizer_b={goner.pk}",
{"name": CUSTOM_VALUE, "name_custom": "", "slug": keeper.slug, "slug_custom": ""},
)
assert response.status_code == 200
assert EventOrganizer.objects.filter(pk=goner.pk).exists()
+127
View File
@@ -0,0 +1,127 @@
import logging
import urllib.error
import pytest
from dnscms import revalidation
from events.models import EventCategory
from tests.conftest import GenericPageFactory
WEBHOOK_URL = "https://frontend.example.com/api/revalidate"
class InlineThread:
def __init__(self, target=None, args=(), kwargs=None, daemon=None):
self._target = target
self._args = args
self._kwargs = kwargs or {}
def start(self):
self._target(*self._args, **self._kwargs)
@pytest.fixture
def webhook_calls(monkeypatch, settings):
settings.REVALIDATE_WEBHOOK_SECRET = "s3cret"
settings.FRONTEND_BASE_URL = "https://frontend.example.com"
calls = []
monkeypatch.setattr(revalidation.threading, "Thread", InlineThread)
monkeypatch.setattr(
revalidation, "_send_webhook", lambda url, secret, reason: calls.append((url, secret))
)
return calls
def test_page_publish_triggers_webhook(
home_page, webhook_calls, django_capture_on_commit_callbacks
):
page = GenericPageFactory(parent=home_page)
with django_capture_on_commit_callbacks(execute=True):
page.save_revision().publish()
assert webhook_calls == [(WEBHOOK_URL, "s3cret")]
def test_page_unpublish_triggers_webhook(
home_page, webhook_calls, django_capture_on_commit_callbacks
):
page = GenericPageFactory(parent=home_page)
page.save_revision().publish()
webhook_calls.clear()
with django_capture_on_commit_callbacks(execute=True):
page.unpublish()
assert webhook_calls == [(WEBHOOK_URL, "s3cret")]
def test_page_delete_triggers_webhook(
home_page, webhook_calls, django_capture_on_commit_callbacks
):
# live=False isolates the post_delete path: deleting a live page would
# also fire page_unpublished (a harmless duplicate purge in production)
page = GenericPageFactory(parent=home_page, live=False)
webhook_calls.clear()
with django_capture_on_commit_callbacks(execute=True):
page.delete()
assert webhook_calls == [(WEBHOOK_URL, "s3cret")]
@pytest.mark.django_db
def test_snippet_save_triggers_webhook(webhook_calls, django_capture_on_commit_callbacks):
with django_capture_on_commit_callbacks(execute=True):
EventCategory.objects.create(name="Konsert", slug="konsert")
assert webhook_calls == [(WEBHOOK_URL, "s3cret")]
def test_unset_secret_skips_and_warns_once(settings, monkeypatch, caplog):
settings.REVALIDATE_WEBHOOK_SECRET = ""
monkeypatch.setattr(revalidation, "_warned_unconfigured", False)
with caplog.at_level(logging.WARNING, logger="dnscms.revalidation"):
revalidation.trigger_frontend_revalidation("test")
revalidation.trigger_frontend_revalidation("test")
warnings = [r for r in caplog.records if r.levelno == logging.WARNING]
assert len(warnings) == 1
def test_send_webhook_failure_is_swallowed(monkeypatch, caplog):
def raise_urlerror(request, timeout):
raise urllib.error.URLError("connection refused")
monkeypatch.setattr(revalidation.urllib.request, "urlopen", raise_urlerror)
with caplog.at_level(logging.ERROR, logger="dnscms.revalidation"):
revalidation._send_webhook(WEBHOOK_URL, "s3cret", "test")
assert any("frontend revalidation failed" in r.message for r in caplog.records)
def test_send_webhook_request_shape(monkeypatch):
seen = {}
class FakeResponse:
status = 200
def __enter__(self):
return self
def __exit__(self, *exc):
return False
def fake_urlopen(request, timeout):
seen["request"] = request
seen["timeout"] = timeout
return FakeResponse()
monkeypatch.setattr(revalidation.urllib.request, "urlopen", fake_urlopen)
revalidation._send_webhook(WEBHOOK_URL, "s3cret", "test")
request = seen["request"]
assert request.get_method() == "POST"
assert request.get_header("X-revalidate-secret") == "s3cret"
assert seen["timeout"] == revalidation.WEBHOOK_TIMEOUT_SECONDS
+203 -180
View File
@@ -13,53 +13,68 @@ wheels = [
[[package]] [[package]]
name = "asgiref" name = "asgiref"
version = "3.9.1" version = "3.12.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/90/61/0aa957eec22ff70b830b22ff91f825e70e1ef732c06666a805730f28b36b/asgiref-3.9.1.tar.gz", hash = "sha256:a5ab6582236218e5ef1648f242fd9f10626cfd4de8dc377db215d5d5098e3142", size = 36870, upload-time = "2025-07-08T09:07:43.344Z" } sdist = { url = "https://files.pythonhosted.org/packages/e6/26/3b59f2bdae5f640389becb1f673cded775287f5fc4f816309d9ca9a3f93d/asgiref-3.12.1.tar.gz", hash = "sha256:59dcb51c272ad209d59bed5708a64a333083e86017d7fcdd67498eeab7784340", size = 42378, upload-time = "2026-07-14T09:56:18.087Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/7c/3c/0464dcada90d5da0e71018c04a140ad6349558afb30b3051b4264cc5b965/asgiref-3.9.1-py3-none-any.whl", hash = "sha256:f3bba7092a48005b5f5bacd747d36ee4a5a61f4a269a6df590b43144355ebd2c", size = 23790, upload-time = "2025-07-08T09:07:41.548Z" }, { url = "https://files.pythonhosted.org/packages/c0/1b/54f4ad77cd8a584fa70746c47df988e002cf1ee1eba43364d46f87803647/asgiref-3.12.1-py3-none-any.whl", hash = "sha256:fe386d1c2bff7259ea95929266d12a8cf9a8b5a1c2598402967d8792e7a7c094", size = 25478, upload-time = "2026-07-14T09:56:16.926Z" },
] ]
[[package]] [[package]]
name = "beautifulsoup4" name = "beautifulsoup4"
version = "4.13.4" version = "4.15.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "soupsieve" }, { name = "soupsieve" },
{ name = "typing-extensions" }, { name = "typing-extensions" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/d8/e4/0c4c39e18fd76d6a628d4dd8da40543d136ce2d1752bd6eeeab0791f4d6b/beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195", size = 621067, upload-time = "2025-04-15T17:05:13.836Z" } sdist = { url = "https://files.pythonhosted.org/packages/43/65/318323f98dbee45d42dff61d8f047181bc6f2268a9068cfad035a46be5af/beautifulsoup4-4.15.0.tar.gz", hash = "sha256:288e3ca7d54b06f2ac191970bc275c1939cb46d450b255bf6718b04aa37ab4f7", size = 632571, upload-time = "2026-06-07T16:44:20.453Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/50/cd/30110dc0ffcf3b131156077b90e9f60ed75711223f306da4db08eff8403b/beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b", size = 187285, upload-time = "2025-04-15T17:05:12.221Z" }, { url = "https://files.pythonhosted.org/packages/88/c6/92fcd42f1ba33e1184263f25bfabf3d27c383410470f169e4b8163bf9c17/beautifulsoup4-4.15.0-py3-none-any.whl", hash = "sha256:d6f88de62e1d4e38ecb1077eb9724cd0eff29d2a08ca16a401e9b9e93f117cf9", size = 109924, upload-time = "2026-06-07T16:44:21.566Z" },
] ]
[[package]] [[package]]
name = "certifi" name = "certifi"
version = "2025.8.3" version = "2026.7.22"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" },
] ]
[[package]] [[package]]
name = "charset-normalizer" name = "charset-normalizer"
version = "3.4.3" version = "3.4.9"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" } sdist = { url = "https://files.pythonhosted.org/packages/bd/2a/23f34ec9d04624958e137efdc394888716353190e75f25dd22c7a2c7a8aa/charset_normalizer-3.4.9.tar.gz", hash = "sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b", size = 152439, upload-time = "2026-07-07T14:34:58.454Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342, upload-time = "2025-08-09T07:56:38.687Z" }, { url = "https://files.pythonhosted.org/packages/7e/8d/496817fa0944239ecae662dd57ea765cfeaec6a735f9f025d4b7b72e7143/charset_normalizer-3.4.9-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0327fcd59a935777d83410750c50600ee9571af2846f71ce40f25b13da1ef380", size = 317253, upload-time = "2026-07-07T14:33:54.994Z" },
{ url = "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995, upload-time = "2025-08-09T07:56:40.048Z" }, { url = "https://files.pythonhosted.org/packages/2b/f9/ef4a69ea338ad3c0deceea0f5f7d2380ae8b52132b06d652cb0d2cd86706/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a79d9f4d8001473a30c163556b3c3bfebec837495a412dde78b51672f6134f9", size = 215898, upload-time = "2026-07-07T14:33:56.334Z" },
{ url = "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640, upload-time = "2025-08-09T07:56:41.311Z" }, { url = "https://files.pythonhosted.org/packages/8c/e7/5ddfd76fc061eb52de219658a4aa431cbacadf0a0219c8854f00da50d289/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33bdcc2a32c0a0e861f60841a512c8acc658c87c2ac59d89e3a46dacf7d866e4", size = 236718, upload-time = "2026-07-07T14:33:57.9Z" },
{ url = "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636, upload-time = "2025-08-09T07:56:43.195Z" }, { url = "https://files.pythonhosted.org/packages/49/ba/768fa3f36048d81c477a0ce61f813bc1454d80917ccfe550abd9f44f5e24/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f840ed6d8ecba8255df8c42b87fadeda98ddfc6eeec05e2dc66e26d46dd6f58a", size = 232519, upload-time = "2026-07-07T14:33:59.811Z" },
{ url = "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939, upload-time = "2025-08-09T07:56:44.819Z" }, { url = "https://files.pythonhosted.org/packages/f4/c4/b3e049d2aa3766180c78507110543d9d50894cc97f57de543f1be521dcdc/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c25fe15c70c59eb7c5ce8c06a1f3fa1da0ecc5ea1e7a5922c40fd2fa9b0d5046", size = 223143, upload-time = "2026-07-07T14:34:01.517Z" },
{ url = "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580, upload-time = "2025-08-09T07:56:46.684Z" }, { url = "https://files.pythonhosted.org/packages/19/79/55c32d06d76ae4feafe053f061f3e3ab70bcf19f4007797ce8c3efda7830/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:f7fb7d750cfa0a070d2c24e831fd3481019a60dd317ea2b39acbcebc08b6ed81", size = 206742, upload-time = "2026-07-07T14:34:03.04Z" },
{ url = "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870, upload-time = "2025-08-09T07:56:47.941Z" }, { url = "https://files.pythonhosted.org/packages/10/e0/47c079dd82d217c807479cd59ffd30af56307ea31c108b75758970459ad3/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d1c96a7a18b9690a4d46df09e3e3382406ae3213727cd1019ebade1c4a81917", size = 219191, upload-time = "2026-07-07T14:34:04.657Z" },
{ url = "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797, upload-time = "2025-08-09T07:56:49.756Z" }, { url = "https://files.pythonhosted.org/packages/42/ab/b9bc2e77d6b44a7e46ef62ec5cac1c9a6ba7b9135a5d560f002696ec9995/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a4cfde78a9f2880208d16a93b795726a3017d5977e08d1e162a7a31322479c41", size = 218328, upload-time = "2026-07-07T14:34:06.115Z" },
{ url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224, upload-time = "2025-08-09T07:56:51.369Z" }, { url = "https://files.pythonhosted.org/packages/f1/78/c9c71d599f5aa2d42bcdd35cbbd46d7f535351a57e40ff7d8e5a7e219401/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4d6fcde76f94f5cb9e43e9e9a61f16dacefd228cbbf6f1a09bd9b219a92f1a1", size = 207406, upload-time = "2026-07-07T14:34:07.554Z" },
{ url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086, upload-time = "2025-08-09T07:56:52.722Z" }, { url = "https://files.pythonhosted.org/packages/f6/39/c914445c321a845097ce4f6ac7de9a18228a77b766272125a1ce00d851eb/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:898f0e9068ca27d37f8e83a5b962821df851532e6c4a7d615c1c033f9da6eedf", size = 225157, upload-time = "2026-07-07T14:34:09.061Z" },
{ url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400, upload-time = "2025-08-09T07:56:55.172Z" }, { url = "https://files.pythonhosted.org/packages/9b/f2/c0d4b8508565a36bc5c624e88ed297f5b0b1095011034d7f5b83a69908b5/charset_normalizer-3.4.9-cp314-cp314-win32.whl", hash = "sha256:c1c948747b03be832dceed96ca815cef7360de9aa19d37c730f8e3f6101aca48", size = 151095, upload-time = "2026-07-07T14:34:10.901Z" },
{ url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" }, { url = "https://files.pythonhosted.org/packages/49/fd/a1d26144398c67486422a72bf5812cda22cb4ccfcd95a290fb41ceb4b8e2/charset_normalizer-3.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:16b65ea0f2465b6fb52aa22de5eca612aa964ddfec00a912e26f4656cbef890b", size = 162796, upload-time = "2026-07-07T14:34:12.47Z" },
{ url = "https://files.pythonhosted.org/packages/20/95/d75e82f8ce9fd323ebf059c16c9aadefb22a1ecde13b7840b35835e4886c/charset_normalizer-3.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:40a126142a56b2dfc0aacbad1de8310cbf60da7656db0e6b16eebd48e3e93519", size = 153334, upload-time = "2026-07-07T14:34:14.044Z" },
{ url = "https://files.pythonhosted.org/packages/00/5e/17398df3a139985ba9d11ed072531986f408c8fca952835ef1ab1820c02b/charset_normalizer-3.4.9-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:609b3ba8fcc0fb5ab7af00719d0fb6ad0cb518e48e7712d12fd68f1327951198", size = 338848, upload-time = "2026-07-07T14:34:15.688Z" },
{ url = "https://files.pythonhosted.org/packages/cd/91/7253a32e86b7e1d1239b1b36ba6dd0f021a21107ab33054b53119cc083b9/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51447e9aa2684679af07ca5021c3db526e0284347ebf4ffcec1154c3350cfe32", size = 223022, upload-time = "2026-07-07T14:34:17.248Z" },
{ url = "https://files.pythonhosted.org/packages/cb/32/2e64bd2be10e89c61e57ebe6a93fd98ae88eb7ebe414b5121f22c96c69eb/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cc1b0fff8ead343dae06305f954eb8468ba0ec1a97881f42489d198e4ce3c632", size = 241590, upload-time = "2026-07-07T14:34:18.813Z" },
{ url = "https://files.pythonhosted.org/packages/3d/ef/d96ec496cfea0c21db43b0ad03891308b02388d054cc902cf0e5a1ad6a88/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa36ec09ef71d158186bc79e359ff5fdd6e7996fe8ab638f00d6b93139ba4fcf", size = 239584, upload-time = "2026-07-07T14:34:20.52Z" },
{ url = "https://files.pythonhosted.org/packages/d4/ce/9af95f7876194bd7a14e3dfe4a4de2e0bff02666a3910d72beafd06cc297/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df115d4d83168fdf2cae48ef1ff6d1cb4c466364e30861b37121de0f3bf1b990", size = 230224, upload-time = "2026-07-07T14:34:22.189Z" },
{ url = "https://files.pythonhosted.org/packages/52/94/af74dde74a3996bd959c350709bfe50e297823d70a8c1cbd54b838880863/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f86c6358749bd4fda175388691e3ba8c46e24c5347d0afd20f9b7edfc9faf07d", size = 212667, upload-time = "2026-07-07T14:34:23.857Z" },
{ url = "https://files.pythonhosted.org/packages/ee/f0/f1c4fe746c395922961b5916ed1d7d6e7d4c84851d19ed43cc89980ec953/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:32286a2c8d167e897177b673176c1e3e00d4057caf5d2b64eef9a3666b03018e", size = 227179, upload-time = "2026-07-07T14:34:25.586Z" },
{ url = "https://files.pythonhosted.org/packages/e4/56/6c745619ac397e8871e2bcd3cea1eec86b877488f33888b3aef5c3ed506e/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:83aed2c10721ddd90f68140685391b50811a880af20654c59af6b6c66c40513c", size = 225372, upload-time = "2026-07-07T14:34:27.212Z" },
{ url = "https://files.pythonhosted.org/packages/78/ad/98aae8630ac71f16711968e38a5acfecce41b778bf2f0312851020f565a8/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cd6c3d4b783c556fa00bf540854e42f135e2f256abd29669fcd0da0f2dec79c2", size = 215222, upload-time = "2026-07-07T14:34:28.774Z" },
{ url = "https://files.pythonhosted.org/packages/f7/40/9593d54209765207a7f11073c06494c1721e4ca4a0a426c597679bf7f91e/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ee2f2a527e3c1a6e6411eb4209642e138b544a2d72fe5d0d76daf77b24063534", size = 231958, upload-time = "2026-07-07T14:34:30.345Z" },
{ url = "https://files.pythonhosted.org/packages/b1/27/693ee5e8a18191eb38647360c51cd505013e2bd3b366aa43fd5344c21e3c/charset_normalizer-3.4.9-cp314-cp314t-win32.whl", hash = "sha256:0d861473f743244d349b50f850d10eb87aeb22bbdcc8e64f79273c94af5a8226", size = 155580, upload-time = "2026-07-07T14:34:31.884Z" },
{ url = "https://files.pythonhosted.org/packages/80/3f/bd97d3d9c613013d07cb7733d299385b41df37f0471310f5a73dc359f0b8/charset_normalizer-3.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:9b8e0f3107e2200b76f6054de99016eac3ee6762713587b36baaa7e4bd2ae177", size = 167620, upload-time = "2026-07-07T14:34:33.438Z" },
{ url = "https://files.pythonhosted.org/packages/3d/c6/eee9dca4439b1061f76373f06ea855678cc4a64c1c3c90b50e479edbb8eb/charset_normalizer-3.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:19ac87f93086ce37b86e098888555c4b4bc48102279bae3350098c0ed664b501", size = 158037, upload-time = "2026-07-07T14:34:35.018Z" },
{ url = "https://files.pythonhosted.org/packages/98/2b/f97f1c193fb855c345d678f5077d6926034db0722df74c8f057020e05a25/charset_normalizer-3.4.9-py3-none-any.whl", hash = "sha256:68e5f26a1ad57ded6d1cfb85331d1c1a195314756471d97758c48498bb4dcdf5", size = 64538, upload-time = "2026-07-07T14:34:56.993Z" },
] ]
[[package]] [[package]]
@@ -73,41 +88,41 @@ wheels = [
[[package]] [[package]]
name = "coverage" name = "coverage"
version = "7.14.0" version = "7.15.3"
source = { registry = "https://pypi.org/simple" } 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" } sdist = { url = "https://files.pythonhosted.org/packages/f4/45/78dbf9604ee5b3db24efbf26bed1cb58862fb40480cba821963c69348751/coverage-7.15.3.tar.gz", hash = "sha256:ae7ea5a4614acf399ef0483c4cb34f8f8f01df848d8fcbe7d3ce0865733f1c4d", size = 935592, upload-time = "2026-08-02T18:50:17.006Z" }
wheels = [ 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/35/6f/8c2dc014357618b3226c90f731b8282766c3685786f422558991dc49fbf2/coverage-7.15.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1e3bb08ad574bd9fb6a991f645728f70d333c1c1958dd5fcde65e24cb862813d", size = 222571, upload-time = "2026-08-02T18:49:11.242Z" },
{ 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/07/50/d867c7ceae9d56b7e74ee61ea834f1aa4f9a1e1c7f0ce39393ba573b1c12/coverage-7.15.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9e5860eaff02a0b7f1b73304bdf846596ee62ab3a78d25c68044ebf684cb1fef", size = 222902, upload-time = "2026-08-02T18:49:13.448Z" },
{ 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/62/77/4f6dfc490c5f2bcacb2d296d9aa4d1e128c43b48e94ad313fec7f49f09ad/coverage-7.15.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:60874e5bd67f0b1bdbe42ab42c7bafa66a6fb8de88721af6df3f7a02713960cd", size = 253947, upload-time = "2026-08-02T18:49:15.304Z" },
{ 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/16/8a/6777f192af264165103e2a3d3768dbadb9894a0a2359a16877141d9ae8f5/coverage-7.15.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f9147be876e9d83765e0b82176674dc248a6b9283e25e01e7462611b97e9b731", size = 256452, upload-time = "2026-08-02T18:49:17.801Z" },
{ 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/7d/7b/3d7ac46a0234bc684f41ee42be95e29b2b6525695adb04083609d5ac2149/coverage-7.15.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:61a01f8c3804760fcc5a3d31c4f3cab792d660d44e17bf7adeaf0ea51e07821e", size = 257798, upload-time = "2026-08-02T18:49:19.878Z" },
{ 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/ff/1e/c6ee59c29afcb5fdb35f936381340d1a06429a07c48f20e809646647acbe/coverage-7.15.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:95bf3e7f26f792e25eb185f85a5a659d48479265176dcfe22b6f334fd0081b5c", size = 260112, upload-time = "2026-08-02T18:49:21.858Z" },
{ 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/c1/e1/e8ea39a46e89e3a143312ee5f80336e992e3ae8fe44bf9c76b83fefeed42/coverage-7.15.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:44c41eff9e413fed8740eca75d5438ebeb9d3e45e7cd37c67329213e7a72c764", size = 253944, upload-time = "2026-08-02T18:49:23.926Z" },
{ 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/95/67/31ab5f6a37fd887d1386f81f0da9306851ad2264e9baaa9c7f606e0b3e17/coverage-7.15.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:54146bafb61f3ba9895b43af0dd17eba01561d586d44ce84ea221b0cbbee5a9e", size = 255805, upload-time = "2026-08-02T18:49:25.973Z" },
{ 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/fb/6a/ee505a80c8fd89620fb337c0596daecff87f33171fbb4ee3015fc3d7331f/coverage-7.15.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:af000dd1bb859ff8066fda4c79512ff938c798116540307226b373099c7b151f", size = 253769, upload-time = "2026-08-02T18:49:27.883Z" },
{ 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/b0/41/6ab0f81c9e89660230d8f3f581d4732e5ddb75a885b0a5dfc73d315dc94f/coverage-7.15.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:a1b82490577f3889950b5a04f18712aef0207243e0749d60fe28c3c73ebfd5fd", size = 258045, upload-time = "2026-08-02T18:49:30.201Z" },
{ 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/bc/62/c995e91cae28cf31d6defab3bfb553dda5ac83ac7381b0f2b121264c307a/coverage-7.15.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:c4fc90a60154c3e4b8a2dc206d6dbe852f1c235c249e0dc0cef909d032c9591a", size = 253587, upload-time = "2026-08-02T18:49:32.349Z" },
{ 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/84/df/f2049980f82d6890321f2065f9e66216eabbf4b2001815db958bc543f40a/coverage-7.15.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f25bb884814a892948b4c20394db3f2364dd452d9492736479e7a493e63b0eb6", size = 255243, upload-time = "2026-08-02T18:49:34.324Z" },
{ 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/1d/82/2c841b67a978c0eb9c3707630b68f93f9e7585d78bb906bc8823ec6b07a5/coverage-7.15.3-cp314-cp314-win32.whl", hash = "sha256:722dbf8e7828fbcfe0dc8586167dc0a5ce85ad6ea171dbb21ed3f8d6581d3cb8", size = 224759, upload-time = "2026-08-02T18:49:36.326Z" },
{ 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/b3/78/5c93ec43784fd3e404ca23cd0584ae24bc1732de4a3fc194b68c3be88db0/coverage-7.15.3-cp314-cp314-win_amd64.whl", hash = "sha256:64d0845f9c3ed47302bed265c15ab4dbb64aa4ec1490839b8e328f4e7fa914d2", size = 225246, upload-time = "2026-08-02T18:49:38.366Z" },
{ 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/9d/77/813a054371f3b018cc63c6bdb46a3c35d5e95d4e3ed4f1449d4196106db5/coverage-7.15.3-cp314-cp314-win_arm64.whl", hash = "sha256:69bc14684f8fbbee9f9dbaa4fe79719b0da9725fc37956785c06ec365acf6926", size = 224673, upload-time = "2026-08-02T18:49:40.552Z" },
{ 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/8f/63/8c9f36cc71178d26db930baa03a4494abcc516d8d41bf820d0d85ef1d80b/coverage-7.15.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f92df943c24b96cb215ca26b4f6a2283e63c5db80f1635aceea7fff11311917b", size = 223298, upload-time = "2026-08-02T18:49:42.634Z" },
{ 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/54/66/211f24d058ce9f56ebf1420d55b7574fdae924f6da3836f83c8bd4793e38/coverage-7.15.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:66591c46bdd2971d3ae2bc503a5f0459c2edcaf6b7e045b292000cc95bc6cb95", size = 223568, upload-time = "2026-08-02T18:49:44.706Z" },
{ 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/dd/bb/9c2ad5574a0d6420a96c6cade4f8a683931b9e79fe609f8924d7b6964616/coverage-7.15.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:caa64458b81b18bfc67cdf1f6dc02b23e3edc672f2f8e11771fad75865415a43", size = 264932, upload-time = "2026-08-02T18:49:47.153Z" },
{ 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/ba/91/938c39e77bdd5a0a440412f975609ce3702dabbda6ac715719d93ca45a7b/coverage-7.15.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:447f5421ccf5475956cf516d4ca1d575f487947b6f4e11f9d80c6aefe24b3dc8", size = 267052, upload-time = "2026-08-02T18:49:49.324Z" },
{ 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/b0/a3/7b431a98af35d9cc6394e54cde9435b33b8591672fbece6a4931267d7a8e/coverage-7.15.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a0c77ef8cd483a4987a5d12d1d9d5f7ee598dfdc6c0844417d847e5768dc779", size = 269473, upload-time = "2026-08-02T18:49:51.599Z" },
{ 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/32/58/dbc9951dce46be47a732823a1c571f62bcabdd54a68d8c281489a1a55cfb/coverage-7.15.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0b273f4ff657446a06c2d85bf80e134fa869a92852ba5f87854a70e1fb44da77", size = 270591, upload-time = "2026-08-02T18:49:53.865Z" },
{ 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/71/bd/1d610772c7c0889bfe477a59c46ee66ea53e271f3f06951e9d55b317f7c6/coverage-7.15.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:daea8c4fafa22488600405be2c2be525a9406fba3fc0a83acc726db3e14e2005", size = 264007, upload-time = "2026-08-02T18:49:55.875Z" },
{ 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/69/97/852eb3dcdba156b1a9078503f098499916bf889f964b61ad4a08223ac169/coverage-7.15.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:93ff57c530f3fa7aa69f92fb9b8892b8aa82712aa970842f4abf28657f42fb57", size = 266926, upload-time = "2026-08-02T18:49:57.944Z" },
{ 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/52/f8/b72cd238757fba2b587fc7dee047efe6e10b0c18343509faaaf502dd4680/coverage-7.15.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:4df21bef8b800eebda9018f53d49c9ace3aeb0090c850139b27923aafcb83e91", size = 264529, upload-time = "2026-08-02T18:50:00.035Z" },
{ 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/b8/0a/6c52ec4b7fb007cb6433d1fcfda4080cb15d75ad37ef9c31025f3427293e/coverage-7.15.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:db567b02685f26034adcbd85055f80d12cdf02111b8ed00886093d98b2874ce2", size = 268263, upload-time = "2026-08-02T18:50:02.161Z" },
{ 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/c0/4e/f1f9aa3efd109a04353563a43fb5155340c1fdcdeaa6296ebed3b6f510ea/coverage-7.15.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5318dd51b8600b947e058cf5a4fe54d183d9d13c49b97b64ca7be05a34df9bef", size = 263377, upload-time = "2026-08-02T18:50:04.243Z" },
{ 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/dd/fb/6b268a0b2728ef1c379ad656b899274477a5f6bed1bf6765b4b387fb0601/coverage-7.15.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c995bfa383c54704839b6c4c2627a1c00895597ada0e5e8190c81d8bd620555c", size = 265688, upload-time = "2026-08-02T18:50:06.428Z" },
{ 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/29/54/1a3ea96e5d5e7cd41dc432597bfc60692910e635d05e1cc25a8ccc243581/coverage-7.15.3-cp314-cp314t-win32.whl", hash = "sha256:6433fafb8da0e1d02eb53411e0ecdadb6b88f0224fdc23317e703c0e88937d42", size = 225066, upload-time = "2026-08-02T18:50:08.533Z" },
{ 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/31/9d/a7b0d9afd18ed5274dd00651a78e7810a931c70d94b79996f150bec1a30f/coverage-7.15.3-cp314-cp314t-win_amd64.whl", hash = "sha256:fe578952b1b29fe8c777f43f241d49efac4b56724a3434f5d22ebe3c208df429", size = 225897, upload-time = "2026-08-02T18:50:10.572Z" },
{ 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/ca/11/34c5ae40b945e69aa72b87dc268135b7049905f3824af573b7073acbb946/coverage-7.15.3-cp314-cp314t-win_arm64.whl", hash = "sha256:d2e1acb7aee29dfa8f3e48c23f36670898baca1209d9bdd3985a50c7f982165e", size = 225212, upload-time = "2026-08-02T18:50:12.63Z" },
{ 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" }, { url = "https://files.pythonhosted.org/packages/37/e7/7069b3d6c018917f49ba2e1c5fb910e498c7fefa3a1b78cb1b79e61ff45d/coverage-7.15.3-py3-none-any.whl", hash = "sha256:da78fa6fc7dafe4212839173133ee85afcf42c5cd5f3e47fa7c1c210453b445e", size = 214297, upload-time = "2026-08-02T18:50:14.709Z" },
] ]
[[package]] [[package]]
@@ -121,16 +136,16 @@ wheels = [
[[package]] [[package]]
name = "django" name = "django"
version = "6.0.5" version = "6.0.7"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "asgiref" }, { name = "asgiref" },
{ name = "sqlparse" }, { name = "sqlparse" },
{ name = "tzdata", marker = "sys_platform == 'win32'" }, { name = "tzdata", marker = "sys_platform == 'win32'" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/5e/f1/bf85f0d29ef76abf901f193fe8fef4769d3da7794197832bc30151c071d8/django-6.0.5.tar.gz", hash = "sha256:bc6d6872e98a2864c836e42edd644b362db311147dd5aa8d5b82ba7a032f5269", size = 10924131, upload-time = "2026-05-05T13:54:39.329Z" } sdist = { url = "https://files.pythonhosted.org/packages/89/55/664f24ff81c9ea19cb7dfc851afeae1f3c2390c7aee01d4ded68b5c1580d/django-6.0.7.tar.gz", hash = "sha256:2998503fc083124fb58037084bfa00de323c7c743f05f1b4284e77bff0ab8890", size = 10921299, upload-time = "2026-07-07T13:51:26.485Z" }
wheels = [ wheels = [
{ 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" }, { url = "https://files.pythonhosted.org/packages/ba/ec/1ce5334b6a2c52ce619c23a0be8d366a57a0e080ebb2d88266e5c849157c/django-6.0.7-py3-none-any.whl", hash = "sha256:a037427c2288443a8c02a1b02295a31c239663aa682bc50b1976afb7cf6a769e", size = 8373344, upload-time = "2026-07-07T13:51:20.007Z" },
] ]
[[package]] [[package]]
@@ -147,14 +162,14 @@ wheels = [
[[package]] [[package]]
name = "django-filter" name = "django-filter"
version = "25.1" version = "26.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "django" }, { name = "django" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/b5/40/c702a6fe8cccac9bf426b55724ebdf57d10a132bae80a17691d0cf0b9bac/django_filter-25.1.tar.gz", hash = "sha256:1ec9eef48fa8da1c0ac9b411744b16c3f4c31176c867886e4c48da369c407153", size = 143021, upload-time = "2025-02-14T16:30:53.238Z" } sdist = { url = "https://files.pythonhosted.org/packages/cb/3e/563965173d4cbb5fc308087e7b3d11a115b7b67273d093622480b1e31f78/django_filter-26.1.tar.gz", hash = "sha256:66ea04031b068c77c86e1ac26ced7a3f8f13ce797f5795751707e3deefc58054", size = 144299, upload-time = "2026-07-11T09:27:02.767Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/07/a6/70dcd68537c434ba7cb9277d403c5c829caf04f35baf5eb9458be251e382/django_filter-25.1-py3-none-any.whl", hash = "sha256:4fa48677cf5857b9b1347fed23e355ea792464e0fe07244d1fdfb8a806215b80", size = 94114, upload-time = "2025-02-14T16:30:50.435Z" }, { url = "https://files.pythonhosted.org/packages/1f/01/afffed1e3c4540fb75bf550a18b6176a9f6371b5f3e52b69a28995b6480c/django_filter-26.1-py3-none-any.whl", hash = "sha256:7d98ef2899218e6242619b532cb1b95af14e09dfcf74844aecb550ad27b59ff2", size = 94069, upload-time = "2026-07-11T09:27:01.012Z" },
] ]
[[package]] [[package]]
@@ -183,15 +198,15 @@ wheels = [
[[package]] [[package]]
name = "django-stubs-ext" name = "django-stubs-ext"
version = "5.2.2" version = "6.0.7"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "django" }, { name = "django" },
{ name = "typing-extensions" }, { name = "typing-extensions" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/fc/06/5e94715d103e6cc72380cb0d0b6682a7d5ad2c366cee478c94d77aad777d/django_stubs_ext-5.2.2.tar.gz", hash = "sha256:d9d151b919fe2438760f5bd938f03e1cb08c84d0651f9e5917f1313907e42683", size = 6244, upload-time = "2025-07-17T08:34:35.054Z" } sdist = { url = "https://files.pythonhosted.org/packages/36/50/917f7224ea470e89cdcdc93d3dfe75b8391adf976cf12f2ecdb5f5d122be/django_stubs_ext-6.0.7.tar.gz", hash = "sha256:c3172c5126614fd2a44d0196b313b44c21f717cb09477ba52b447d41f4ce613e", size = 6665, upload-time = "2026-07-14T10:07:56.933Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/4e/38/2903676f97f7902ee31984a06756b0e8836e897f4b617e1a03be4a43eb4f/django_stubs_ext-5.2.2-py3-none-any.whl", hash = "sha256:8833bbe32405a2a0ce168d3f75a87168f61bd16939caf0e8bf173bccbd8a44c5", size = 8816, upload-time = "2025-07-17T08:34:33.715Z" }, { url = "https://files.pythonhosted.org/packages/83/65/4d73fce956b5ebf26449259664360e539fbe95f0e86be396c28b636ba72a/django_stubs_ext-6.0.7-py3-none-any.whl", hash = "sha256:53a9c7c5a7c7e718cc6308cfce1e7470f2cac0b9d38dbcd60fbfa82704f1d592", size = 10362, upload-time = "2026-07-14T10:07:55.653Z" },
] ]
[[package]] [[package]]
@@ -208,40 +223,40 @@ wheels = [
[[package]] [[package]]
name = "django-tasks" name = "django-tasks"
version = "0.9.0" version = "0.12.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "django" }, { name = "django" },
{ name = "django-stubs-ext" }, { name = "django-stubs-ext" },
{ name = "typing-extensions" }, { name = "typing-extensions" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/60/b1/064645bf246a1f5b46d9638755b1869ea44a6d05e7c7c12841fddebb71f6/django_tasks-0.9.0.tar.gz", hash = "sha256:971b3829efeee68147f7deced8d21b907131b11ec7953af83eb94b11f128a24d", size = 32343, upload-time = "2025-10-17T16:21:08.58Z" } sdist = { url = "https://files.pythonhosted.org/packages/1d/6e/34d4e77bb7951e5a5acbd846f43240f062c68bb04d9c246cb446a55d4cbc/django_tasks-0.12.0.tar.gz", hash = "sha256:58be66c1e487da32a3ce7320bd1949d0d1dc381b9004819f92591eb37fb2c1b8", size = 15445, upload-time = "2026-02-06T16:15:33.593Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/71/e1/ce539ce1e21be71696649f84b6dbd508b2c0b89b559a6e620478bb126f7c/django_tasks-0.9.0-py3-none-any.whl", hash = "sha256:fddc344934a605d9eafa08ac8ba32c0cde9da23ef534e03a41f09fa0417b535a", size = 44057, upload-time = "2025-10-17T16:21:07.283Z" }, { url = "https://files.pythonhosted.org/packages/07/e7/40c45768e84efd77e3ae6ffdcd2b5540011036cbb00c6df6ef2a2ad69551/django_tasks-0.12.0-py3-none-any.whl", hash = "sha256:ffcc1d7bdfad3bc5ef9c2d498596c6546484c9ec6b182fb9709c691eb66a8709", size = 15758, upload-time = "2026-02-06T16:15:32.168Z" },
] ]
[[package]] [[package]]
name = "django-treebeard" name = "django-treebeard"
version = "5.1.0" version = "5.3.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "django" }, { name = "django" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/f7/16/aa732ea1033586e4f946d8e47be9116ffb22b050485b179a7cafb82dfc34/django_treebeard-5.1.0.tar.gz", hash = "sha256:8ac2ba41307469a679c98188124933bc3baade36b02480343d1a301a1fca0700", size = 302339, upload-time = "2026-05-12T02:06:37.002Z" } sdist = { url = "https://files.pythonhosted.org/packages/cc/b4/d63da0e36e42fa0cf355d5692ee70066922ff9e8f54b30f69605d43cc9c5/django_treebeard-5.3.0.tar.gz", hash = "sha256:8606cefd3a65689e99d2a493e5af8a8ece450b96915c949b5c72f6f302cfbe19", size = 312630, upload-time = "2026-06-24T05:34:50.545Z" }
wheels = [ wheels = [
{ 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" }, { url = "https://files.pythonhosted.org/packages/50/5a/7b9fa8374e3da778109d1135c6d0131cc3ad27ba3d5436ef3a3ae3f55e7d/django_treebeard-5.3.0-py3-none-any.whl", hash = "sha256:ae9e570fa74b4c882bc08def1f0c97d27f163b6285a19f3383027d074882e9f4", size = 82023, upload-time = "2026-06-24T05:34:49.133Z" },
] ]
[[package]] [[package]]
name = "djangorestframework" name = "djangorestframework"
version = "3.16.1" version = "3.17.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "django" }, { name = "django" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/8a/95/5376fe618646fde6899b3cdc85fd959716bb67542e273a76a80d9f326f27/djangorestframework-3.16.1.tar.gz", hash = "sha256:166809528b1aced0a17dc66c24492af18049f2c9420dbd0be29422029cfc3ff7", size = 1089735, upload-time = "2025-08-06T17:50:53.251Z" } sdist = { url = "https://files.pythonhosted.org/packages/ca/d7/c016e69fac19ff8afdc89db9d31d9ae43ae031e4d1993b20aca179b8301a/djangorestframework-3.17.1.tar.gz", hash = "sha256:a6def5f447fe78ff853bff1d47a3c59bf38f5434b031780b351b0c73a62db1a5", size = 905742, upload-time = "2026-03-24T16:58:33.705Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/b0/ce/bf8b9d3f415be4ac5588545b5fcdbbb841977db1c1d923f7568eeabe1689/djangorestframework-3.16.1-py3-none-any.whl", hash = "sha256:33a59f47fb9c85ede792cbf88bde71893bcda0667bc573f784649521f1102cec", size = 1080442, upload-time = "2025-08-06T17:50:50.667Z" }, { url = "https://files.pythonhosted.org/packages/5a/e1/2c516bdc83652b1a60c6119366ac2c0607b479ed05cd6093f916ca8928f8/djangorestframework-3.17.1-py3-none-any.whl", hash = "sha256:c3c74dd3e83a5a3efc37b3c18d92bd6f86a6791c7b7d4dff62bb068500e76457", size = 898844, upload-time = "2026-03-24T16:58:31.845Z" },
] ]
[[package]] [[package]]
@@ -270,32 +285,32 @@ dev = [
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
{ name = "django", specifier = ">=6.0.5,<7" }, { name = "django", specifier = ">=6.0.6,<7" },
{ name = "django-extensions", specifier = ">=4.1,<5" }, { name = "django-extensions", specifier = ">=4.1,<5" },
{ name = "gunicorn", specifier = ">=26.0.0,<27" }, { name = "gunicorn", specifier = ">=26.0.0,<27" },
{ name = "psycopg2-binary", specifier = ">=2.9.12,<3" }, { name = "psycopg2-binary", specifier = ">=2.9.12,<3" },
{ name = "wagtail", specifier = ">=7.4.1,<8" }, { name = "wagtail", specifier = ">=7.4.2,<8" },
{ name = "wagtail-grapple", specifier = ">=0.31.0,<0.32" }, { name = "wagtail-grapple", specifier = ">=0.31.0,<0.32" },
{ name = "wagtail-headless-preview", specifier = ">=0.8,<0.9" }, { name = "wagtail-headless-preview", specifier = ">=0.9,<0.10" },
{ name = "whitenoise", specifier = ">=6.12.0,<7" }, { name = "whitenoise", specifier = ">=6.12.0,<7" },
] ]
[package.metadata.requires-dev] [package.metadata.requires-dev]
dev = [ dev = [
{ name = "pytest", specifier = ">=9.0.3,<10" }, { name = "pytest", specifier = ">=9.1.1,<10" },
{ name = "pytest-cov", specifier = ">=7.0.0,<8" }, { name = "pytest-cov", specifier = ">=7.0.0,<8" },
{ name = "pytest-django", specifier = ">=4.12.0,<5" }, { name = "pytest-django", specifier = ">=4.12.0,<5" },
{ name = "ruff", specifier = ">=0.15.13,<0.16" }, { name = "ruff", specifier = ">=0.16.1,<0.17" },
{ name = "wagtail-factories", specifier = ">=4.4.0,<5" }, { name = "wagtail-factories", specifier = ">=4.5.0,<5" },
] ]
[[package]] [[package]]
name = "draftjs-exporter" name = "draftjs-exporter"
version = "5.1.0" version = "5.2.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d1/52/8b98525ab5477410bdbaf279c5fe0a99108211d40818bb769460784c1c41/draftjs_exporter-5.1.0.tar.gz", hash = "sha256:9f44b8dcecb702540e3aab24af2fad8683aec910fe0034c12cfab5d716ac5f84", size = 33500, upload-time = "2025-02-21T17:32:57.175Z" } sdist = { url = "https://files.pythonhosted.org/packages/4f/61/8b7fab482ef246f931b57f55f370e68377a53dcf5259f3fcf01e4889d4af/draftjs_exporter-5.2.0.tar.gz", hash = "sha256:4d665f8c75fd173d2c99326405300e8defcf4961b9b2f16ff117486489c6760b", size = 19674, upload-time = "2026-01-02T14:19:38.17Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/dc/99/26d5524aaa3e89266e0af19332053aa9f8a61b1c39b29c0dc709f43fbb29/draftjs_exporter-5.1.0-py3-none-any.whl", hash = "sha256:c32932b7933b994fd5ea74c1decf47b5c41e13ba06363f5b69b76ef10137d4e9", size = 26302, upload-time = "2025-02-21T17:32:54.4Z" }, { url = "https://files.pythonhosted.org/packages/53/e1/2f81aa30ba22ceabb6779ed5dbd6b27fbf6f28deabad91140d1a32f3da5b/draftjs_exporter-5.2.0-py3-none-any.whl", hash = "sha256:f5255510a9c1de60807c1ba4be9666fb44ba263841b63329c8ce70d66146764c", size = 26251, upload-time = "2026-01-02T14:19:36.843Z" },
] ]
[[package]] [[package]]
@@ -321,14 +336,14 @@ wheels = [
[[package]] [[package]]
name = "faker" name = "faker"
version = "40.18.0" version = "40.36.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "tzdata", marker = "sys_platform == 'win32'" }, { 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" } sdist = { url = "https://files.pythonhosted.org/packages/98/d2/026af1e002bbc6df534d1f8262b18ec79a974f928e9290bfbfdfe7c7b2af/faker-40.36.0.tar.gz", hash = "sha256:754048c76c03afa7de83eee8f4bcee3cf668cbb7d995f54a4e9678db7f110308", size = 2025903, upload-time = "2026-07-24T21:11:33.088Z" }
wheels = [ 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" }, { url = "https://files.pythonhosted.org/packages/50/9a/b947ed175ce9a0dcb070ccf3607f0ce8720cfb5ed1a36166a150b2acd5af/faker-40.36.0-py3-none-any.whl", hash = "sha256:82b9497d9cfe017048075bcf969298a74b1b6e39f5e4dad1211085d1133f7b62", size = 2062829, upload-time = "2026-07-24T21:11:31.37Z" },
] ]
[[package]] [[package]]
@@ -374,11 +389,11 @@ wheels = [
[[package]] [[package]]
name = "graphql-core" name = "graphql-core"
version = "3.2.6" version = "3.2.11"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/c4/16/7574029da84834349b60ed71614d66ca3afe46e9bf9c7b9562102acb7d4f/graphql_core-3.2.6.tar.gz", hash = "sha256:c08eec22f9e40f0bd61d805907e3b3b1b9a320bc606e23dc145eebca07c8fbab", size = 505353, upload-time = "2025-01-26T16:36:27.374Z" } sdist = { url = "https://files.pythonhosted.org/packages/4d/90/f2aff026ab4aebd80eb71905106a0885f4cfde85dcf965543f45bed0d9ee/graphql_core-3.2.11.tar.gz", hash = "sha256:e7e156d10beb127cab5c89ff0da71416fc73d27c484a4757d3b2d35633774802", size = 528407, upload-time = "2026-06-05T13:45:22.915Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/ae/4f/7297663840621022bc73c22d7d9d80dbc78b4db6297f764b545cd5dd462d/graphql_core-3.2.6-py3-none-any.whl", hash = "sha256:78b016718c161a6fb20a7d97bbf107f331cd1afe53e45566c59f776ed7f0b45f", size = 203416, upload-time = "2025-01-26T16:36:24.868Z" }, { url = "https://files.pythonhosted.org/packages/00/15/b92b4e1d88d02c6eff9733c9eea21846ab435cc4d813d84ccc5d335955df/graphql_core-3.2.11-py3-none-any.whl", hash = "sha256:0b3e35ff41e9adba53021ab0cef475eb18f57c7f53f0f2ca55567fbf3c537ea0", size = 214879, upload-time = "2026-06-05T13:45:21.245Z" },
] ]
[[package]] [[package]]
@@ -407,11 +422,11 @@ wheels = [
[[package]] [[package]]
name = "idna" name = "idna"
version = "3.10" version = "3.18"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" },
] ]
[[package]] [[package]]
@@ -462,59 +477,67 @@ wheels = [
[[package]] [[package]]
name = "packaging" name = "packaging"
version = "24.1" version = "26.2"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/51/65/50db4dda066951078f0a96cf12f4b9ada6e4b811516bf0262c0f4f7064d4/packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", size = 148788, upload-time = "2024-06-09T23:19:24.956Z" } sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/08/aa/cc0199a5f0ad350994d660967a8efb233fe0416e4639146c089643407ce6/packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124", size = 53985, upload-time = "2024-06-09T23:19:21.909Z" }, { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" },
] ]
[[package]] [[package]]
name = "pillow" name = "pillow"
version = "11.3.0" version = "12.3.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069, upload-time = "2025-07-01T09:16:30.666Z" } sdist = { url = "https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce", size = 47025035, upload-time = "2026-07-01T11:56:38.965Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/73/f4/04905af42837292ed86cb1b1dabe03dce1edc008ef14c473c5c7e1443c5d/pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12", size = 5278520, upload-time = "2025-07-01T09:15:17.429Z" }, { url = "https://files.pythonhosted.org/packages/dc/01/001f65b68192f0228cc1dbbc8d2530ab5d58b61037ba0587f946fea607cd/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9cf95fe4d0f84c82d282745d9bb08ad9f926efa00be4697e767b814ce40d4330", size = 4161736, upload-time = "2026-07-01T11:54:51.156Z" },
{ url = "https://files.pythonhosted.org/packages/41/b0/33d79e377a336247df6348a54e6d2a2b85d644ca202555e3faa0cf811ecc/pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a", size = 4686116, upload-time = "2025-07-01T09:15:19.423Z" }, { url = "https://files.pythonhosted.org/packages/1a/d2/0219746d0fd16fc8a84498e79452375be3797d3ce4044596ce565164b84f/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:8728f216dcdb6e6d555cf971cb34076139ad74b31fc2c14da4fafc741c5f6217", size = 4255435, upload-time = "2026-07-01T11:54:53.414Z" },
{ url = "https://files.pythonhosted.org/packages/49/2d/ed8bc0ab219ae8768f529597d9509d184fe8a6c4741a6864fea334d25f3f/pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632", size = 5864597, upload-time = "2025-07-03T13:10:38.404Z" }, { url = "https://files.pythonhosted.org/packages/c8/02/8d0bc62ef0302318c46ff2a512822d2610e81c7aa46c9b3abe6cbaca5ad0/pillow-12.3.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a45650e8ce7fafffd731db8550230db6b0d306d181a90b67d3e6bca2f1990930", size = 3696262, upload-time = "2026-07-01T11:54:55.739Z" },
{ url = "https://files.pythonhosted.org/packages/b5/3d/b932bb4225c80b58dfadaca9d42d08d0b7064d2d1791b6a237f87f661834/pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673", size = 7638246, upload-time = "2025-07-03T13:10:44.987Z" }, { url = "https://files.pythonhosted.org/packages/85/e2/73c77d218410b14f5f2d565e8a998d5317b7b9c75368d29985139f7a46f0/pillow-12.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ba54cfebe86920a559a7c4d6b9050791c20513650a1952ebe3368c7dc70306f8", size = 5350344, upload-time = "2026-07-01T11:54:57.657Z" },
{ url = "https://files.pythonhosted.org/packages/09/b5/0487044b7c096f1b48f0d7ad416472c02e0e4bf6919541b111efd3cae690/pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027", size = 5973336, upload-time = "2025-07-01T09:15:21.237Z" }, { url = "https://files.pythonhosted.org/packages/c7/da/32c752228ae345f489e3a42499d817b6c3996da7e8a3bc7a04fc806b243b/pillow-12.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e158cb00350dc278f3b91551101aa7d12415a66ebf2c91d8d5ac14e56ddd3ad0", size = 4780131, upload-time = "2026-07-01T11:54:59.713Z" },
{ url = "https://files.pythonhosted.org/packages/a8/2d/524f9318f6cbfcc79fbc004801ea6b607ec3f843977652fdee4857a7568b/pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77", size = 6642699, upload-time = "2025-07-01T09:15:23.186Z" }, { url = "https://files.pythonhosted.org/packages/b1/9d/8b2c807dbef61a5197c047afe99823787eb66f63daf9fb2432f91d6f0462/pillow-12.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9aeb04d6aef139de265b29683e119b638208f88cf73cdd1658aa07221165321", size = 6263757, upload-time = "2026-07-01T11:55:01.778Z" },
{ url = "https://files.pythonhosted.org/packages/6f/d2/a9a4f280c6aefedce1e8f615baaa5474e0701d86dd6f1dede66726462bbd/pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874", size = 6083789, upload-time = "2025-07-01T09:15:25.1Z" }, { url = "https://files.pythonhosted.org/packages/5c/44/c85361f65dbe00eea8576ee467c768d25129989efb76e94f205e9ca9bb46/pillow-12.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:251bf95b67017e27b13d82f5b326234ca62d70f9cf4c2b9032de2358a3b12c7b", size = 6936962, upload-time = "2026-07-01T11:55:03.93Z" },
{ url = "https://files.pythonhosted.org/packages/fe/54/86b0cd9dbb683a9d5e960b66c7379e821a19be4ac5810e2e5a715c09a0c0/pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a", size = 6720386, upload-time = "2025-07-01T09:15:27.378Z" }, { url = "https://files.pythonhosted.org/packages/18/7e/e483414b35800b86b6f08dbbc7803fb5cd52c4d6f897f47d53ea2c7e6f65/pillow-12.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fe3cca2e4e8a592be0f269a1ca4835c25199d9f3ce815c8491048f785b0a0198", size = 6339171, upload-time = "2026-07-01T11:55:05.989Z" },
{ url = "https://files.pythonhosted.org/packages/e7/95/88efcaf384c3588e24259c4203b909cbe3e3c2d887af9e938c2022c9dd48/pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214", size = 6370911, upload-time = "2025-07-01T09:15:29.294Z" }, { url = "https://files.pythonhosted.org/packages/f0/f4/68c491844841ede6bed70189546b3ee9731cf9f2cbad396faff5e1ccba45/pillow-12.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:23aceaa007d6172b02c277f0cd359c79492bbb14f7072b4ede9fbcaf20648130", size = 7048116, upload-time = "2026-07-01T11:55:08.131Z" },
{ url = "https://files.pythonhosted.org/packages/2e/cc/934e5820850ec5eb107e7b1a72dd278140731c669f396110ebc326f2a503/pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635", size = 7117383, upload-time = "2025-07-01T09:15:31.128Z" }, { url = "https://files.pythonhosted.org/packages/a3/34/77f3f793fed8efc7d243f21b33c5a3f0d1c97ee70346d3db855587e155ff/pillow-12.3.0-cp314-cp314-win32.whl", hash = "sha256:af8d94b0db561cf68b88a267c5c44b49e134f525d0dc2cb7ed413a66bc23559a", size = 6467209, upload-time = "2026-07-01T11:55:10.408Z" },
{ url = "https://files.pythonhosted.org/packages/d6/e9/9c0a616a71da2a5d163aa37405e8aced9a906d574b4a214bede134e731bc/pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6", size = 2511385, upload-time = "2025-07-01T09:15:33.328Z" }, { url = "https://files.pythonhosted.org/packages/f1/e0/492879f69d94f91f60fc8cd05ba03650e9520afebb2fb7aa12777d7c7f38/pillow-12.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:fdafc9cce40277e0f7a0feabce0ee50dd2fa1800f3b38015e51296b5e814048d", size = 7237707, upload-time = "2026-07-01T11:55:12.745Z" },
{ url = "https://files.pythonhosted.org/packages/1a/33/c88376898aff369658b225262cd4f2659b13e8178e7534df9e6e1fa289f6/pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae", size = 5281129, upload-time = "2025-07-01T09:15:35.194Z" }, { url = "https://files.pythonhosted.org/packages/c9/ac/6b11f2875f1c2ac040d84e1bbf9cf22a88038f901ca1037898b280b38365/pillow-12.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:e91206ee562682b51b98ef4b26a6ef48fd84e15fd4c4bc5ec768eb641d206838", size = 2565995, upload-time = "2026-07-01T11:55:14.736Z" },
{ url = "https://files.pythonhosted.org/packages/1f/70/d376247fb36f1844b42910911c83a02d5544ebd2a8bad9efcc0f707ea774/pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653", size = 4689580, upload-time = "2025-07-01T09:15:37.114Z" }, { url = "https://files.pythonhosted.org/packages/52/69/c2208e56af9bfc1913afb24020297a691eb1d4ef688474c8a04913f65e04/pillow-12.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:164b31cd1a0490ab6efae01aa5df49da7061be0af1b30e035b6e9a1bfe34ee6e", size = 5352503, upload-time = "2026-07-01T11:55:17.076Z" },
{ url = "https://files.pythonhosted.org/packages/eb/1c/537e930496149fbac69efd2fc4329035bbe2e5475b4165439e3be9cb183b/pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6", size = 5902860, upload-time = "2025-07-03T13:10:50.248Z" }, { url = "https://files.pythonhosted.org/packages/07/70/e5686d753e898a45d778ff1718dba8516ead6ab6b95d85fc8c4b70650cf2/pillow-12.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5afb51d599ea772b8365ae807ae557f18bccfe46ab261fd1c2a9ed700fc6eb17", size = 4782956, upload-time = "2026-07-01T11:55:19.448Z" },
{ url = "https://files.pythonhosted.org/packages/bd/57/80f53264954dcefeebcf9dae6e3eb1daea1b488f0be8b8fef12f79a3eb10/pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36", size = 7670694, upload-time = "2025-07-03T13:10:56.432Z" }, { url = "https://files.pythonhosted.org/packages/d5/37/25c6692f06927ee973ff18c8d9ee98ad0b4d84ee67a09610c2dd1447958e/pillow-12.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3edce1d53195db527e0191f84b71d02022de0540bf43a16ed734ed7537b07385", size = 6322855, upload-time = "2026-07-01T11:55:21.613Z" },
{ url = "https://files.pythonhosted.org/packages/70/ff/4727d3b71a8578b4587d9c276e90efad2d6fe0335fd76742a6da08132e8c/pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b", size = 6005888, upload-time = "2025-07-01T09:15:39.436Z" }, { url = "https://files.pythonhosted.org/packages/cc/91/420637fcb8f1bc11029e403b4538e6694744428d8246118e45719f944556/pillow-12.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf16ba1b4d0b6b7c8e534936632270cf70eb00dbe09005bc345b2677b726855c", size = 6989642, upload-time = "2026-07-01T11:55:24.006Z" },
{ url = "https://files.pythonhosted.org/packages/05/ae/716592277934f85d3be51d7256f3636672d7b1abfafdc42cf3f8cbd4b4c8/pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477", size = 6670330, upload-time = "2025-07-01T09:15:41.269Z" }, { url = "https://files.pythonhosted.org/packages/10/08/b94d7811281ccf0d143a1cf768d1c49e1e54af63e7b708ab2ee3eb87face/pillow-12.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:24870b09b224f7ae3c39ed07d10e819d06f8720bc551847b1d623832b5b0e28d", size = 6391281, upload-time = "2026-07-01T11:55:26.252Z" },
{ url = "https://files.pythonhosted.org/packages/e7/bb/7fe6cddcc8827b01b1a9766f5fdeb7418680744f9082035bdbabecf1d57f/pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50", size = 6114089, upload-time = "2025-07-01T09:15:43.13Z" }, { url = "https://files.pythonhosted.org/packages/d2/87/24233f785f55474dc02ce3e739c5528a77e3a862e9333d1dd7a25cc31f70/pillow-12.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:30f2aa603c41533cc25c05acd0da21636e84a315768feb631c937177db558931", size = 7096716, upload-time = "2026-07-01T11:55:28.318Z" },
{ url = "https://files.pythonhosted.org/packages/8b/f5/06bfaa444c8e80f1a8e4bff98da9c83b37b5be3b1deaa43d27a0db37ef84/pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b", size = 6748206, upload-time = "2025-07-01T09:15:44.937Z" }, { url = "https://files.pythonhosted.org/packages/23/26/fcb2f6e37175b04f53570b59937867e2b80ee1685e744023153028fc14f9/pillow-12.3.0-cp314-cp314t-win32.whl", hash = "sha256:4b0a7fe987b14c31ebda6083f74f22b561fd3739bc0ac51e019622e3d72668c7", size = 6474125, upload-time = "2026-07-01T11:55:30.956Z" },
{ url = "https://files.pythonhosted.org/packages/f0/77/bc6f92a3e8e6e46c0ca78abfffec0037845800ea38c73483760362804c41/pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12", size = 6377370, upload-time = "2025-07-01T09:15:46.673Z" }, { url = "https://files.pythonhosted.org/packages/90/de/3634abee5f1c9e13c56787b7d5517b0ba8d6de51700b95578cf338349c9f/pillow-12.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:962864dc93511324d51ddbb5b9f8731bf71675b93ca612a07441896f4688fb8c", size = 7242939, upload-time = "2026-07-01T11:55:34.044Z" },
{ url = "https://files.pythonhosted.org/packages/4a/82/3a721f7d69dca802befb8af08b7c79ebcab461007ce1c18bd91a5d5896f9/pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db", size = 7121500, upload-time = "2025-07-01T09:15:48.512Z" }, { url = "https://files.pythonhosted.org/packages/ce/2a/fd13f8eb24de5714a6eb444a3d67e2842c6c576e159a43793adf23051351/pillow-12.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0740a512dc522224c77d9aa5a8d70d8b7d73fb91f2c21125d8d025d3b8990e45", size = 2567506, upload-time = "2026-07-01T11:55:35.988Z" },
{ url = "https://files.pythonhosted.org/packages/89/c7/5572fa4a3f45740eaab6ae86fcdf7195b55beac1371ac8c619d880cfe948/pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa", size = 2512835, upload-time = "2025-07-01T09:15:50.399Z" },
] ]
[[package]] [[package]]
name = "pillow-heif" name = "pillow-heif"
version = "1.1.0" version = "1.5.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "pillow" }, { name = "pillow" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/6f/d4/597cf8c54d1ed494a46cc12e358d2f73a993b5f139402b35681f56beffde/pillow_heif-1.1.0.tar.gz", hash = "sha256:6c0c5f81a780185bbddc56e0d5537c53aa6cb5fb6018f5a60534a47c53f5455d", size = 18271020, upload-time = "2025-08-02T09:58:32.54Z" } sdist = { url = "https://files.pythonhosted.org/packages/08/dc/6ab7a469ced899fbdbbc27b00067e4da02c21ff1ce7e9c6110f14e8fea6c/pillow_heif-1.5.0.tar.gz", hash = "sha256:16b11a37b762ff42da2d36527bb5cb14bd9194c24c389ee911155d6e23c53065", size = 17114105, upload-time = "2026-07-22T14:27:50.571Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/cf/93/e32cd695bca9750f6fa166f28a4c84fd8a23209f99d56712359c0b42f2e6/pillow_heif-1.1.0-cp314-cp314-macosx_13_0_x86_64.whl", hash = "sha256:c47f12a30a86c43f714353d0fb8478dc4da7915d678642e0b6cf6f0fd53d711e", size = 3384304, upload-time = "2025-08-02T09:57:55.997Z" }, { url = "https://files.pythonhosted.org/packages/67/51/9b5a9352b86b7d82ad62969a84e3b4f998f6b5dc0320cec3e2d54d74d754/pillow_heif-1.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:8d96783ca1648ba8384e66b888128ca3a48d769c0e3593acf84c40ce0c791629", size = 4743156, upload-time = "2026-07-22T14:27:15.721Z" },
{ url = "https://files.pythonhosted.org/packages/62/88/3e7cda3383c165a5eebf9a0193450de2b810b7654dbc363c8c26a87be6f2/pillow_heif-1.1.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3cc4a7a808b659f657c5144aeec8152d261d8f3c4a859eba66ef31dab26982d2", size = 2261529, upload-time = "2025-08-02T09:57:57.6Z" }, { url = "https://files.pythonhosted.org/packages/2a/10/dc89bec0edf37df5d7609d76ce125a2f8d1e901b2d34944d64d05f497bef/pillow_heif-1.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5c50626e77510f38cb11aacb3b4bba56eff17ce39a9d85b7ee210bd81387a083", size = 4264368, upload-time = "2026-07-22T14:27:17.198Z" },
{ url = "https://files.pythonhosted.org/packages/bf/f2/469d9ef5c64efecbc0559aa56ebe6539007f478d8aee83253037dc203567/pillow_heif-1.1.0-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f3b0df4a03f9c7f150d801f3644961df5af0739503d6fc5d3e0aeb540d16c3d", size = 5780880, upload-time = "2025-08-02T09:57:59.408Z" }, { url = "https://files.pythonhosted.org/packages/cf/54/837e9077784df4a0ba69d3e76e9f29a4e52929c84f0e1b0d29a13437a5cc/pillow_heif-1.5.0-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4fb15135733626ee3fd4d7104bde1e54d4370f9f8b697a5d239b8b552b73fc7b", size = 6346432, upload-time = "2026-07-22T14:27:18.55Z" },
{ url = "https://files.pythonhosted.org/packages/13/74/a5c13f96ba06f4ec7d85a6d343b91c2767bd5a3fd90c146cf3c59fbcb12c/pillow_heif-1.1.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44fcd3a171337788bf672882484725f124143d2f290cfdbf029c22c113a15c79", size = 5502822, upload-time = "2025-08-02T09:58:00.841Z" }, { url = "https://files.pythonhosted.org/packages/2b/90/42f392c0513bf351ba2bca1350c457b0d7bd94f12d0e5fa74d7a9deae336/pillow_heif-1.5.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:646ddad98c8f9c5e637dbb84b6b7db905e565994e9fd6a496a36adff36ae28b3", size = 5602088, upload-time = "2026-07-22T14:27:20.112Z" },
{ url = "https://files.pythonhosted.org/packages/57/04/1a6a88647dbb0eec95fa8b9d5cac501f690cdd83479e7cacf56772f1189f/pillow_heif-1.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f07f28e99ae74dffca936e24d8881f3e7e572233cf1f7bc16fe42fb08a795be", size = 6822779, upload-time = "2025-08-02T09:58:02.238Z" }, { url = "https://files.pythonhosted.org/packages/cb/4d/8070974a2e695f83fb10883b7d83c3eda635561f99993623bf111a55a984/pillow_heif-1.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b9803c1291c7c598134f16016a071ec8d9dce268ab1806c8541450c4e3972893", size = 7374543, upload-time = "2026-07-22T14:27:22.08Z" },
{ url = "https://files.pythonhosted.org/packages/ae/37/8873625e681505940381cff6a4a191301f98d9a5c1499f60a2e892ad8c89/pillow_heif-1.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7a6de39cfba4037479aa5deb65a6bb0b27da0a11b95f740202f03b578ee8932f", size = 6429866, upload-time = "2025-08-02T09:58:03.637Z" }, { url = "https://files.pythonhosted.org/packages/4a/78/9a5abfb163b0fb44a4f624146de142ba4887e5011f965442af2c997db6bc/pillow_heif-1.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1ad925299422d4592e4b5344c03e25b27dc7a5130447603a89404d7d138b746c", size = 6633558, upload-time = "2026-07-22T14:27:23.97Z" },
{ url = "https://files.pythonhosted.org/packages/6b/3c/63cbbe3a6a54e3ec925d2433bb431a4bf61695f34b5f1e689db642fb20c1/pillow_heif-1.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:514c856230995dc2f918fb12d83906885d42829e911868e23035e2d916c4c7c5", size = 5573890, upload-time = "2025-08-02T09:58:05.049Z" }, { url = "https://files.pythonhosted.org/packages/7d/bc/7bae1c1efcec25dfdca950fe1a10cd723c544269db73203019e965394015/pillow_heif-1.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:cfd5f8a8a7b65f2ded217c5bde4dd9365b6b67187657a121b05224841a1b92c0", size = 6703549, upload-time = "2026-07-22T14:27:25.504Z" },
{ url = "https://files.pythonhosted.org/packages/74/8d/fff074683f96eb004d7159228d2fc9e2e9fe8afd899d53aa695c622487d8/pillow_heif-1.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:6759febf63ea31fd00756e4a828cd643d0b9f98c93f5814b0ce5baa036da50e4", size = 4017234, upload-time = "2026-07-22T14:27:27.443Z" },
{ url = "https://files.pythonhosted.org/packages/57/04/fa5727a62b2fc37b611c7a172fc10c6719530d27bc9126de8456fac69346/pillow_heif-1.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:e2c3f47eac33d8368ea5779eb7bd8c736e7048b5897474d7e1799c073c9505e3", size = 4744030, upload-time = "2026-07-22T14:27:29.047Z" },
{ url = "https://files.pythonhosted.org/packages/4c/6f/afaccf55774c1084e5beba83f0ac201d315096f6a95d003696376184a7a3/pillow_heif-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d0881c82752d676fbe054cb0258100790ead94946fbb075ffa041bb9ae579b11", size = 4265061, upload-time = "2026-07-22T14:27:30.703Z" },
{ url = "https://files.pythonhosted.org/packages/f7/a6/c90f9fffd5cb56e213753f22a9a955525d5ad99816481ae2392ab6c7ccd8/pillow_heif-1.5.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d5810606f70d3432b55987a5e44aa26fb7598fea565d8645b017f7ed935f5e99", size = 6351671, upload-time = "2026-07-22T14:27:32.231Z" },
{ url = "https://files.pythonhosted.org/packages/74/b3/e15933817e71b9fcd8865e3b64171995ed26f5ca329bf4d03f3571213c52/pillow_heif-1.5.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e737d326410e733868566c045f651b5371086a4ec294c40d39e129481d024bef", size = 5606028, upload-time = "2026-07-22T14:27:34.213Z" },
{ url = "https://files.pythonhosted.org/packages/f5/55/2665f6c781b90bb5a25595c58ede1955125f0ad2c7d81bf3c153c9e9c9d8/pillow_heif-1.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:522719e5dd56eae6f9f48828c08eefe0e5c02dd92f5a07780e6f3c04bdbcf880", size = 7379667, upload-time = "2026-07-22T14:27:35.883Z" },
{ url = "https://files.pythonhosted.org/packages/54/77/0f0487b9f6cb1f5dff629b42ad08a7ae1997886048c4c2671e17079ad5d6/pillow_heif-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f1d281b2efee954ef78c0a473a41498d0548a32290d7250b4879ddd2480dffb8", size = 6637581, upload-time = "2026-07-22T14:27:37.67Z" },
{ url = "https://files.pythonhosted.org/packages/8a/b7/c65adbcab1b1c99c886c55b97b144b3092c432e7b0fedb91501c479cebe4/pillow_heif-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:65b3d619cea2d1f758cd039cddb331cb44d63768c355606ea9100cfee4421eb4", size = 6704174, upload-time = "2026-07-22T14:27:39.32Z" },
{ url = "https://files.pythonhosted.org/packages/17/53/2a52f64f399717adae560068e9b0ae12ae3e43be2c64991e246af5aedc3f/pillow_heif-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:508fc9dd8fb4df933b666c30b60b0930270d9e072fcd90b75990166050e44656", size = 4017728, upload-time = "2026-07-22T14:27:41.102Z" },
] ]
[[package]] [[package]]
@@ -565,7 +588,7 @@ wheels = [
[[package]] [[package]]
name = "pytest" name = "pytest"
version = "9.0.3" version = "9.1.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" }, { name = "colorama", marker = "sys_platform == 'win32'" },
@@ -574,9 +597,9 @@ dependencies = [
{ name = "pluggy" }, { name = "pluggy" },
{ name = "pygments" }, { 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" } sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" }
wheels = [ 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" }, { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" },
] ]
[[package]] [[package]]
@@ -619,7 +642,7 @@ wheels = [
[[package]] [[package]]
name = "requests" name = "requests"
version = "2.32.4" version = "2.34.2"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "certifi" }, { name = "certifi" },
@@ -627,34 +650,34 @@ dependencies = [
{ name = "idna" }, { name = "idna" },
{ name = "urllib3" }, { name = "urllib3" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" } sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" },
] ]
[[package]] [[package]]
name = "ruff" name = "ruff"
version = "0.15.13" version = "0.16.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
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" } sdist = { url = "https://files.pythonhosted.org/packages/70/25/7113f6d5498888c5fb7db34081cba7d5971c4cb1bfb26819966eee68f003/ruff-0.16.1.tar.gz", hash = "sha256:fedad7c801dabd3fb9741d76aca39246e6ddd9ca446a015875207bf19f1e6bc7", size = 4877500, upload-time = "2026-07-30T19:37:01.379Z" }
wheels = [ wheels = [
{ 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/1b/bd/694da69368e0973de65df2ddc73ab18d43c469d5963d9b150911de6bc513/ruff-0.16.1-py3-none-linux_armv6l.whl", hash = "sha256:58edb313b88f0c5460a26adf5f39a37a3be789494a15e3e411e35fa78b89f9a0", size = 10839126, upload-time = "2026-07-30T19:36:13.697Z" },
{ 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/3f/f0/b626e5d5bd0dd9576263658ef12885e2288afd1029a48e26ffed65ec1ac1/ruff-0.16.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:fde5a99e2f97479af66edd6622c6d5a2a7592c77cf4153d9e4428f5eeb55b60c", size = 11070253, upload-time = "2026-07-30T19:36:17.14Z" },
{ 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/83/63/f40acfb6b35b88623e71684942b552c3edd96035f5d98f313815f7b277de/ruff-0.16.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e0d4c20532fca4f7fa609369161d968dd28f65d83dabbd61d8e9c7edbf7001f6", size = 10561425, upload-time = "2026-07-30T19:36:20.04Z" },
{ 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/aa/dd/14ec0e9c2b4d315547dd38765004b4863e354e1b52cb308272215d9f6f6d/ruff-0.16.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30affbcedf59ad5703d9c91f82266e02b47739f797e1a7b6e158e5526a6dae38", size = 10948879, upload-time = "2026-07-30T19:36:22.476Z" },
{ 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/33/e9/9d870cbae575030fdef595f04b4b97573c525b5497cce4f4498cf2f85446/ruff-0.16.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:24e9c631573cbca9d20f1283f8f479b2afa4a8503504822bd71a293889f16743", size = 10643691, upload-time = "2026-07-30T19:36:24.914Z" },
{ 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/c4/09/12743d544e2173f53ecd27217c65f90d2bc0f8424a66a60339e56bbc0457/ruff-0.16.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b41bdd48fb420987a9b5212e4957c26ad4abce401fa9ea9d4d85843727945f4f", size = 11435354, upload-time = "2026-07-30T19:36:28.447Z" },
{ 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/7f/89/a1652b2daee52083c9554a6333b678a8b01d0400f976827bb87857f9449a/ruff-0.16.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b0d1e1393b7648079e13669de1c1f4fde06d4583e84d8fd5c1551e0a77a2aa75", size = 12259033, upload-time = "2026-07-30T19:36:31.326Z" },
{ 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/16/96/ecdcb8c54ee7b123b487f807eb014e6e019155a0b81dfb669acd52f28ce3/ruff-0.16.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:07bf434b1c95f4e093be4532068ef4fcf00924eb2ade8796075980902d6fd54a", size = 11667981, upload-time = "2026-07-30T19:36:34.394Z" },
{ url = "https://files.pythonhosted.org/packages/e8/31/bf1a0803d077e679cfeee5f2f67290a0fa79c7385b5d9a8c17b9db2c48f0/ruff-0.15.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc411dfebe5eebe55ce041c6ae080eb7668955e866daa2fbb16692a784f1c4ca", size = 11434486, upload-time = "2026-05-14T13:44:27.761Z" }, { url = "https://files.pythonhosted.org/packages/cd/90/c52e12e0d862e9572f2a33aa227409143520abe53111e9a6babbac7b4af8/ruff-0.16.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39897739f112253ee4fdd2e8aa9a4f9ded99fb2be367d5f31dfa4ded6025584c", size = 11468183, upload-time = "2026-07-30T19:36:37.339Z" },
{ 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/2c/6b/4ffb7ad1d83eb16cf8cbb3c8815d3f11c88460fd162d4b372a2059be1c2a/ruff-0.16.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:82ae3c0c0d74daf17b968a10b7b3bb3ef297ab7de0c1f749646b25e690ccb150", size = 11470071, upload-time = "2026-07-30T19:36:39.91Z" },
{ 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/9c/72/32ae7db4c0b5e32ab611787caa19d1546800676d79f7483b7100a3561bf4/ruff-0.16.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4d5f2ed10f8242d83fc08d521301089364e3375375705356f20c0e31606ef3ef", size = 10919503, upload-time = "2026-07-30T19:36:42.65Z" },
{ 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/f7/ca/3d901ba6ad6fc38da39c3448fc6c59ac945679293a17c3ceb6d6c1cba13e/ruff-0.16.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a4665b309891f83f3e3c25447935f1213e9abbd4b5640af7a1f2def9f8d413c1", size = 10649861, upload-time = "2026-07-30T19:36:45.18Z" },
{ 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/92/79/894ef1ced26552d5f8c9cf6d85b0687840e1128c55aeab7b9c2d54a0d880/ruff-0.16.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:26e9ca5c9bc3971f20d3cf18a957f52ffd6a5f6564ff15c4912a144dcac22494", size = 11148137, upload-time = "2026-07-30T19:36:47.936Z" },
{ 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/2d/69/3609a09fa1cb46cc28b762363e440a354204e5dff01bd0c8d7437874d6b9/ruff-0.16.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:67e1e1e3fa4f0c82f0e36d4cd61e661f6e7a6196cb1aa92fe0828fa7b8f257cd", size = 11559211, upload-time = "2026-07-30T19:36:50.448Z" },
{ 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/fc/8a/fb22af2fd78a736e241fabf67e30ce1799a64244026377a49e133af90762/ruff-0.16.1-py3-none-win32.whl", hash = "sha256:d31765e131295b8445caf301e3e8a85b34d1b9b211b4109b7ba457888b051806", size = 10838258, upload-time = "2026-07-30T19:36:53.298Z" },
{ 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/d4/35/e57fd9fb5d423961df087a00b12d42c0a830288dc2f3b45ecca299158b4f/ruff-0.16.1-py3-none-win_amd64.whl", hash = "sha256:09b05e8b90c2cb06ad63464350e7a45e8e44a2dfe52072ebfba6666ca8d3f596", size = 11961111, upload-time = "2026-07-30T19:36:56.107Z" },
{ 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" }, { url = "https://files.pythonhosted.org/packages/cb/46/240ea004bf6dc4feb40e9832f2205a476a47dd5b8a3f8211a5fc5f95e20e/ruff-0.16.1-py3-none-win_arm64.whl", hash = "sha256:dbaadaac38c70239f056d306b7476f246b0bf000fa6b3876402acbf5b227eaf8", size = 11309414, upload-time = "2026-07-30T19:36:58.79Z" },
] ]
[[package]] [[package]]
@@ -668,20 +691,20 @@ wheels = [
[[package]] [[package]]
name = "soupsieve" name = "soupsieve"
version = "2.7" version = "2.9.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/3f/f4/4a80cd6ef364b2e8b65b15816a843c0980f7a5a2b4dc701fc574952aa19f/soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a", size = 103418, upload-time = "2025-04-20T18:50:08.518Z" } sdist = { url = "https://files.pythonhosted.org/packages/d9/38/e12680bbe6b4f8f3d17adcaf38d26850aa756c85cf4a80e79fc12a018fe8/soupsieve-2.9.1.tar.gz", hash = "sha256:c33e6605bbc71dd628b00c632d58ae607c22bade247e52553928f83bbb75b4ba", size = 122261, upload-time = "2026-07-21T16:57:17.452Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/e7/9c/0e6afc12c269578be5c0c1c9f4b49a8d32770a080260c333ac04cc1c832d/soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4", size = 36677, upload-time = "2025-04-20T18:50:07.196Z" }, { url = "https://files.pythonhosted.org/packages/0f/2c/437fe806897c2d6cfdc3ee43a18da8bf8e568530a4ae9bac781541ca9896/soupsieve-2.9.1-py3-none-any.whl", hash = "sha256:4f4477399246b7a0c720a88ca2454b11cd6bb9ae4c9d170140786e916776c14c", size = 37404, upload-time = "2026-07-21T16:57:16.421Z" },
] ]
[[package]] [[package]]
name = "sqlparse" name = "sqlparse"
version = "0.5.3" version = "0.5.5"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e5/40/edede8dd6977b0d3da179a342c198ed100dd2aba4be081861ee5911e4da4/sqlparse-0.5.3.tar.gz", hash = "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272", size = 84999, upload-time = "2024-12-10T12:05:30.728Z" } sdist = { url = "https://files.pythonhosted.org/packages/90/76/437d71068094df0726366574cf3432a4ed754217b436eb7429415cf2d480/sqlparse-0.5.5.tar.gz", hash = "sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e", size = 120815, upload-time = "2025-12-19T07:17:45.073Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/a9/5c/bfd6bd0bf979426d405cc6e71eceb8701b148b16c21d2dc3c261efc61c7b/sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca", size = 44415, upload-time = "2024-12-10T12:05:27.824Z" }, { url = "https://files.pythonhosted.org/packages/49/4b/359f28a903c13438ef59ebeee215fb25da53066db67b305c125f1c6d2a25/sqlparse-0.5.5-py3-none-any.whl", hash = "sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba", size = 46138, upload-time = "2025-12-19T07:17:46.573Z" },
] ]
[[package]] [[package]]
@@ -704,34 +727,34 @@ wheels = [
[[package]] [[package]]
name = "typing-extensions" name = "typing-extensions"
version = "4.14.1" version = "4.16.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" },
] ]
[[package]] [[package]]
name = "tzdata" name = "tzdata"
version = "2025.2" version = "2026.3"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } sdist = { url = "https://files.pythonhosted.org/packages/92/ff/5a28bdfd8c3ebec42564ac7d0e54ca3db65044a9314a97f9564fa7a1e926/tzdata-2026.3.tar.gz", hash = "sha256:4a1518b8993086a7982523e071643f3c0e5f213e75b21318e78bcabfff9d1415", size = 198674, upload-time = "2026-07-10T08:50:37.887Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, { url = "https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl", hash = "sha256:dc096730c87af6cab1b171c9d532be840741ff5d459015e7f6947bd7d7e54931", size = 348168, upload-time = "2026-07-10T08:50:36.46Z" },
] ]
[[package]] [[package]]
name = "urllib3" name = "urllib3"
version = "2.5.0" version = "2.7.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" },
] ]
[[package]] [[package]]
name = "wagtail" name = "wagtail"
version = "7.4.1" version = "7.4.2"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "anyascii" }, { name = "anyascii" },
@@ -753,22 +776,22 @@ dependencies = [
{ name = "telepath" }, { name = "telepath" },
{ name = "willow", extra = ["heif"] }, { name = "willow", extra = ["heif"] },
] ]
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" } sdist = { url = "https://files.pythonhosted.org/packages/91/83/a644e4657aba0f6d56e225d5a46f6471e44dab7fbfcac48af1d1c4f6a70b/wagtail-7.4.2.tar.gz", hash = "sha256:6e261b50693abfbe2b738717bedc5bd0915e2e08efb9400102f4098c0c03aaab", size = 6811456, upload-time = "2026-06-15T15:36:21.187Z" }
wheels = [ wheels = [
{ 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" }, { url = "https://files.pythonhosted.org/packages/9e/2e/9b0903d637ccfcbf1f8847d0f649d9d29eb46b329135313abff7835c0610/wagtail-7.4.2-py3-none-any.whl", hash = "sha256:ae80a6d536a4098f7c9d37e169ba0f14c29c9769b50b6d92546f41e5cad201d8", size = 9463486, upload-time = "2026-06-15T15:36:15.799Z" },
] ]
[[package]] [[package]]
name = "wagtail-factories" name = "wagtail-factories"
version = "4.4.0" version = "4.5.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "factory-boy" }, { name = "factory-boy" },
{ name = "wagtail" }, { 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" } sdist = { url = "https://files.pythonhosted.org/packages/0c/b8/0a6e91ea32467acd96c5769face88c6ac79f2f0990f822d7396d054bbc15/wagtail_factories-4.5.0.tar.gz", hash = "sha256:d981ceaaf7715b2325f9debb1be1ffc07e338ae8957e9527f4f9175fcdff2c56", size = 10131, upload-time = "2026-06-04T15:29:25.031Z" }
wheels = [ 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" }, { url = "https://files.pythonhosted.org/packages/8f/18/36e0be763870e22b1f0f87361cc768a7bfcc842f6504a6bafdddf932fd7d/wagtail_factories-4.5.0-py3-none-any.whl", hash = "sha256:4ad0e9f396fca7f7d349a8752c49f68e18deeffee9c857897b849ff08c6c33a6", size = 10753, upload-time = "2026-06-04T15:29:24.087Z" },
] ]
[[package]] [[package]]
@@ -787,14 +810,14 @@ wheels = [
[[package]] [[package]]
name = "wagtail-headless-preview" name = "wagtail-headless-preview"
version = "0.8.0" version = "0.9.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "wagtail" }, { name = "wagtail" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/b4/28/87fda34fd89999bee03519a614ddca608def6d07b69d93365833c434b9c5/wagtail_headless_preview-0.8.0.tar.gz", hash = "sha256:581d8419cd1ef1f7de88235445e9695e5591d46259283d56bfe814e8620fa1d5", size = 13957, upload-time = "2024-02-23T15:53:44.986Z" } sdist = { url = "https://files.pythonhosted.org/packages/46/c4/5fe6d6c0df1a11be144d6cc9e0f951fb871490208b93237eeda75f8a1bd5/wagtail_headless_preview-0.9.0.tar.gz", hash = "sha256:dbabbb614bd1689f048738c46034c557a3ee426e0ef80feb4831bcfc93a24e15", size = 14887, upload-time = "2026-06-13T10:49:37.597Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/62/57/bb6c10a11f7a0ec0d8e10ec99b4c7b5a6036e8846e61d145966b6c72113d/wagtail_headless_preview-0.8.0-py3-none-any.whl", hash = "sha256:91b305c36573490856e212f9e5645ada81f72ad37f1bb00e6feddc5537252358", size = 12301, upload-time = "2024-02-23T15:53:43.904Z" }, { url = "https://files.pythonhosted.org/packages/69/80/0cc32c5192f875d5556487cc71e7280bbfd734b5d6bf24156c5f15e23c92/wagtail_headless_preview-0.9.0-py3-none-any.whl", hash = "sha256:9047701345323d77938c07685279770646c92545bed851c6608f026ac645f16d", size = 12781, upload-time = "2026-06-13T10:49:35.969Z" },
] ]
[[package]] [[package]]
@@ -808,15 +831,15 @@ wheels = [
[[package]] [[package]]
name = "willow" name = "willow"
version = "1.11.0" version = "1.12.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "defusedxml" }, { name = "defusedxml" },
{ name = "filetype" }, { name = "filetype" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/a3/bd/2a383be24c3e47423aa9b0aa5b4ca818ef193506b58800dd51e1b89d7bb3/willow-1.11.0.tar.gz", hash = "sha256:70292b2d0cd2d5bb4076f0b3d61308aeaa0b225f3970d00752f08a8fd386c3d1", size = 113827, upload-time = "2025-07-16T08:46:26.939Z" } sdist = { url = "https://files.pythonhosted.org/packages/21/56/93e0bc60a3f9fe3b8ad9bac96c34179d1f4f37d42c8fddcf8de16ba5a711/willow-1.12.0.tar.gz", hash = "sha256:db29c8d2f19b9d91f4ce22881521055aa2f6070131d64b4098cb86a98e851a70", size = 113747, upload-time = "2025-10-26T13:22:22.345Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/c1/05/b3f1b443c31ad871c48e19ea2be189681c2df4ccf594b1dd83d6775c032b/willow-1.11.0-py3-none-any.whl", hash = "sha256:0a4388dbf18726eef8f27449659047689c39b7023045ca5a8a75410d3864ee6f", size = 119459, upload-time = "2025-07-16T08:46:25.596Z" }, { url = "https://files.pythonhosted.org/packages/76/ef/a2e237f856b374e466fba458a6188a73fd68db7fb6b76706ce454980092f/willow-1.12.0-py3-none-any.whl", hash = "sha256:d3541690b6726a4f6f4654f80507d81bacfa0c686ab065eb5c90159f1b9cee38", size = 119425, upload-time = "2025-10-26T13:22:21.113Z" },
] ]
[package.optional-dependencies] [package.optional-dependencies]
@@ -0,0 +1,29 @@
# Generated by Django 6.0.5 on 2026-07-02 14:53
import wagtail.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('venues', '0025_verbose_names'),
]
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), ('schedule', 33), ('embed', 34), ('raw_html', 35)], 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.blocks.CharBlock', (), {'help_text': 'Overskrift for timeplanen (valgfritt)', 'label': 'Tittel', 'max_length': 64, 'required': False}), 28: ('wagtail.blocks.CharBlock', (), {'help_text': 'Fritekst, f.eks. "13:00", "20:00 23:00" eller "23.06"', 'label': 'Tidspunkt/dato', 'max_length': 64, 'required': True}), 29: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link'], 'help_text': 'Kort tittel på én linje. Du kan legge inn lenker.', 'label': 'Tittel', 'required': True}), 30: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link'], 'help_text': 'Valgfri tilleggsinformasjon.', 'label': 'Beskrivelse', 'required': False}), 31: ('wagtail.blocks.StructBlock', [[('time', 28), ('title', 29), ('description', 30)]], {}), 32: ('wagtail.blocks.ListBlock', (31,), {'label': 'Punkter', 'min_num': 1}), 33: ('wagtail.blocks.StructBlock', [[('title', 27), ('items', 32)]], {}), 34: ('wagtail.embeds.blocks.EmbedBlock', (), {}), 35: ('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), ('schedule', 33), ('embed', 34), ('raw_html', 35)], 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.blocks.CharBlock', (), {'help_text': 'Overskrift for timeplanen (valgfritt)', 'label': 'Tittel', 'max_length': 64, 'required': False}), 28: ('wagtail.blocks.CharBlock', (), {'help_text': 'Fritekst, f.eks. "13:00", "20:00 23:00" eller "23.06"', 'label': 'Tidspunkt/dato', 'max_length': 64, 'required': True}), 29: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link'], 'help_text': 'Kort tittel på én linje. Du kan legge inn lenker.', 'label': 'Tittel', 'required': True}), 30: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link'], 'help_text': 'Valgfri tilleggsinformasjon.', 'label': 'Beskrivelse', 'required': False}), 31: ('wagtail.blocks.StructBlock', [[('time', 28), ('title', 29), ('description', 30)]], {}), 32: ('wagtail.blocks.ListBlock', (31,), {'label': 'Punkter', 'min_num': 1}), 33: ('wagtail.blocks.StructBlock', [[('title', 27), ('items', 32)]], {}), 34: ('wagtail.embeds.blocks.EmbedBlock', (), {}), 35: ('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), ('schedule', 33), ('embed', 34), ('raw_html', 35)], 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.blocks.CharBlock', (), {'help_text': 'Overskrift for timeplanen (valgfritt)', 'label': 'Tittel', 'max_length': 64, 'required': False}), 28: ('wagtail.blocks.CharBlock', (), {'help_text': 'Fritekst, f.eks. "13:00", "20:00 23:00" eller "23.06"', 'label': 'Tidspunkt/dato', 'max_length': 64, 'required': True}), 29: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link'], 'help_text': 'Kort tittel på én linje. Du kan legge inn lenker.', 'label': 'Tittel', 'required': True}), 30: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link'], 'help_text': 'Valgfri tilleggsinformasjon.', 'label': 'Beskrivelse', 'required': False}), 31: ('wagtail.blocks.StructBlock', [[('time', 28), ('title', 29), ('description', 30)]], {}), 32: ('wagtail.blocks.ListBlock', (31,), {'label': 'Punkter', 'min_num': 1}), 33: ('wagtail.blocks.StructBlock', [[('title', 27), ('items', 32)]], {}), 34: ('wagtail.embeds.blocks.EmbedBlock', (), {}), 35: ('wagtail.blocks.RawHTMLBlock', (), {})}, default=[('paragraph', '')]),
),
]
File diff suppressed because one or more lines are too long
@@ -0,0 +1,29 @@
# Generated by Django 6.0.5 on 2026-07-04 22:29
import wagtail.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('venues', '0027_alter_venueindex_body_alter_venuepage_body_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), ('schedule', 33), ('embed', 34), ('raw_html', 35)], 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.blocks.CharBlock', (), {'help_text': 'Overskrift for timeplanen (valgfritt)', 'label': 'Tittel', 'max_length': 64, 'required': False}), 28: ('wagtail.blocks.CharBlock', (), {'help_text': 'Fritekst, f.eks. "13:00", "20:00 23:00" eller "23.06"', 'label': 'Tidspunkt/dato', 'max_length': 64, 'required': True}), 29: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link'], 'help_text': 'Kort tittel på én linje. Du kan legge inn lenker.', 'label': 'Tittel', 'required': True}), 30: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link'], 'help_text': 'Valgfri tilleggsinformasjon.', 'label': 'Beskrivelse', 'required': False}), 31: ('wagtail.blocks.StructBlock', [[('time', 28), ('title', 29), ('description', 30)]], {}), 32: ('wagtail.blocks.ListBlock', (31,), {'label': 'Punkter', 'min_num': 1}), 33: ('wagtail.blocks.StructBlock', [[('title', 27), ('items', 32)]], {}), 34: ('wagtail.embeds.blocks.EmbedBlock', (), {}), 35: ('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), ('schedule', 33), ('embed', 34), ('raw_html', 35)], 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.blocks.CharBlock', (), {'help_text': 'Overskrift for timeplanen (valgfritt)', 'label': 'Tittel', 'max_length': 64, 'required': False}), 28: ('wagtail.blocks.CharBlock', (), {'help_text': 'Fritekst, f.eks. "13:00", "20:00 23:00" eller "23.06"', 'label': 'Tidspunkt/dato', 'max_length': 64, 'required': True}), 29: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link'], 'help_text': 'Kort tittel på én linje. Du kan legge inn lenker.', 'label': 'Tittel', 'required': True}), 30: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link'], 'help_text': 'Valgfri tilleggsinformasjon.', 'label': 'Beskrivelse', 'required': False}), 31: ('wagtail.blocks.StructBlock', [[('time', 28), ('title', 29), ('description', 30)]], {}), 32: ('wagtail.blocks.ListBlock', (31,), {'label': 'Punkter', 'min_num': 1}), 33: ('wagtail.blocks.StructBlock', [[('title', 27), ('items', 32)]], {}), 34: ('wagtail.embeds.blocks.EmbedBlock', (), {}), 35: ('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), ('schedule', 33), ('embed', 34), ('raw_html', 35)], 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.blocks.CharBlock', (), {'help_text': 'Overskrift for timeplanen (valgfritt)', 'label': 'Tittel', 'max_length': 64, 'required': False}), 28: ('wagtail.blocks.CharBlock', (), {'help_text': 'Fritekst, f.eks. "13:00", "20:00 23:00" eller "23.06"', 'label': 'Tidspunkt/dato', 'max_length': 64, 'required': True}), 29: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link'], 'help_text': 'Kort tittel på én linje. Du kan legge inn lenker.', 'label': 'Tittel', 'required': True}), 30: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link'], 'help_text': 'Valgfri tilleggsinformasjon.', 'label': 'Beskrivelse', 'required': False}), 31: ('wagtail.blocks.StructBlock', [[('time', 28), ('title', 29), ('description', 30)]], {}), 32: ('wagtail.blocks.ListBlock', (31,), {'label': 'Punkter', 'min_num': 1}), 33: ('wagtail.blocks.StructBlock', [[('title', 27), ('items', 32)]], {}), 34: ('wagtail.embeds.blocks.EmbedBlock', (), {}), 35: ('wagtail.blocks.RawHTMLBlock', (), {})}, default=[('paragraph', '')]),
),
]
+11
View File
@@ -27,3 +27,14 @@ exclude = '/migrations/'
id = "ruff-format" id = "ruff-format"
files = '^dnscms/.*\.py$' files = '^dnscms/.*\.py$'
exclude = '/migrations/' exclude = '/migrations/'
[[repos]]
repo = "local"
[[repos.hooks]]
id = "vitest"
name = "vitest"
entry = "npm --prefix web test"
language = "system"
files = '^web/(src/.*\.(ts|tsx)|package\.json|vitest\.config\.mts)$'
pass_filenames = false
+1787 -466
View File
File diff suppressed because it is too large Load Diff
+19 -10
View File
@@ -7,6 +7,8 @@
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "next lint", "lint": "next lint",
"test": "vitest run",
"test:watch": "vitest",
"codegen": "graphql-codegen", "codegen": "graphql-codegen",
"perf:build": "next build", "perf:build": "next build",
"perf:serve": "next start -p 3100", "perf:serve": "next start -p 3100",
@@ -15,22 +17,23 @@
"perf:lh": "wait-on http://localhost:3100/ && npm run perf:lh:desktop && npm run perf:lh:mobile" "perf:lh": "wait-on http://localhost:3100/ && npm run perf:lh:desktop && npm run perf:lh:mobile"
}, },
"dependencies": { "dependencies": {
"@graphql-codegen/cli": "^7.1.3", "@graphql-codegen/cli": "^7.2.0",
"@graphql-codegen/client-preset": "^6.0.1", "@graphql-codegen/client-preset": "^6.1.0",
"@parcel/watcher": "^2.5.6", "@parcel/watcher": "^2.5.6",
"@sindresorhus/slugify": "^3.0.0", "@sindresorhus/slugify": "^3.0.0",
"@urql/next": "^2.0.1", "@urql/next": "^2.0.1",
"date-fns": "^4.4.0", "date-fns": "^4.4.0",
"date-fns-tz": "^3.2.0", "date-fns-tz": "^3.2.0",
"graphql": "^16.14.2", "graphql": "^17.0.2",
"next": "^16.2.9", "html-react-parser": "^6.1.4",
"nuqs": "^2.8.9", "next": "^16.2.10",
"nuqs": "^2.9.0",
"react": "19.2.7", "react": "19.2.7",
"react-dom": "19.2.7", "react-dom": "19.2.7",
"react-intersection-observer": "^10.0.3", "react-intersection-observer": "^10.0.3",
"sass": "^1.101.0", "sass": "^1.101.0",
"sharp": "^0.35.1", "sharp": "^0.35.3",
"swiper": "^12.2.0", "swiper": "^14.0.1",
"urql": "^5.0.3", "urql": "^5.0.3",
"use-debounce": "^10.1.1" "use-debounce": "^10.1.1"
}, },
@@ -38,11 +41,12 @@
"@types/node": "^24", "@types/node": "^24",
"@types/react": "19.2.17", "@types/react": "19.2.17",
"@types/react-dom": "19.2.3", "@types/react-dom": "19.2.3",
"baseline-browser-mapping": "^2.10.38", "baseline-browser-mapping": "^2.10.42",
"eslint": "^9", "eslint": "^9",
"eslint-config-next": "16.2.9", "eslint-config-next": "16.2.10",
"lighthouse": "^13.4.0", "lighthouse": "^13.4.0",
"typescript": "^6", "typescript": "^6",
"vitest": "^4.1.10",
"wait-on": "^9.0.10" "wait-on": "^9.0.10"
}, },
"overrides": { "overrides": {
@@ -52,5 +56,10 @@
"browserslist": [ "browserslist": [
"> 0.5% in NO", "> 0.5% in NO",
"not dead" "not dead"
] ],
"allowScripts": {
"@parcel/watcher@2.5.6": true,
"sharp@0.34.5": true,
"unrs-resolver@1.7.2": true
}
} }
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 22 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 56 KiB

@@ -0,0 +1,25 @@
<svg width="385" height="56" viewBox="0 0 385 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_2021_703" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="385" height="56">
<path d="M384.115 0H0V55.2045H384.115V0Z" fill="white"/>
</mask>
<g mask="url(#mask0_2021_703)">
<path d="M17.8144 3.56014C18.4689 2.80011 19.2199 2.37034 20.0643 2.2693C20.9088 2.16525 21.708 2.31153 22.4651 2.70963C23.2251 3.10473 23.8102 3.70491 24.2204 4.51319C24.6335 5.32147 24.7391 6.27905 24.534 7.3844C22.3263 18.8617 20.6705 30.4416 19.5697 42.1225C19.4642 43.2248 19.1188 44.0512 18.5322 44.6016C17.9486 45.152 17.2685 45.4431 16.4934 45.4762C15.7168 45.5124 14.9583 45.3224 14.2194 44.9092C13.4789 44.4991 12.8833 43.8989 12.4339 43.1057C11.9875 42.3095 11.8156 41.3609 11.9212 40.2586C12.5757 33.539 13.4201 26.7681 14.4546 19.9429C13.6614 20.8402 12.8682 21.7375 12.072 22.6332C11.2788 23.5305 10.5052 24.4277 9.74968 25.3235C9.05752 26.1167 8.30805 26.554 7.49976 26.6384C6.69148 26.7229 5.92542 26.56 5.20159 26.1498C4.47775 25.7366 3.88964 25.1666 3.44025 24.4428C2.99389 23.719 2.77825 22.9197 2.79483 22.0421C2.81444 21.1614 3.1839 20.326 3.90471 19.5328C6.21646 16.8787 8.5267 14.2171 10.8354 11.5465C13.1427 8.87279 15.4695 6.21119 17.8144 3.56014Z" fill="#151E1D"/>
<path d="M58.2907 8.57267C59.666 10.6763 60.5677 13.0212 60.999 15.6059C61.4333 18.1876 61.4695 20.8145 61.1076 23.4837C60.7457 26.1543 60.0369 28.7224 58.9844 31.1865C57.9348 33.6521 56.6168 35.7979 55.0334 37.6226C53.9628 38.8652 52.7383 39.9781 51.36 40.9583C49.9801 41.94 48.4978 42.6819 46.9144 43.1841C45.3295 43.6832 43.7114 43.8114 42.0587 43.5701C40.4376 43.362 38.904 42.8267 37.4563 41.9656C36.0086 41.1061 34.7419 40.0384 33.6562 38.7626C32.5704 37.4884 31.7516 36.0935 31.2011 34.5765C30.1305 31.8545 29.6645 29.012 29.8017 26.0473C29.942 23.0795 30.3898 20.2189 31.1469 17.4638C31.56 15.9799 32.0607 14.5066 32.6488 13.0424C33.2354 11.5796 33.9593 10.2104 34.8203 8.93459C35.7161 7.62415 36.7581 6.45244 37.9449 5.41796C39.1347 4.38498 40.4542 3.57519 41.9018 2.98708C43.7959 2.19538 45.8015 1.94656 47.9217 2.23911C50.0405 2.53317 52.0129 3.24042 53.8391 4.36236C55.6683 5.48129 57.1522 6.88372 58.2907 8.57267ZM47.5357 33.5932C48.914 32.42 50.0948 31.0146 51.0765 29.3769C52.0567 27.7407 52.7895 25.9839 53.2721 24.105C53.7546 22.2275 53.9356 20.341 53.815 18.447C53.6943 16.5499 53.2027 14.7916 52.3432 13.1751C51.9963 12.5206 51.5741 11.9083 51.0765 11.3413C50.5773 10.7713 49.9817 10.3823 49.291 10.1772C48.4661 9.9359 47.6322 9.97812 46.7877 10.3038C45.9433 10.6296 45.1742 11.0518 44.4835 11.5706C42.8308 12.7769 41.4525 14.294 40.3516 16.1187C39.2493 17.9448 38.4305 19.9173 37.8966 22.036C37.3613 24.1562 37.1275 26.2147 37.1969 28.2127C37.2331 29.7328 37.5528 31.0628 38.156 32.1999C38.7592 33.3339 39.5584 34.1949 40.5567 34.7815C41.558 35.3697 42.6589 35.5838 43.8622 35.427C45.0686 35.2701 46.2931 34.6594 47.5357 33.5932Z" fill="#151E1D"/>
<path d="M73.848 36.6393C74.225 36.9862 74.5009 37.3993 74.6744 37.8819C74.8463 38.3645 74.9337 38.8651 74.9337 39.3839C74.9654 39.8664 74.8689 40.358 74.6442 40.8557C74.4225 41.3548 74.1571 41.777 73.848 42.1224C73.088 42.8794 72.1741 43.2564 71.1095 43.2564H70.4339C69.4356 43.2564 68.5233 42.8794 67.6954 42.1224C67.3169 41.777 67.0409 41.3548 66.869 40.8557C66.6956 40.358 66.593 39.8664 66.5614 39.3839C66.5252 38.9013 66.6172 38.4112 66.8388 37.9121C67.0635 37.4099 67.3485 36.9862 67.6954 36.6393C68.4554 35.8838 69.3677 35.5053 70.4339 35.5053H71.0552C72.0882 35.5053 73.0186 35.8838 73.848 36.6393Z" fill="#151E1D"/>
<path d="M100.363 22.7901C101.365 22.7901 102.167 23.0586 102.77 23.5924C103.373 24.1232 103.768 24.7777 103.958 25.5528C104.147 26.3294 104.147 27.106 103.958 27.8811C103.768 28.6577 103.373 29.2956 102.77 29.7932C102.167 30.2924 101.365 30.5412 100.363 30.5412C97.6083 30.4733 94.8592 30.4055 92.1177 30.3361C89.3792 30.2683 86.6316 30.2004 83.8781 30.131C82.8768 30.131 82.082 29.8641 81.4954 29.3288C80.9118 28.795 80.5152 28.139 80.3071 27.3624C80.102 26.5873 80.102 25.8121 80.3071 25.0401C80.5152 24.265 80.9118 23.6271 81.4954 23.1279C82.082 22.6258 82.8768 22.3739 83.8781 22.3739C86.6316 22.4433 89.3792 22.5112 92.1177 22.579C94.8592 22.6484 97.6083 22.7178 100.363 22.7901Z" fill="#151E1D"/>
<path d="M124.248 3.5602C124.902 2.80018 125.653 2.3704 126.498 2.26936C127.342 2.16531 128.142 2.31159 128.899 2.7097C129.659 3.10479 130.244 3.70497 130.654 4.51325C131.067 5.32154 131.173 6.27911 130.968 7.38447C128.76 18.8618 127.104 30.4416 126.003 42.1225C125.898 43.2249 125.552 44.0513 124.966 44.6017C124.382 45.1521 123.702 45.4431 122.927 45.4763C122.15 45.5125 121.392 45.3225 120.653 44.9093C119.913 44.4991 119.317 43.8989 118.868 43.1057C118.421 42.3095 118.249 41.361 118.355 40.2587C119.009 33.539 119.854 26.7682 120.888 19.943C120.095 20.8403 119.302 21.7375 118.506 22.6333C117.712 23.5305 116.939 24.4278 116.183 25.3235C115.491 26.1167 114.742 26.554 113.933 26.6385C113.125 26.7229 112.359 26.5601 111.635 26.1499C110.911 25.7367 110.323 25.1667 109.874 24.4429C109.427 23.719 109.212 22.9198 109.228 22.0421C109.248 21.1615 109.617 20.326 110.338 19.5328C112.65 16.8788 114.96 14.2172 117.269 11.5465C119.576 8.87285 121.903 6.21125 124.248 3.5602Z" fill="#151E1D"/>
<mask id="mask1_2021_703" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="135" y="0" width="31" height="46">
<path d="M135.889 0.236816H165.228V45.1674H135.889V0.236816Z" fill="white"/>
</mask>
<g mask="url(#mask1_2021_703)">
<path d="M162.239 20.878C163.204 21.083 163.91 21.4796 164.356 22.0663C164.806 22.6498 165.032 23.33 165.032 24.1051C165.032 24.8817 164.851 25.6161 164.489 26.3067C164.127 26.9944 163.599 27.5463 162.909 27.9595C162.221 28.3697 161.413 28.5069 160.484 28.3697C160.414 30.4733 160.354 32.5769 160.303 34.6791C160.25 36.7782 160.224 38.8803 160.224 40.9825C160.188 41.9838 159.911 42.786 159.392 43.3892C158.876 43.9924 158.231 44.389 157.456 44.5775C156.684 44.766 155.918 44.766 155.158 44.5775C154.401 44.389 153.764 43.9924 153.245 43.3892C152.727 42.786 152.467 41.9838 152.467 40.9825C152.467 38.6753 152.493 36.3665 152.546 34.0578C152.597 31.7506 152.675 29.4403 152.781 27.1271C148.646 26.5767 144.442 26.2328 140.168 26.0956C139.408 26.0956 138.681 25.8543 137.991 25.3718C137.303 24.8892 136.796 24.2785 136.471 23.5381C136.145 22.7991 136.136 22.015 136.446 21.1856C137.511 18.4637 138.38 15.6814 139.052 12.8374C139.728 9.99479 140.186 7.10699 140.428 4.17546C140.495 3.17868 140.821 2.37793 141.405 1.77474C141.991 1.17154 142.673 0.776449 143.45 0.586442C144.225 0.397944 144.991 0.397944 145.748 0.586442C146.508 0.776449 147.119 1.17154 147.581 1.77474C148.047 2.37793 148.246 3.17868 148.179 4.17546C147.974 6.62444 147.655 9.05532 147.226 11.4681C146.794 13.8809 146.218 16.2575 145.494 18.5979C146.769 18.7034 148.043 18.8256 149.319 18.9658C150.593 19.1031 151.849 19.2403 153.089 19.376C153.193 17.9977 153.288 16.6013 153.372 15.1898C153.46 13.7753 153.556 12.3608 153.662 10.9494C153.73 9.98424 154.039 9.20008 154.591 8.59689C155.141 7.99369 155.812 7.58955 156.605 7.38446C157.397 7.17636 158.171 7.17636 158.928 7.38446C159.688 7.58955 160.309 7.98615 160.791 8.57276C161.274 9.16087 161.464 9.95257 161.364 10.9494C161.259 12.5704 161.162 14.1825 161.075 15.787C160.991 17.3885 160.896 18.999 160.791 20.6186C161.033 20.6518 161.274 20.694 161.515 20.7453C161.757 20.798 161.998 20.8418 162.239 20.878Z" fill="#151E1D"/>
</g>
<path d="M177.491 36.6393C177.868 36.9862 178.144 37.3993 178.317 37.8819C178.489 38.3645 178.576 38.8651 178.576 39.3839C178.608 39.8664 178.511 40.358 178.287 40.8557C178.065 41.3548 177.8 41.777 177.491 42.1224C176.731 42.8794 175.817 43.2564 174.752 43.2564H174.076C173.078 43.2564 172.166 42.8794 171.338 42.1224C170.959 41.777 170.683 41.3548 170.512 40.8557C170.338 40.358 170.236 39.8664 170.204 39.3839C170.168 38.9013 170.26 38.4112 170.481 37.9121C170.706 37.4099 170.991 36.9862 171.338 36.6393C172.098 35.8838 173.01 35.5053 174.076 35.5053H174.698C175.731 35.5053 176.661 35.8838 177.491 36.6393Z" fill="#151E1D"/>
<path d="M223.701 21.9094C223.805 25.3205 223.891 28.7481 223.96 32.1939C224.028 35.6411 224.114 39.0869 224.22 42.5326C224.256 43.534 224.015 44.3362 223.496 44.9394C222.977 45.5426 222.338 45.9377 221.578 46.1277C220.821 46.3162 220.055 46.3162 219.279 46.1277C218.507 45.9377 217.853 45.5426 217.319 44.9394C216.784 44.3362 216.499 43.534 216.463 42.5326C216.429 41.4997 216.404 40.4758 216.384 39.4624C216.368 38.446 216.343 37.4206 216.312 36.3861L208.555 36.0724C208.28 37.3844 208.024 38.6843 207.783 39.9751C207.541 41.2659 207.3 42.5673 207.059 43.8778C206.885 44.8791 206.498 45.6029 205.895 46.0493C205.291 46.4987 204.593 46.7143 203.801 46.6947C203.008 46.6781 202.259 46.4715 201.552 46.0734C200.843 45.6783 200.291 45.1204 199.893 44.3965C199.498 43.6727 199.388 42.8101 199.561 41.8088C200.179 38.4309 200.877 35.0892 201.654 31.7837C202.429 28.4752 203.524 25.2511 204.935 22.1145C205.626 20.5673 206.505 18.8964 207.571 17.1019C208.641 15.3089 209.855 13.6637 211.215 12.1678C212.578 10.6688 214.086 9.57251 215.739 8.88034C217.29 8.22587 218.55 8.10523 219.515 8.51842C220.48 8.93312 221.237 9.67505 221.789 10.7442C222.339 11.8104 222.743 13.0424 223.001 14.4418C223.262 15.8382 223.435 17.2075 223.52 18.5496C223.607 19.8932 223.668 21.0136 223.701 21.9094ZM210.678 28.4239C211.607 28.4571 212.519 28.4902 213.416 28.5264C214.312 28.5596 215.208 28.5928 216.101 28.629C216.067 27.492 216.042 26.364 216.022 25.2451C216.006 24.1231 215.981 23.0117 215.95 21.9094C215.914 21.46 215.896 20.985 215.896 20.4858C215.896 19.9882 215.896 19.4785 215.896 18.9597C215.312 19.6519 214.813 20.3079 214.4 20.9262C213.707 21.9606 213.076 23.0374 212.506 24.1593C211.939 25.2782 211.432 26.4062 210.985 27.5432C210.949 27.6805 210.904 27.8267 210.853 27.9836C210.805 28.1374 210.746 28.2837 210.678 28.4239Z" fill="#151E1D"/>
<path d="M257.042 15.1355C257.25 16.9979 257.268 19.0231 257.096 21.2097C256.923 23.3978 256.526 25.5874 255.908 27.7785C255.288 29.9666 254.418 31.9994 253.296 33.8768C252.177 35.7558 250.799 37.3422 249.158 38.636C247.521 39.9269 245.619 40.7623 243.452 41.1393C241.175 41.5178 239.236 41.3444 237.631 40.6205C236.03 39.8967 234.703 38.7778 233.65 37.2607C232.6 35.7452 231.765 34.0065 231.147 32.0431C230.527 30.0767 230.087 28.0258 229.826 25.8905C229.568 23.7522 229.431 21.7088 229.416 19.762C229.399 17.8167 229.441 16.1036 229.542 14.6228C229.61 13.6577 229.928 12.8735 230.495 12.2703C231.065 11.6671 231.739 11.263 232.516 11.0579C233.291 10.8498 234.057 10.8498 234.814 11.0579C235.574 11.263 236.196 11.6596 236.678 12.2462C237.161 12.8298 237.367 13.6215 237.299 14.6228C237.263 15.1385 237.227 16.0162 237.191 17.2588C237.158 18.4983 237.176 19.9008 237.245 21.4691C237.313 23.0374 237.459 24.6147 237.685 26.1981C237.91 27.783 238.237 29.196 238.669 30.4386C239.098 31.6782 239.676 32.5739 240.4 33.1288C241.124 33.6808 242.036 33.6959 243.138 33.1771C244.654 32.4533 245.86 31.3856 246.758 29.9741C247.653 28.5596 248.309 26.9732 248.724 25.2149C249.137 23.4581 249.378 21.6922 249.448 19.9189C249.516 18.1455 249.463 16.5515 249.291 15.1355C249.154 14.1704 249.309 13.3862 249.755 12.783C250.202 12.1798 250.794 11.7848 251.535 11.5947C252.278 11.4062 253.046 11.4168 253.839 11.6249C254.631 11.83 255.329 12.2266 255.932 12.8132C256.535 13.3968 256.905 14.1704 257.042 15.1355Z" fill="#151E1D"/>
<path d="M283.254 22.9408C285.561 22.9092 287.335 23.4626 288.574 24.5996C289.817 25.7336 290.385 27.4919 290.281 29.8715C290.212 31.939 289.687 33.9115 288.707 35.7889C287.725 37.6679 286.391 39.1909 284.702 40.3611C282.839 41.6731 280.814 42.3622 278.628 42.4301C276.44 42.4995 274.294 42.2235 272.192 41.6037C269.642 40.8799 267.607 39.7519 266.087 38.2198C264.57 36.6846 263.53 34.8826 262.963 32.8151C262.396 30.7492 262.222 28.5792 262.444 26.3067C262.669 24.0311 263.237 21.7736 264.151 19.5328C265.063 17.2934 266.244 15.2259 267.692 13.3319C269.139 11.4349 270.801 9.8394 272.68 8.54857C274.558 7.25471 276.59 6.41778 278.778 6.03927C280.97 5.66227 283.219 5.88545 285.528 6.70882C286.493 7.01946 287.182 7.52766 287.597 8.2349C288.01 8.94366 288.208 9.70369 288.188 10.515C288.172 11.3233 287.973 12.0637 287.591 12.7347C287.213 13.4073 286.67 13.8974 285.963 14.2065C285.258 14.5172 284.442 14.4991 283.514 14.1523C281.856 13.6018 280.262 13.6124 278.73 14.1824C277.198 14.7494 275.802 15.707 274.544 17.0536C273.285 18.3972 272.259 19.9218 271.468 21.6259C270.675 23.3314 270.192 25.055 270.02 26.7952C269.847 28.537 270.088 30.0796 270.744 31.4218C271.121 32.215 271.733 32.886 272.578 33.4364C273.422 33.9884 274.369 34.3759 275.419 34.6006C276.471 34.8268 277.489 34.86 278.471 34.7031C279.451 34.5463 280.27 34.1422 280.926 33.4907C281.408 32.972 281.803 32.3507 282.114 31.6268C282.15 31.5228 282.176 31.3946 282.193 31.2408C282.208 31.084 282.235 30.9196 282.271 30.7462C282.235 30.7462 282.19 30.7462 282.138 30.7462C282.09 30.7462 282.048 30.7462 282.012 30.7462C281.874 30.7462 281.746 30.7462 281.626 30.7462C281.505 30.7462 281.375 30.7462 281.239 30.7462C280.651 31.3705 279.891 31.7595 278.959 31.9164C278.03 32.0702 277.126 31.9496 276.245 31.5545C275.367 31.1564 274.74 30.4235 274.363 29.3528C273.844 27.8689 273.826 26.6972 274.309 25.8362C274.791 24.9766 275.557 24.3493 276.607 23.9542C277.66 23.5561 278.798 23.2967 280.021 23.1761C281.243 23.0554 282.321 22.977 283.254 22.9408Z" fill="#151E1D"/>
<path d="M323.104 15.1355C323.313 16.9979 323.331 19.0231 323.159 21.2097C322.985 23.3978 322.589 25.5874 321.97 27.7785C321.351 29.9666 320.481 31.9994 319.359 33.8768C318.24 35.7558 316.861 37.3422 315.221 38.636C313.583 39.9269 311.681 40.7623 309.514 41.1393C307.237 41.5178 305.298 41.3444 303.694 40.6205C302.092 39.8967 300.765 38.7778 299.713 37.2607C298.663 35.7452 297.828 34.0065 297.209 32.0431C296.59 30.0767 296.149 28.0258 295.888 25.8905C295.63 23.7522 295.493 21.7088 295.478 19.762C295.462 17.8167 295.504 16.1036 295.605 14.6228C295.673 13.6577 295.991 12.8735 296.558 12.2703C297.128 11.6671 297.802 11.263 298.579 11.0579C299.354 10.8498 300.12 10.8498 300.877 11.0579C301.637 11.263 302.258 11.6596 302.741 12.2462C303.223 12.8298 303.43 13.6215 303.362 14.6228C303.326 15.1385 303.29 16.0162 303.253 17.2588C303.22 18.4983 303.238 19.9008 303.308 21.4691C303.375 23.0374 303.522 24.6147 303.748 26.1981C303.973 27.783 304.3 29.196 304.731 30.4386C305.161 31.6782 305.738 32.5739 306.462 33.1288C307.186 33.6808 308.098 33.6959 309.201 33.1771C310.716 32.4533 311.923 31.3856 312.82 29.9741C313.716 28.5596 314.372 26.9732 314.786 25.2149C315.2 23.4581 315.441 21.6922 315.51 19.9189C315.578 18.1455 315.525 16.5515 315.353 15.1355C315.216 14.1704 315.372 13.3862 315.818 12.783C316.264 12.1798 316.857 11.7848 317.597 11.5947C318.341 11.4062 319.108 11.4168 319.902 11.6249C320.693 11.83 321.391 12.2266 321.995 12.8132C322.598 13.3968 322.967 14.1704 323.104 15.1355Z" fill="#151E1D"/>
<path d="M345.13 22.1686C347.06 23.4761 348.583 25.1816 349.702 27.2838C350.824 29.3874 351.264 31.6268 351.023 34.0034C350.782 36.0708 350.073 37.8457 348.9 39.3296C347.73 40.8134 346.29 41.9701 344.581 42.798C342.876 43.6228 341.066 44.0797 339.152 44.1672C337.242 44.2517 335.408 43.932 333.651 43.2081C331.893 42.4843 330.412 41.3126 329.206 39.6915C328.586 38.8998 328.326 38.1337 328.427 37.3933C328.532 36.6499 328.851 36.003 329.387 35.451C329.92 34.9006 330.584 34.5146 331.377 34.2929C332.169 34.0682 332.97 34.0607 333.778 34.2688C334.589 34.4739 335.289 34.973 335.877 35.7647C336.15 36.1432 336.664 36.3588 337.421 36.4101C338.181 36.4629 338.938 36.4282 339.695 36.3076C340.455 36.1869 340.989 36.0572 341.3 35.9155C342.024 35.5747 342.566 35.068 342.928 34.3954C343.29 33.7199 343.42 32.987 343.32 32.1938C343.215 31.2287 342.87 30.437 342.283 29.8172C341.699 29.1989 341.001 28.656 340.19 28.1885C339.382 27.7226 338.563 27.2747 337.735 26.8434C336.905 26.4136 336.182 25.9416 335.563 25.4259C333.841 23.9104 332.79 22.2652 332.409 20.4918C332.03 18.7154 332.158 16.9389 332.795 15.1655C333.434 13.3891 334.425 11.7756 335.768 10.3279C337.115 8.88024 338.649 7.76131 340.371 6.96811C341.303 6.52174 342.165 6.40412 342.958 6.61222C343.75 6.81731 344.43 7.20335 344.997 7.77036C345.567 8.33736 345.956 9.00088 346.161 9.76091C346.367 10.5179 346.33 11.2493 346.053 11.9565C345.778 12.6653 345.193 13.2263 344.298 13.6395C343.714 13.9139 343.034 14.2909 342.259 14.7735C341.482 15.256 340.844 15.8336 340.347 16.5046C339.848 17.1772 339.753 17.9267 340.063 18.7546C340.235 19.3065 340.639 19.78 341.276 20.1781C341.915 20.5732 342.597 20.9261 343.32 21.2397C344.044 21.5504 344.647 21.8595 345.13 22.1686Z" fill="#151E1D"/>
<path d="M377.229 8.98284C378.194 9.12459 378.968 9.48802 379.552 10.0746C380.138 10.6582 380.535 11.3459 380.74 12.1376C380.948 12.9308 380.948 13.7059 380.74 14.4659C380.535 15.2229 380.138 15.8171 379.552 16.2514C378.968 16.6826 378.194 16.8455 377.229 16.7399C375.644 16.5318 373.99 16.3765 372.265 16.2755C372.024 20.0319 371.79 23.7883 371.565 27.5432C371.344 31.2996 371.113 35.0741 370.872 38.8652C370.835 39.8303 370.531 40.6144 369.961 41.2176C369.394 41.8208 368.721 42.228 367.946 42.4361C367.174 42.6412 366.408 42.6412 365.648 42.4361C364.891 42.228 364.273 41.8299 363.79 41.2418C363.307 40.6552 363.081 39.8635 363.114 38.8652C363.356 35.0741 363.588 31.292 363.814 27.5191C364.039 23.7431 364.273 19.961 364.514 16.1729C362.961 16.2423 361.445 16.3449 359.966 16.4806C358.964 16.5861 358.162 16.4052 357.559 15.9377C356.956 15.4717 356.559 14.861 356.371 14.104C356.181 13.344 356.19 12.5779 356.395 11.8058C356.603 11.0307 357 10.3491 357.583 9.76097C358.17 9.17436 358.964 8.83054 359.966 8.7295C365.72 8.14289 371.475 8.22734 377.229 8.98284Z" fill="#151E1D"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 17 KiB

@@ -0,0 +1,5 @@
<svg width="56" height="56" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.9463 3.91835C12.1758 4.04929 4.66515 5.07688 4.7608 14.767C4.85646 24.4571 -0.402086 49.2839 11.5555 51.3106C23.5131 53.3373 45.9213 52.267 48.8613 48.2136C51.8013 44.1601 53.4331 17.7524 51.4147 11.1855C48.8613 2.87994 33.773 3.45665 16.9469 3.91835H16.9463Z" stroke="#151E1D" stroke-width="5.69375" stroke-miterlimit="10"/>
<path d="M29.441 16.464C23.628 15.585 13.3268 19.7905 16.674 29.8933C20.0207 39.9962 26.4666 41.5976 34.3994 37.4087C42.5303 33.1156 41.6139 18.3046 29.4415 16.464H29.441Z" stroke="#151E1D" stroke-width="5.69375" stroke-miterlimit="10"/>
<path d="M42.4426 13.8809L42.4796 14.0073" stroke="#151E1D" stroke-width="5.69375" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 812 B

@@ -0,0 +1,59 @@
<svg width="169" height="141" viewBox="0 0 169 141" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2028_4438)">
<path d="M137.304 89.3376C136.33 89.8793 135.251 90.2649 134.476 91.0634C134.349 91.2152 134.196 91.2906 134.034 91.3199C133.511 91.3767 133.086 91.2978 132.532 91.342C132.451 91.3446 132.384 91.3009 132.35 91.2311C132.071 90.6591 132.251 89.9811 132.612 89.5009C132.839 89.2076 133.159 88.9911 133.513 88.9359C134.26 88.8703 135.095 88.743 135.835 88.8281C136.051 88.8694 137.736 88.9869 137.304 89.3376Z" fill="#151E1D"/>
<path d="M148.061 95.3948C146.994 95.7249 145.841 95.7796 144.917 96.4021C144.763 96.5163 144.625 96.5454 144.477 96.5043C143.989 96.3372 143.75 96.0458 143.069 95.924C142.966 95.9121 142.879 95.8437 142.846 95.7544C142.554 95.0071 143.069 94.1336 143.693 93.7856C144.451 93.3375 145.376 93.8893 146.143 94.036C146.414 94.1332 148.688 95.1041 148.064 95.3892L148.061 95.3948Z" fill="#151E1D"/>
<path d="M149.963 107.471C149.191 107.186 148.292 106.663 147.489 106.899C147.357 106.931 147.28 106.933 147.208 106.818C146.994 106.444 146.745 105.967 146.449 105.567C146.389 105.488 146.362 105.383 146.391 105.291C146.615 104.319 147.796 103.662 148.579 104.32C149.43 105.138 150.121 106.114 150.196 107.316C150.207 107.439 150.062 107.527 149.958 107.468L149.963 107.471Z" fill="#151E1D"/>
<path d="M29.2616 84.014C29.8515 82.8358 30.9772 82.1227 32.1923 81.6747C32.8009 81.4553 33.4483 81.8102 33.7917 82.3446C33.9937 82.5957 34.0845 83.005 34.0766 83.4255C34.0749 83.5317 34.0111 83.6174 33.9247 83.6644C33.5207 83.9178 33.1875 84.1419 32.8296 84.3856C32.6937 84.4719 32.5818 84.558 32.4413 84.5538C32.2274 84.4977 32.0433 84.2861 31.8148 84.2523C31.0244 84.1104 30.2616 84.1456 29.4404 84.246C29.3159 84.2722 29.2055 84.1365 29.2682 84.0223L29.2616 84.014Z" fill="#151E1D"/>
<path d="M20.1027 91.0944C20.0448 89.9947 20.9954 89.1354 21.8924 88.6628C22.318 88.4434 22.7636 87.9984 23.29 88.1669C23.6782 88.273 24.0607 88.5585 24.3192 88.9181C24.4322 89.135 24.7416 89.4165 24.6096 89.7042C24.3299 90.2058 23.9387 90.7261 23.4794 90.9912C23.1583 91.2271 22.7994 90.8934 22.4461 90.8618C21.7341 90.7948 21.0119 90.7252 20.3788 91.1712C20.2882 91.2672 20.1208 91.2495 20.1046 91.1037L20.1027 91.0944Z" fill="#151E1D"/>
<path d="M18.077 102.871C17.4011 101.877 17.2472 100.504 17.5637 99.33C17.7158 98.7252 17.7803 97.9559 18.368 97.5917C18.9718 97.1664 19.8107 97.0815 20.512 97.3721C20.6076 97.4146 20.6648 97.5036 20.6668 97.6043C20.6842 98.3516 20.6763 98.8635 20.501 99.4491C20.387 99.8918 19.8715 99.9809 19.6067 100.301C19.3096 100.629 19.0536 100.99 18.8487 101.39C18.6237 101.808 18.4685 102.283 18.3463 102.824C18.3254 102.953 18.14 102.988 18.0697 102.882L18.077 102.871Z" fill="#151E1D"/>
<path d="M54.1769 114.127C66.5165 133.214 86.7624 142.855 100.422 114.245C100.422 114.245 103.074 114.246 104.826 113.17C107.036 111.82 108.273 110.448 109.006 110.52C109.738 110.593 112.391 134.753 114.091 141.077C114.934 144.216 106.229 145.603 106.229 145.603C106.229 145.603 111.296 155.811 112.113 164.53C112.662 170.417 110.575 178.119 104.94 182.89C97.4778 189.205 85.2841 173.972 79.597 173.435C70.5763 172.587 59.6078 178.595 53.1569 177.423C33.4424 173.837 27.4033 173.205 26.9797 167.022C26.6063 161.628 26.2413 151.716 35.1648 139.53C36.2606 138.033 29.5072 132.95 31.1694 130.324C33.4848 126.66 38.2022 122.332 54.1815 114.126L54.1769 114.127Z" fill="#ED7334" stroke="#ED7334" stroke-width="0.52448" stroke-miterlimit="10"/>
<path d="M30.7847 116.45C30.7847 116.45 50.0121 119.918 50.7563 101.383C51.4543 83.9798 38.4248 82.0832 35.6333 82.2224C33.4042 82.3347 32.355 83.7779 33.8754 86.1106C35.1473 88.0623 27.4779 83.3157 23.2727 89.0951C21.4213 91.6455 29.3057 92.8811 29.3057 92.8811C29.3057 92.8811 22.8744 93.6121 20.4894 95.6393C19.072 96.8422 18.6457 99.3483 21.5057 101.207C23.8088 102.706 26.3735 98.4651 29.2575 101.262C31.6519 103.584 25.0202 106.591 30.7801 116.451L30.7847 116.45Z" fill="#F8F6EB" stroke="#F8F6EB" stroke-width="0.52448" stroke-miterlimit="10"/>
<path d="M114.923 106.11C114.923 106.11 117.397 121.981 128.266 117.607C137.861 113.746 138.987 109.187 140.367 109.292C146.608 109.765 150.815 101.314 141.047 102.198C140.38 102.256 142.599 100.423 143.351 98.6425C144.264 96.4818 144.359 91.552 134.929 94.3911C132.914 94.9975 135.942 89.4079 129.567 91.0751C124.174 92.4833 118.796 101.022 114.928 106.114L114.923 106.11Z" fill="#F8F6EB" stroke="#F8F6EB" stroke-width="0.52448" stroke-miterlimit="10"/>
<path d="M83.1092 130.583C83.1092 130.583 90.48 129.558 95.8483 121.229C98.9006 116.491 100.706 115.62 101.557 108.13C101.847 105.57 110.863 100.854 109 88.1676C107.601 78.6275 101.819 83.7276 101.819 83.7276C101.819 83.7276 96.036 78.3149 91.2497 79.475C86.7472 80.5659 95.6371 72.9594 103.731 75.5372C105.522 76.1091 94.8177 66.8403 83.1046 75.305C79.4938 77.9124 85.5806 65.3066 98.3313 60.9994C104.305 58.9832 77.4894 58.1408 68.5228 76.7865C67.1367 79.6724 67.3715 71.0116 73.537 64.4591C77.5035 60.2462 61.3851 71.8897 62.3629 81.7396C62.6079 84.2105 55.7112 82.2233 52.9271 92.1757C52.8593 92.4258 53.7905 90.7187 55.1247 89.6921C56.1939 88.8705 58.2483 88.8429 58.2483 88.8429C58.2483 88.8429 52.3904 93.9733 54.3635 105.742C54.542 106.797 53.7297 107.627 54.2512 108.711C55.1377 110.547 55.2898 110.606 56.1745 114.815C57.0592 119.024 56.6057 118.286 61.5522 124.048C64.8333 127.864 70.5523 127.316 73.9635 129.232C76.3212 130.556 78.7657 129.224 83.0963 130.591L83.1092 130.583Z" fill="#F8F6EB" stroke="#F8F6EB" stroke-width="0.52448" stroke-miterlimit="10"/>
<path d="M106.707 85.6853C109.11 85.7385 111.365 79.2234 108.15 78.2146C103.986 76.9093 102.61 85.5933 106.707 85.6853Z" fill="#DED59F" stroke="#DED59F" stroke-width="0.52448" stroke-miterlimit="10"/>
<path d="M86.0499 121.307C86.0499 121.307 90.2893 119.309 92.042 114.319" stroke="#151E1D" stroke-width="1.04896" stroke-miterlimit="10" stroke-linecap="round"/>
<path d="M68.1541 103.792C68.1541 103.792 73.7362 114.18 92.9702 107.466C94.9838 106.763 94.9935 109.077 96.4945 110.08" stroke="#151E1D" stroke-width="1.04896" stroke-miterlimit="10" stroke-linecap="round"/>
<path d="M59.1202 110.74C54.6316 111.919 53.7106 108.82 54.4148 106.857C54.9912 105.249 57.2848 105.397 57.2848 105.397L77.8246 87.8809" stroke="#151E1D" stroke-width="1.04896" stroke-miterlimit="10" stroke-linecap="round"/>
<path d="M54.4907 106.231C52.4196 98.0499 56.2071 90.5581 58.1038 89.139C58.9604 88.4968 56.6993 88.64 55.1425 89.9588C53.0461 91.7328 52.0101 93.308 52.7531 91.4382C55.982 83.3273 62.1581 83.3287 62.1558 82.3328C62.1172 69.7149 75.7873 62.591 74.4067 63.6947C68.9011 68.0964 66.5425 80.2795 68.14 76.8871C76.7742 58.5616 102.642 59.3864 99.3446 60.4599C85.0488 65.1112 79.6377 77.5653 82.8202 75.3705C95.7878 66.4159 103.993 75.6565 103.993 75.6565C103.993 75.6565 95.8552 74.4307 90.6622 78.9688C89.857 79.6724 94.7358 77.4868 101.387 83.4102C102.086 84.0334 103.271 83.0527 104.308 80.9139C104.756 79.9967 106.151 77.4267 108.149 78.21C111.213 79.4143 108.648 84.5651 108.648 84.5651C108.648 84.5651 113.182 96.7001 101.914 107.146C100.39 108.554 104.997 110.454 93.5961 124.596" stroke="#151E1D" stroke-width="1.04896" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M28.2161 170.34C28.1411 169.754 27.4837 169.695 27.2756 168.728C25.0477 158.404 29.1192 147.18 35.3239 138.867" stroke="#151E1D" stroke-width="1.04896" stroke-miterlimit="10" stroke-linecap="round"/>
<path d="M38.6129 143.633L30.4367 131.758C30.4367 131.758 31.1177 129.411 33.1566 127.889" stroke="#151E1D" stroke-width="1.04896" stroke-miterlimit="10" stroke-linecap="round"/>
<path d="M55.1467 110.545C55.3047 111.296 56.872 117.287 56.8541 118.668" stroke="#151E1D" stroke-width="1.04896" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M64.6702 127.293C62.2832 125.806 58.4334 121.095 56.8538 118.664C55.823 117.08 53.3904 112.744 53.3904 112.744L53.7752 113.636C53.7752 113.636 56.2965 120.249 48.8496 127.552C42.9153 133.372 31.3485 132.16 30.585 131.55" stroke="#151E1D" stroke-width="1.04896" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M114.845 105.641C114.845 105.641 124.157 91.1149 131.072 90.576C134.548 90.3073 135.656 94.1323 132.9 97.3735" stroke="#151E1D" stroke-width="1.04896" stroke-miterlimit="10" stroke-linecap="round"/>
<path d="M125.532 101.459C125.532 101.459 132.559 97.9986 134.109 100.47C136.818 104.795 130.018 109.2 130.018 109.2" stroke="#151E1D" stroke-width="1.04896" stroke-miterlimit="10" stroke-linecap="round"/>
<path d="M134.474 94.5781C134.474 94.5781 142.153 91.1059 143.612 95.5982C145.072 100.091 138.073 103.757 138.073 103.757" stroke="#151E1D" stroke-width="1.04896" stroke-miterlimit="10" stroke-linecap="round"/>
<path d="M140.514 102.045C140.514 102.045 145.924 101.172 146.996 104.37C147.654 106.33 145.786 108.735 141.014 109.343C137.442 109.8 139.679 113.114 128.074 118.176" stroke="#151E1D" stroke-width="1.04896" stroke-miterlimit="10" stroke-linecap="round"/>
<path d="M30.7853 116.451C30.7853 116.451 27.6415 111.273 28.7754 106.592C29.571 103.301 29.7405 101.749 28.5146 100.909C26.8507 99.7715 24.86 100.835 23.6646 101.216C20.2937 102.286 18.3666 98.3394 19.9188 96.1744C21.9969 93.2826 25.2677 93.4086 27.7189 93.0474C30.3814 92.661 32.8862 96.173 32.8862 96.173C32.8862 96.173 31.5503 93.3443 25.2395 91.8549C21.5623 90.9864 23.7617 88.1221 24.5909 87.4183C26.484 85.8218 32.1718 85.8585 33.1775 86.2921C35.856 87.4475 36.2183 91.9876 35.9659 90.5581C35.2285 86.3857 31.609 84.7157 33.1712 83.1021C34.6263 81.5976 41.3721 80.8054 46.7264 86.4237C52.0444 92.0063 50.4737 104.914 50.4737 104.914" stroke="#151E1D" stroke-width="1.04896" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M45.7924 96.2225C45.7924 96.2225 44.5207 89.0047 40.919 91.7017C37.0092 94.6271 40.9396 105.219 40.9396 105.219" stroke="#151E1D" stroke-width="1.04896" stroke-miterlimit="10" stroke-linecap="round"/>
<path d="M56.0894 108.066C56.0894 108.066 57.7941 107.611 58.7352 108.905" stroke="#151E1D" stroke-width="1.04896" stroke-miterlimit="10" stroke-linecap="round"/>
<path d="M104.8 84.5786C105.265 84.2017 105.958 83.4928 106.305 82.4186" stroke="#151E1D" stroke-width="1.04896" stroke-miterlimit="10" stroke-linecap="round"/>
<path d="M106.474 85.3054C107.02 84.815 107.587 84.0316 107.776 83.2361" stroke="#151E1D" stroke-width="1.04896" stroke-miterlimit="10" stroke-linecap="round"/>
<path d="M109.595 81.5354C109.595 81.5354 109.08 85.6147 106.707 85.685C105.45 85.7232 103.481 84.1682 104.448 80.6388" stroke="#151E1D" stroke-width="1.04896" stroke-miterlimit="10" stroke-linecap="round"/>
<path d="M39.485 88.2243C40.4314 88.4442 41.2117 89.0648 41.8221 89.7933C42.1083 90.1471 42.6259 90.4571 42.5728 90.9833C42.5455 91.5859 42.1409 92.1571 41.6373 92.5325C41.5585 92.5924 41.4561 92.6091 41.37 92.5887C40.8225 92.4343 40.2306 92.1832 39.8665 91.8025C39.7322 91.667 39.6982 91.5057 39.7167 91.3189C39.8587 90.3456 39.612 89.401 39.3229 88.4606C39.2608 88.3485 39.3555 88.2034 39.486 88.2289L39.485 88.2243Z" fill="#151E1D"/>
<path d="M103.821 183.911C103.821 183.911 118.711 174.827 108.746 151.761C105.557 144.379 107.158 141.005 104.184 130.794" stroke="#151E1D" stroke-width="1.04896" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M106.925 145.963C110.705 145.169 115.269 142.125 114.197 141.489C108.352 138.024 104.846 125.081 108.963 112.904C111.113 106.551 106.022 114.803 100.416 114.243" stroke="#151E1D" stroke-width="1.04896" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M114.357 141.725C114.357 141.725 114.327 137 113.571 134.506" stroke="#151E1D" stroke-width="1.04896" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M136.734 99.0369C136.234 99.7486 135.513 100.348 135.293 101.179C135.261 101.325 135.194 101.397 135.072 101.408C134.607 101.419 134.259 101.3 133.626 101.38C133.532 101.39 133.44 101.361 133.37 101.304C133.086 101.055 132.905 100.718 132.867 100.355C132.79 99.8275 132.986 99.1798 133.52 99.0194C134.126 98.8777 134.809 98.6474 135.431 98.6515C135.671 98.6731 137.023 98.6008 136.735 99.0415L136.734 99.0369Z" fill="#151E1D"/>
<path d="M96.4314 85.5777C96.4314 85.5777 100.799 84.8282 102.764 83.1636" stroke="#151E1D" stroke-width="1.04896" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M94.2051 85.2422C94.2051 85.2422 97.8903 84.4772 102.21 83.5692" stroke="#151E1D" stroke-width="1.04896" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M33.701 131.815C33.701 131.815 51.0873 132.752 53.8314 117.297C54.0635 115.996 54.3576 112.633 51.5845 110.409C50.4783 109.52 51.5236 108.241 51.6394 106.961C51.7643 105.563 50.8653 104.423 49.3075 104.5C46.9408 104.622 48.6307 106.525 48.0521 107.436C47.0421 109.02 44.9353 108.134 44.9367 109.217C44.9404 109.876 44.6275 110.196 43.8398 110.227C42.9717 110.265 39.8886 107.895 38.5057 108.942C37.0227 110.062 37.9089 111.873 36.4545 112.511C34.5374 113.352 33.965 110.469 32.3488 111.757C30.7327 113.045 32.1741 114.292 30.5687 116.868C29.6728 118.308 29.4891 120.113 31.8006 124.354C33.9784 128.349 34.3825 130.454 33.6964 131.816L33.701 131.815Z" fill="#151E1D" stroke="#151E1D" stroke-width="1.04896" stroke-miterlimit="10"/>
<path d="M109.881 110.775C110.379 110.232 112.641 105.651 113.812 105.404C115.471 105.056 114.709 107.728 115.425 108.315C116.539 109.221 116.96 108.131 118.362 108.299C120.185 108.517 117.691 110.645 118.485 111.426C119.28 112.207 121.063 111.231 121.462 112.807C121.746 113.932 119.758 114.961 120.951 116.125C122.144 117.29 124.07 114.796 125.134 115.804C126.198 116.813 124.176 117.522 125.355 118.117C126.26 118.572 127.798 116.616 128.626 117.236C130.433 118.594 126.39 121.62 125.761 122.383C125.132 123.146 125.825 125.32 124.224 125.97C122.623 126.619 121.512 126.877 121.556 128.576C121.63 131.242 120.602 131.183 119.528 131.683C118.541 132.141 117.758 131.873 116.884 133.943C116.359 135.18 115.448 137.899 111.949 138.634C111.166 138.799 107.812 129.737 107.764 123.095C107.711 116.044 109.443 111.252 109.877 110.776L109.881 110.775Z" fill="#151E1D" stroke="#151E1D" stroke-width="1.04896" stroke-miterlimit="10"/>
<path d="M62.6275 124.586C62.3263 125.145 64.8845 127.881 67.2954 129.16C69.7063 130.439 82.6698 136.167 92.2947 127.958C95.2422 125.447 92.6972 124.056 91.7523 124.645C90.8073 125.233 91.8135 125.989 90.6936 126.822C89.8525 127.446 88.5666 126.137 87.8302 126.413C86.3399 126.971 86.6581 128.989 85.3527 129.465C81.9567 130.713 80.8581 127.777 78.1885 128.29C76.6062 128.594 76.2327 129.702 74.7648 129.154C73.7625 128.782 72.6546 122.825 68.7784 125.224C66.1631 126.842 65.5485 119.178 62.6275 124.586Z" fill="#151E1D" stroke="#151E1D" stroke-width="1.04896" stroke-miterlimit="10"/>
<path d="M70.268 106.595C70.268 106.595 68.7552 110.139 72.0479 112.773C74.5071 114.739 78.6024 115.193 79.812 113.138C80.1637 112.544 78.2071 109.923 77.4619 109.767C72.7211 108.785 70.268 106.595 70.268 106.595Z" fill="#151E1D" stroke="#151E1D" stroke-width="1.04896" stroke-miterlimit="10"/>
<path d="M78.8344 86.8941C78.8344 86.8941 76.7985 88.1548 77.8655 92.0172C78.3655 93.8231 80.5619 99.051 86.8622 98.338C95.2131 97.3961 96.6322 92.3083 96.1292 86.1371C95.7947 82.0726 82.6278 84.5371 78.8344 86.8941Z" fill="#151E1D" stroke="#151E1D" stroke-width="1.04896" stroke-miterlimit="10"/>
<path d="M108.815 84.1969C108.681 82.6461 115.845 82.5122 116.194 83.7385C117.661 88.8645 115.415 93.1299 110.237 93.7562C109.575 93.8375 110.339 93.393 109.978 88.7707C109.883 87.5632 108.969 86.0275 108.815 84.1969Z" fill="#151E1D" stroke="#151E1D" stroke-width="1.04896" stroke-miterlimit="10"/>
<path d="M33.4449 39.6636C36.2624 38.3917 34.64 35.0333 33.2446 33.4009C32.2298 32.2158 30.7676 30.6301 27.2857 30.4231C19.621 29.9691 17.648 34.8297 17.9885 39.1595C18.99 51.9752 30.7809 47.7022 32.0695 60.4579C32.4134 63.8596 29.5958 66.9075 27.7464 66.9075C25.897 66.9075 24.5083 65.0414 25.029 62.6578C25.7802 59.216 23.9708 55.5973 19.4908 55.604C16.1391 55.6107 15.0709 59.0157 15.0074 61.2057C14.9507 63.1252 15.0074 73.9346 23.3899 76.869C34.7969 80.8616 40.3986 69.8252 39.7643 60.0573C39.2569 52.239 33.258 48.1529 31.1315 47.0713C26.6147 44.7745 25.5064 43.4425 24.9189 41.72C24.3781 40.1343 25.4163 38.5118 26.5947 38.1914C28.6511 37.6339 30.6608 40.9221 33.4483 39.6636H33.4449Z" fill="#720328" stroke="#720328" stroke-width="0.371687" stroke-miterlimit="10"/>
<path d="M35.7849 22.0611C37.6477 21.33 41.006 21.2766 44.9419 19.3537C50.8674 16.4594 51.7654 12.7171 55.2673 14.6033C57.6709 15.8952 59.3233 25.3426 55.9216 25.5763C53.4479 25.7466 48.8744 24.8018 50.0061 28.514C51.3014 32.7637 52.5165 39.1632 53.0206 42.8888C54.6397 54.9334 54.2792 59.2932 49.3251 60.1245C46.778 60.5518 44.0272 57.327 44.4879 54.3325C46.3206 42.4348 46.6411 41.2697 44.4478 29.7959C43.68 25.7799 40.512 31.0912 37.4908 30.3935C34.71 29.7525 30.3969 24.1809 35.7849 22.0611Z" fill="#720328" stroke="#720328" stroke-width="0.371687" stroke-miterlimit="10"/>
<path d="M62.9724 12.3799C60.1782 13.6552 60.1548 17.6778 61.2364 20.719C62.7754 25.0488 64.1107 26.925 63.3062 35.9117C62.5017 44.9218 65.2224 49.5687 69.195 51.8421C74.0789 54.6329 81.3898 53.3176 85.0386 45.6028C92.6599 29.4921 84.5645 24.4112 85.3924 14.0925C85.6295 11.1414 84.9785 8.54422 82.2645 8.07018C79.7407 7.62953 77.6609 9.3087 77.247 11.8491C76.3824 17.1537 78.0649 18.8729 80.0545 24.0139C82.8119 31.1446 85.0386 42.0775 76.2121 43.5197C70.4902 44.4544 67.9264 37.1302 70.8842 28.3437C74.109 18.7628 70.4836 8.9515 62.9724 12.3833V12.3799Z" fill="#720328" stroke="#720328" stroke-width="0.371687" stroke-miterlimit="10"/>
<path d="M102.334 51.6118C95.5106 53.6916 89.111 50.4868 90.8036 44.4411C95.8144 26.5211 91.8685 28.7077 90.6667 16.0488C90.0324 9.37889 92.1322 8.78801 94.7761 8.78467C98.8055 8.78467 105.379 15.1675 108.717 17.6112C113.194 20.886 118.351 25.2525 116.822 34.7801C115.874 40.6822 112.823 48.4137 102.337 51.6118H102.334ZM96.3051 20.886C97.2098 24.7284 100.461 30.5137 98.6119 37.1269C97.2064 42.1544 103.249 41.5702 107.318 35.6013C111.852 28.9514 107.011 23.0927 102.588 20.4955C99.2361 18.5292 95.3904 16.9836 96.3084 20.8827L96.3051 20.886Z" fill="#720328"/>
<path d="M98.2181 52.4125C95.6943 52.4125 93.4476 51.6648 91.9921 50.2426C90.4799 48.7638 89.9992 46.6873 90.6401 44.3939C94.0452 32.2124 93.3141 29.4149 92.1056 24.7847C91.5314 22.5881 90.8805 20.101 90.4966 16.065C90.1861 12.7835 90.4966 10.7471 91.488 9.66216C92.2726 8.79754 93.3708 8.62061 94.7763 8.62061C97.9377 8.62061 102.638 12.4797 106.07 15.2939C107.131 16.1652 108.049 16.9196 108.814 17.4771C113.558 20.949 118.492 25.4357 116.983 34.8063C115.577 43.5393 110.667 49.2445 102.381 51.7716C100.965 52.2022 99.5534 52.4125 98.2181 52.4125ZM94.7763 8.9511C93.461 8.9511 92.4395 9.11134 91.7351 9.88249C90.8271 10.8806 90.5333 12.8903 90.8304 16.0317C91.211 20.0443 91.8586 22.518 92.4294 24.7012C93.6546 29.3882 94.3924 32.2191 90.9639 44.4874C90.3497 46.6873 90.787 48.5969 92.2291 50.0056C94.369 52.0954 98.3182 52.6629 102.287 51.4544C110.44 48.9674 115.273 43.349 116.656 34.7562C118.131 25.5725 113.494 21.3195 108.617 17.7475C107.842 17.1834 106.924 16.4256 105.856 15.5509C102.464 12.7668 97.8175 8.9511 94.7763 8.9511ZM100.391 40.6917C99.9974 40.6917 99.6402 40.6216 99.3397 40.4814C98.3049 40.0007 97.9811 38.7622 98.4484 37.083C99.8438 32.0956 98.3182 27.6122 97.0931 24.0069C96.7158 22.8986 96.3586 21.8503 96.1416 20.9256C95.8646 19.7572 95.9948 18.966 96.5222 18.5754C97.4369 17.9011 99.5067 18.4987 102.668 20.3548C106.01 22.3177 108.413 25.2955 109.091 28.3233C109.648 30.807 109.098 33.2874 107.452 35.701C105.182 39.0293 102.314 40.6951 100.391 40.6951V40.6917ZM97.4937 18.6389C97.1665 18.6389 96.9095 18.7056 96.7225 18.8425C96.3119 19.1463 96.2251 19.8206 96.4655 20.8455C96.6791 21.7535 97.033 22.7951 97.4069 23.8967C98.6487 27.5455 100.191 32.0856 98.7689 37.1698C98.3416 38.6954 98.5953 39.7637 99.48 40.1743C101.136 40.9454 104.624 39.2529 107.178 35.504C108.767 33.1705 109.301 30.777 108.767 28.3901C108.106 25.4524 105.766 22.5514 102.501 20.6352C100.258 19.3165 98.5319 18.6322 97.4937 18.6322V18.6389Z" fill="#720328"/>
<path d="M124.908 28.3435C128.08 28.3435 130.55 25.0586 128.941 22.6283C127.332 20.198 123.553 20.4116 121.56 22.6283C119.567 24.8449 121.733 28.3435 124.905 28.3435H124.908Z" fill="#720328" stroke="#720328" stroke-width="0.371687" stroke-miterlimit="10"/>
<path d="M123.863 31.5546C122.681 31.9552 120.875 33.4675 120.795 36.6122C120.431 50.4528 116.609 51.067 116.609 55.5304C116.609 61.8431 127.899 62.0334 127.131 53.0033C126.176 41.7465 129.354 41.4727 129.922 37.3867C130.489 33.3006 128.59 29.9489 123.866 31.5513L123.863 31.5546Z" fill="#720328" stroke="#720328" stroke-width="0.371687" stroke-miterlimit="10"/>
<path d="M131.217 63.2359C130.182 50.1965 135.033 39.347 140.141 36.7364C145.248 34.1258 151.634 33.5617 153.921 41.0628C155.697 46.8882 151.998 48.3671 156.862 58.2017C161.165 66.9047 155.76 79.0228 145.996 78.8258C140.471 78.7156 132.062 73.8083 131.221 63.2359H131.217ZM149.181 42.3281C141.86 33.802 137.383 48.9613 138.588 55.8415C139.243 59.5671 141.012 62.842 143.96 63.957C147.902 65.4459 153.26 64.895 150.816 56.3256C148.837 49.3886 153.06 46.8415 149.181 42.3281Z" fill="#720328" stroke="#720328" stroke-width="0.371687" stroke-miterlimit="10"/>
</g>
<defs>
<clipPath id="clip0_2028_4438">
<rect width="169" height="141" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 21 KiB

@@ -0,0 +1,3 @@
<svg width="30" height="25" viewBox="0 0 30 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.50049 19.2085C3.45546 20.0651 8.99546 22.066 15.5157 23.2177M7.70036 5.64159C12.1164 8.59943 16.6618 12.0402 21.5955 16.9299M23.1552 1.50049L27.8393 13.1863" stroke="#FF6915" stroke-width="3" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 332 B

-2
View File
@@ -8,8 +8,6 @@ import {
} from "@/components/general/GenericPageView"; } from "@/components/general/GenericPageView";
import { getSeoMetadata } from "@/lib/seo"; import { getSeoMetadata } from "@/lib/seo";
export const dynamicParams = false;
function getWagtailUrlPath(url: string[]): string { function getWagtailUrlPath(url: string[]): string {
// for the page /foo/bar we need to look for `/home/foo/bar/` // for the page /foo/bar we need to look for `/home/foo/bar/`
return `/home/${url.join("/")}/`; return `/home/${url.join("/")}/`;
+2 -24
View File
@@ -1,36 +1,14 @@
import { Metadata, ResolvingMetadata } from "next"; import { Metadata, ResolvingMetadata } from "next";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getClient } from "@/app/client";
import { import {
NewsPageView, NewsPageView,
loadNewsPageProps, loadNewsPageProps,
} from "@/components/news/NewsPageView"; } from "@/components/news/NewsPageView";
import { graphql } from "@/gql";
import { getSeoMetadata } from "@/lib/seo"; import { getSeoMetadata } from "@/lib/seo";
export async function generateStaticParams() { export async function generateStaticParams() {
const allNewsSlugsQuery = graphql(` // Prerender nothing at build time; render and cache on first request
query allNewsSlugs { return [];
pages(contentType: "news.NewsPage") {
id
slug
}
}
`);
const { data, error } = await getClient().query(allNewsSlugsQuery, {});
if (error) {
throw new Error(error.message);
}
if (!data?.pages) {
throw new Error(
"Failed to generate static params for subpages of /aktuelt"
);
}
return data.pages.map((page: any) => ({
slug: page.slug,
}));
} }
type Params = Promise<{ slug: string }>; type Params = Promise<{ slug: string }>;
+50
View File
@@ -0,0 +1,50 @@
import type { NextRequest } from "next/server";
import { afterEach, describe, expect, it, vi } from "vitest";
vi.mock("next/cache", () => ({ revalidateTag: vi.fn() }));
import { revalidateTag } from "next/cache";
import { CMS_CACHE_TAG } from "@/lib/revalidation";
import { POST } from "./route.ts";
const request = (headers: Record<string, string> = {}) =>
new Request("http://localhost/api/revalidate", {
method: "POST",
headers,
}) as unknown as NextRequest;
afterEach(() => {
vi.unstubAllEnvs();
vi.clearAllMocks();
});
describe("POST /api/revalidate", () => {
it("returns 503 when no secret is configured", async () => {
vi.stubEnv("REVALIDATE_WEBHOOK_SECRET", undefined);
const res = await POST(request({ "x-revalidate-secret": "hemmelig" }));
expect(res.status).toBe(503);
expect(revalidateTag).not.toHaveBeenCalled();
});
it("returns 401 on missing or wrong secret", async () => {
vi.stubEnv("REVALIDATE_WEBHOOK_SECRET", "hemmelig");
expect((await POST(request())).status).toBe(401);
expect(
(await POST(request({ "x-revalidate-secret": "feil" }))).status
).toBe(401);
expect(revalidateTag).not.toHaveBeenCalled();
});
it("purges the cms tag on the correct secret", async () => {
vi.stubEnv("REVALIDATE_WEBHOOK_SECRET", "hemmelig");
const res = await POST(request({ "x-revalidate-secret": "hemmelig" }));
expect(res.status).toBe(200);
expect(await res.json()).toEqual({ revalidated: true, tag: CMS_CACHE_TAG });
expect(revalidateTag).toHaveBeenCalledWith(CMS_CACHE_TAG, { expire: 0 });
});
});
+28
View File
@@ -0,0 +1,28 @@
import { timingSafeEqual } from "node:crypto";
import { revalidateTag } from "next/cache";
import { NextRequest, NextResponse } from "next/server";
import { CMS_CACHE_TAG } from "@/lib/revalidation";
function secretMatches(provided: string, expected: string): boolean {
const a = Buffer.from(provided);
const b = Buffer.from(expected);
return a.length === b.length && timingSafeEqual(a, b);
}
export async function POST(req: NextRequest) {
const secret = process.env.REVALIDATE_WEBHOOK_SECRET;
if (!secret) {
return NextResponse.json(
{ error: "revalidation is not configured" },
{ status: 503 }
);
}
const provided = req.headers.get("x-revalidate-secret") ?? "";
if (!secretMatches(provided, secret)) {
return NextResponse.json({ error: "invalid secret" }, { status: 401 });
}
// expire: 0 purges immediately; the next request renders fresh
revalidateTag(CMS_CACHE_TAG, { expire: 0 });
return NextResponse.json({ revalidated: true, tag: CMS_CACHE_TAG });
}
+6 -1
View File
@@ -1,5 +1,6 @@
import { NextRequest, NextResponse } from "next/server"; import { NextRequest, NextResponse } from "next/server";
import { getClient } from "@/app/client"; import { getClient } from "@/app/client";
import { cachedUntilOsloMidnight } from "@/lib/revalidation";
import { import {
eventsOverviewQuery, eventsOverviewQuery,
getSingularEvents, getSingularEvents,
@@ -45,7 +46,11 @@ export async function GET(req: NextRequest) {
); );
} }
const { data, error } = await getClient().query(eventsOverviewQuery, {}); const { data, error } = await getClient().query(
eventsOverviewQuery,
{},
cachedUntilOsloMidnight()
);
if (error) { if (error) {
throw new Error(error.message); throw new Error(error.message);
} }
+2 -24
View File
@@ -1,36 +1,14 @@
import { Metadata, ResolvingMetadata } from "next"; import { Metadata, ResolvingMetadata } from "next";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getClient } from "@/app/client";
import { import {
EventPageView, EventPageView,
loadEventPageProps, loadEventPageProps,
} from "@/components/events/EventPageView"; } from "@/components/events/EventPageView";
import { graphql } from "@/gql";
import { getSeoMetadata } from "@/lib/seo"; import { getSeoMetadata } from "@/lib/seo";
export async function generateStaticParams() { export async function generateStaticParams() {
const allEventSlugsQuery = graphql(` // Prerender nothing at build time; render and cache on first request
query allEventSlugs { return [];
pages(contentType: "events.EventPage") {
id
slug
}
}
`);
const { data, error } = await getClient().query(allEventSlugsQuery, {});
if (error) {
throw new Error(error.message);
}
if (!data?.pages) {
throw new Error(
"Failed to generate static params for subpages of /arrangementer"
);
}
return data.pages.map((page: any) => ({
slug: page.slug,
}));
} }
type Params = Promise<{ slug: string }>; type Params = Promise<{ slug: string }>;
+6 -1
View File
@@ -18,7 +18,12 @@ export async function generateMetadata(
return getSeoMetadata(data.index as EventIndexFragment, parent); return getSeoMetadata(data.index as EventIndexFragment, parent);
} }
export default async function Page() { export default async function Page({
searchParams,
}: {
searchParams?: Promise<Record<string, string | string[] | undefined>>;
}) {
await searchParams;
const props = await loadEventIndexProps(); const props = await loadEventIndexProps();
return <EventIndexView {...props} />; return <EventIndexView {...props} />;
} }
+5 -2
View File
@@ -3,6 +3,8 @@ import "server-only";
import { cacheExchange, createClient, fetchExchange } from "@urql/core"; import { cacheExchange, createClient, fetchExchange } from "@urql/core";
import { registerUrql } from "@urql/next/rsc"; import { registerUrql } from "@urql/next/rsc";
import { CMS_CACHE_TAG } from "@/lib/revalidation";
const wagtailBaseUrl = process.env.WAGTAIL_BASE_URL; const wagtailBaseUrl = process.env.WAGTAIL_BASE_URL;
if (!wagtailBaseUrl) { if (!wagtailBaseUrl) {
throw new Error("WAGTAIL_BASE_URL is not set"); throw new Error("WAGTAIL_BASE_URL is not set");
@@ -13,8 +15,9 @@ const makeClient = () => {
return createClient({ return createClient({
url: graphqlEndpoint, url: graphqlEndpoint,
exchanges: [cacheExchange, fetchExchange], exchanges: [cacheExchange, fetchExchange],
// requestPolicy: "network-only", // Cache all queries in the Data Cache until revalidateTag(CMS_CACHE_TAG);
fetchOptions: { next: { revalidate: 0 } }, // override per operation with the contexts in @/lib/revalidation
fetchOptions: { cache: "force-cache", next: { tags: [CMS_CACHE_TAG] } },
}); });
}; };
+6 -1
View File
@@ -1,4 +1,5 @@
import { getClient } from "@/app/client"; import { getClient } from "@/app/client";
import { uncached } from "@/lib/revalidation";
import { PreviewBanner } from "@/components/general/PreviewBanner"; import { PreviewBanner } from "@/components/general/PreviewBanner";
import { import {
AssociationIndexView, AssociationIndexView,
@@ -149,7 +150,11 @@ export default async function PreviewRender() {
return <ExpiredPreview />; return <ExpiredPreview />;
} }
const { data, error } = await getClient().query(previewPageQuery, { token }); const { data, error } = await getClient().query(
previewPageQuery,
{ token },
uncached
);
if (error) { if (error) {
throw new Error(error.message); throw new Error(error.message);
} }
+2 -1
View File
@@ -1,4 +1,5 @@
import { getClient } from "@/app/client"; import { getClient } from "@/app/client";
import { uncached } from "@/lib/revalidation";
import { import {
type SearchResult, type SearchResult,
SearchResults, SearchResults,
@@ -65,7 +66,7 @@ export default async function Page({
} }
`); `);
const { data } = await getClient().query(searchQuery, { query }); const { data } = await getClient().query(searchQuery, { query }, uncached);
const all = (data?.results ?? []) as SearchResult[]; const all = (data?.results ?? []) as SearchResult[];
totalCount = all.length; totalCount = all.length;
results = all.slice(0, RESULT_LIMIT); results = all.slice(0, RESULT_LIMIT);
@@ -2,6 +2,7 @@ import { AssociationFragment } from "@/gql/graphql";
import { Image } from "@/components/general/Image"; import { Image } from "@/components/general/Image";
import styles from './associationHeader.module.scss'; import styles from './associationHeader.module.scss';
import { Breadcrumb } from "../general/Breadcrumb"; import { Breadcrumb } from "../general/Breadcrumb";
import { RichText } from "@/components/general/RichText";
import { Icon } from "../general/Icon"; import { Icon } from "../general/Icon";
export const AssociationHeader = ({ export const AssociationHeader = ({
@@ -15,10 +16,9 @@ export const AssociationHeader = ({
<Breadcrumb link="/foreninger" text={association.associationType ? association.associationType : "Foreninger"} /> <Breadcrumb link="/foreninger" text={association.associationType ? association.associationType : "Foreninger"} />
<h1>{association.title}</h1> <h1>{association.title}</h1>
{association.lead && ( {association.lead && (
<div <div className="lead">
className="lead" <RichText content={association.lead} />
dangerouslySetInnerHTML={{ __html: association.lead }} </div>
/>
)} )}
{association.websiteUrl && ( {association.websiteUrl && (
<a className="button" href={association.websiteUrl} target="_blank"> <a className="button" href={association.websiteUrl} target="_blank">
+138 -50
View File
@@ -1,80 +1,168 @@
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import { RichTextBlock } from "./RichTextBlock"; import type {
import { ImageWithTextBlock } from "./ImageWithTextBlock"; AccordionBlockFragment,
import { HorizontalRuleBlock } from "./HorizontalRuleBlock"; ContactEntityBlockFragment,
ContactListBlockFragment,
const ImageSliderBlock = dynamic(() => ContactSectionBlockFragment,
import("./ImageSliderBlock").then((m) => m.ImageSliderBlock) ContactSubsectionBlockFragment,
); EmbedBlockFragment,
import { FeaturedBlock } from "./FeaturedBlock"; FactBoxBlockFragment,
FeaturedBlockFragment,
HorizontalRuleBlockFragment,
ImageSliderBlockFragment,
ImageWithTextBlockFragment,
PageSectionBlockFragment,
RichTextBlockFragment,
ScheduleBlockFragment,
} from "@/gql/graphql";
import { AccordionBlock } from "./AccordionBlock"; import { AccordionBlock } from "./AccordionBlock";
import { ContactEntityBlock } from "./ContactEntityBlock";
import { ContactListBlock } from "./ContactListBlock";
import { ContactSectionBlock, ContactSubsectionBlock } from "./ContactSection";
import { EmbedBlock } from "./EmbedBlock"; import { EmbedBlock } from "./EmbedBlock";
import { FactBoxBlock } from "./FactBoxBlock"; import { FactBoxBlock } from "./FactBoxBlock";
import { PageSectionBlock, PageSectionNavigationBlock } from "./PageSection"; import { FeaturedBlock } from "./FeaturedBlock";
import { ContactSectionBlock, ContactSubsectionBlock } from "./ContactSection"; import { HorizontalRuleBlock } from "./HorizontalRuleBlock";
import { ContactListBlock } from "./ContactListBlock"; import { ImageWithTextBlock } from "./ImageWithTextBlock";
import { ContactEntityBlock } from "./ContactEntityBlock";
import { NeufAddressSectionBlock } from "./NeufAddressSectionBlock"; import { NeufAddressSectionBlock } from "./NeufAddressSectionBlock";
import { OpeningHoursSectionBlock } from "./OpeningHoursSectionBlock"; import { OpeningHoursSectionBlock } from "./OpeningHoursSectionBlock";
import { PageSectionBlock, PageSectionNavigationBlock } from "./PageSection";
import { RichTextBlock } from "./RichTextBlock";
import { ScheduleBlock } from "./ScheduleBlock";
export const Blocks = ({ blocks, pageContent }: { blocks: any, pageContent?: boolean }) => { const ImageSliderBlock = dynamic(() =>
const sections = blocks.filter( import("./ImageSliderBlock").then((m) => m.ImageSliderBlock),
(block: any) => block?.__typename === "PageSectionBlock"
); );
return blocks.map((block: any) => { export type StreamFieldBlock = {
switch (block?.blockType) { id?: string | null;
blockType?: string;
__typename?: string;
[key: string]: unknown;
};
export const Blocks = ({
blocks,
pageContent,
}: {
blocks?: Array<StreamFieldBlock | null> | null;
pageContent?: boolean;
}) => {
const sections = (blocks ?? []).filter(
(block) => block?.__typename === "PageSectionBlock",
) as PageSectionBlockFragment[];
return (blocks ?? []).map((block) => {
if (!block) {
return null;
}
switch (block.blockType) {
case "RichTextBlock": case "RichTextBlock":
return <RichTextBlock key={block.id} block={block} />; return (
break; <RichTextBlock
key={block.id}
block={block as RichTextBlockFragment}
/>
);
case "ImageWithTextBlock": case "ImageWithTextBlock":
return <ImageWithTextBlock key={block.id} block={block} />; return (
break; <ImageWithTextBlock
key={block.id}
block={block as ImageWithTextBlockFragment}
/>
);
case "EmbedBlock": case "EmbedBlock":
return <EmbedBlock key={block.id} block={block} />; return (
break; <EmbedBlock key={block.id} block={block as EmbedBlockFragment} />
);
case "ImageSliderBlock": case "ImageSliderBlock":
return <ImageSliderBlock key={block.id} block={block} pageContent />; return (
break; <ImageSliderBlock
key={block.id}
block={block as ImageSliderBlockFragment}
pageContent
/>
);
case "HorizontalRuleBlock": case "HorizontalRuleBlock":
return <HorizontalRuleBlock key={block.id} block={block} />; return (
break; <HorizontalRuleBlock
key={block.id}
block={block as HorizontalRuleBlockFragment}
/>
);
case "FeaturedBlock": case "FeaturedBlock":
return <FeaturedBlock key={block.id} block={block} />; return (
break; <FeaturedBlock
key={block.id}
block={block as FeaturedBlockFragment}
/>
);
case "AccordionBlock": case "AccordionBlock":
return <AccordionBlock key={block.id} block={block} />; return (
break; <AccordionBlock
key={block.id}
block={block as AccordionBlockFragment}
/>
);
case "FactBoxBlock": case "FactBoxBlock":
return <FactBoxBlock key={block.id} block={block} />; return (
break; <FactBoxBlock key={block.id} block={block as FactBoxBlockFragment} />
);
case "ScheduleBlock":
return (
<ScheduleBlock
key={block.id}
block={block as ScheduleBlockFragment}
/>
);
case "PageSectionBlock": case "PageSectionBlock":
return <PageSectionBlock key={block.id} block={block} />; return (
break; <PageSectionBlock
key={block.id}
block={block as PageSectionBlockFragment}
/>
);
case "PageSectionNavigationBlock": case "PageSectionNavigationBlock":
return <PageSectionNavigationBlock sections={sections} />; return (
break; <PageSectionNavigationBlock key={block.id} sections={sections} />
);
case "ContactSectionBlock": case "ContactSectionBlock":
return <ContactSectionBlock key={block.id} block={block} />; return (
break; <ContactSectionBlock
key={block.id}
block={block as ContactSectionBlockFragment}
/>
);
case "ContactSubsectionBlock": case "ContactSubsectionBlock":
return <ContactSubsectionBlock key={block.id} block={block} />; return (
break; <ContactSubsectionBlock
key={block.id}
block={block as ContactSubsectionBlockFragment}
/>
);
case "ContactListBlock": case "ContactListBlock":
return <ContactListBlock key={block.id} block={block} />; return (
break; <ContactListBlock
key={block.id}
block={block as ContactListBlockFragment}
/>
);
case "ContactEntityBlock": case "ContactEntityBlock":
return <ContactEntityBlock key={block.id} block={block} />; return (
break; <ContactEntityBlock
key={block.id}
block={block as ContactEntityBlockFragment}
/>
);
case "NeufAddressSectionBlock": case "NeufAddressSectionBlock":
return <NeufAddressSectionBlock />; return <NeufAddressSectionBlock key={block.id} />;
break;
case "OpeningHoursSectionBlock": case "OpeningHoursSectionBlock":
return <OpeningHoursSectionBlock />; return <OpeningHoursSectionBlock key={block.id} />;
break;
default: default:
console.log("unsupported block", block); console.log("unsupported block", block);
return <div>Unsupported block type {block?.blockType}</div>; return (
<div key={block.id}>Unsupported block type {block.blockType}</div>
);
} }
}); });
}; };
+7 -8
View File
@@ -3,6 +3,7 @@ import {
type ContactSectionBlockFragment, type ContactSectionBlockFragment,
type ContactSubsectionBlockFragment, type ContactSubsectionBlockFragment,
} from "@/gql/graphql"; } from "@/gql/graphql";
import { RichText } from "@/components/general/RichText";
import styles from "./contactSection.module.scss"; import styles from "./contactSection.module.scss";
import { Blocks } from "./Blocks"; import { Blocks } from "./Blocks";
@@ -37,10 +38,9 @@ export const ContactSectionBlock = ({
<section className={styles.contactSection}> <section className={styles.contactSection}>
<h2 className={styles.heading}>{block.title}</h2> <h2 className={styles.heading}>{block.title}</h2>
{block.text && ( {block.text && (
<p <div className={styles.intro}>
className={styles.intro} <RichText content={block.text} />
dangerouslySetInnerHTML={{ __html: block.text }} </div>
/>
)} )}
<Blocks blocks={block.blocks} /> <Blocks blocks={block.blocks} />
</section> </section>
@@ -56,10 +56,9 @@ export const ContactSubsectionBlock = ({
<section className={styles.contactSubsection}> <section className={styles.contactSubsection}>
<h3 className={styles.heading}>{block.title}</h3> <h3 className={styles.heading}>{block.title}</h3>
{block.text && ( {block.text && (
<p <div className={styles.intro}>
className={styles.intro} <RichText content={block.text} />
dangerouslySetInnerHTML={{ __html: block.text }} </div>
/>
)} )}
<Blocks blocks={block.blocks} /> <Blocks blocks={block.blocks} />
</section> </section>
+4 -4
View File
@@ -1,5 +1,6 @@
import { graphql } from "@/gql"; import { graphql } from "@/gql";
import { type FactBoxBlockFragment } from "@/gql/graphql"; import { type FactBoxBlockFragment } from "@/gql/graphql";
import { RichText } from "@/components/general/RichText";
import styles from "./factBoxBlock.module.scss"; import styles from "./factBoxBlock.module.scss";
const FactBoxBlockFragmentDefinition = graphql(` const FactBoxBlockFragmentDefinition = graphql(`
@@ -23,10 +24,9 @@ export const FactBoxBlock = ({
className={styles.factBox} className={styles.factBox}
data-background-color={block.backgroundColor ?? ""} data-background-color={block.backgroundColor ?? ""}
> >
<div <div className={styles.factBoxContent}>
className={styles.factBoxContent} <RichText content={block.factBoxBody} />
dangerouslySetInnerHTML={{ __html: block.factBoxBody }} </div>
/>
</div> </div>
</section> </section>
); );
+9 -2
View File
@@ -2,7 +2,9 @@ import { graphql, unmaskFragment } from "@/gql";
import { type FeaturedBlockFragment } from "@/gql/graphql"; import { type FeaturedBlockFragment } from "@/gql/graphql";
import Link from "next/link"; import Link from "next/link";
import { Image } from "@/components/general/Image"; import { Image } from "@/components/general/Image";
import { RichText } from "@/components/general/RichText";
import { ImageFragmentDefinition } from "@/lib/common"; import { ImageFragmentDefinition } from "@/lib/common";
import { internalHref } from "@/lib/links";
import styles from "./featuredBlock.module.scss"; import styles from "./featuredBlock.module.scss";
const FeaturedBlockFragmentDefinition = graphql(` const FeaturedBlockFragmentDefinition = graphql(`
@@ -44,6 +46,9 @@ export const FeaturedBlock = ({
); );
// TODO: fetch image from target page // TODO: fetch image from target page
const pageUrl = block.featuredPage.url;
const featuredHref = pageUrl ? internalHref(pageUrl) ?? pageUrl : "#";
return ( return (
<div <div
className={styles.featuredBlock} className={styles.featuredBlock}
@@ -51,8 +56,10 @@ export const FeaturedBlock = ({
> >
<div className={styles.text}> <div className={styles.text}>
<h2>{block.title}</h2> <h2>{block.title}</h2>
<div dangerouslySetInnerHTML={{ __html: block.featuredBlockText }} /> <div>
<Link href={block.featuredPage.url ?? "#"}> <RichText content={block.featuredBlockText} />
</div>
<Link href={featuredHref}>
{block.linkText} &rarr; {block.linkText} &rarr;
</Link> </Link>
</div> </div>
+1 -1
View File
@@ -59,7 +59,7 @@ export const PageSectionNavigationBlock = ({
return ( return (
<li key={anchor}> <li key={anchor}>
<a href={`#${anchor}`} className="button"> <a href={`#${anchor}`} className="button">
{section.title} <span>{section.title}</span>
</a> </a>
</li> </li>
); );
+4 -4
View File
@@ -1,5 +1,6 @@
import { graphql } from "@/gql"; import { graphql } from "@/gql";
import { type RichTextBlockFragment } from "@/gql/graphql"; import { type RichTextBlockFragment } from "@/gql/graphql";
import { RichText } from "@/components/general/RichText";
import styles from "./richTextBlock.module.scss"; import styles from "./richTextBlock.module.scss";
const RichTextBlockFragmentDefinition = graphql(` const RichTextBlockFragmentDefinition = graphql(`
@@ -11,9 +12,8 @@ const RichTextBlockFragmentDefinition = graphql(`
export const RichTextBlock = ({ block }: { block: RichTextBlockFragment }) => { export const RichTextBlock = ({ block }: { block: RichTextBlockFragment }) => {
return ( return (
<div <div className={styles.richTextBlock}>
className={styles.richTextBlock} <RichText content={block.value} />
dangerouslySetInnerHTML={{ __html: block.value }} </div>
></div>
); );
}; };
@@ -0,0 +1,64 @@
import { graphql, unmaskFragment } from "@/gql";
import { type ScheduleBlockFragment } from "@/gql/graphql";
import { RichText } from "@/components/general/RichText";
import styles from "./scheduleBlock.module.scss";
const ScheduleItemFragmentDefinition = graphql(`
fragment ScheduleItem on ScheduleItemBlock {
time
scheduleItemTitle: title
description
}
`);
export const ScheduleBlockFragmentDefinition = graphql(`
fragment ScheduleBlock on ScheduleBlock {
scheduleTitle: title
scheduleItems: items {
__typename
... on ScheduleItemBlock {
...ScheduleItem
}
}
}
`);
export const ScheduleBlock = ({
block,
}: {
block: ScheduleBlockFragment;
}) => {
return (
<section className={styles.schedule}>
{block.scheduleTitle && (
<h2 className={styles.heading}>{block.scheduleTitle}</h2>
)}
<dl className={styles.items}>
{block.scheduleItems?.map((item, index) => {
if (item?.__typename !== "ScheduleItemBlock") {
return null;
}
const scheduleItem = unmaskFragment(
ScheduleItemFragmentDefinition,
item
);
return (
<div key={index} className={styles.item}>
<dt className={styles.time}>{scheduleItem.time}</dt>
<dd className={styles.body}>
<div className={styles.title}>
<RichText content={scheduleItem.scheduleItemTitle} />
</div>
{scheduleItem.description && (
<div className={styles.description}>
<RichText content={scheduleItem.description} />
</div>
)}
</dd>
</div>
);
})}
</dl>
</section>
);
};
@@ -51,11 +51,14 @@
height: var(--size-icon-large); height: var(--size-icon-large);
background: var(--color-goldenBeige); background: var(--color-goldenBeige);
border-radius: 100%; border-radius: 100%;
display: flex; position: relative;
align-items: center;
justify-content: center;
img, svg { img, svg {
width: 100%; width: var(--page-section-icon-scale, 100%);
height: var(--page-section-icon-scale, 100%);
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
} }
} }
@@ -92,7 +92,7 @@
font-style: italic; font-style: italic;
} }
a { a:not(:global(.button)) {
text-decoration-thickness: .1rem; text-decoration-thickness: .1rem;
text-decoration-color: var(--color-goldenOrange); text-decoration-color: var(--color-goldenOrange);
font-weight: 500; font-weight: 500;
@@ -0,0 +1,62 @@
.schedule {
max-width: var(--size-width-p);
margin: 0 auto var(--spacing-section-bottom);
}
.heading {
font-size: var(--font-size-h3);
margin: 0 0 var(--spacing-s);
}
.items {
margin: 0;
}
.item {
display: flex;
gap: var(--spacing-s);
padding: var(--spacing-s) 0;
border-top: var(--border);
&:last-child {
border-bottom: var(--border);
}
}
.title {
font-weight: 600;
font-size: 1.1rem;
line-height: 1;
a {
text-decoration-thickness: .1rem;
-webkit-text-decoration-color: var(--color-goldenOrange);
text-decoration-color: var(--color-goldenOrange);
transition: text-decoration-color var(--transition-easing);
font-weight: 600;
}
}
.time {
flex: 0 0 8rem;
font-family: var(--font-serif);
line-height: 1.1rem;
}
.body {
margin: 0;
}
.description {
margin-top: var(--spacing-xs);
font-size: var(--font-size-caption);
}
@media (max-width: 800px) {
.item {
display: block;
}
.time {
margin-bottom: var(--spacing-xs);
}
}
+7 -7
View File
@@ -304,14 +304,14 @@ const CalendarWeek = ({
); );
}; };
function maybeYear(yearMonthString: string) { function srOnlyYear(yearMonthString: string) {
// formatYearMonth only shows the year for non-current years; always expose it to screen readers
const yearMonth = new Date(yearMonthString); const yearMonth = new Date(yearMonthString);
const now = new Date(); const isCurrentYear = yearMonth.getFullYear() === new Date().getFullYear();
const isCurrentYear = yearMonth.getFullYear() == now.getFullYear(); if (!isCurrentYear) {
if (isCurrentYear) { return null;
return <span className="sr-only"> {yearMonth.getFullYear()}</span>;
} }
return ` ${yearMonth.getFullYear()}`; return <span className="sr-only"> {yearMonth.getFullYear()}</span>;
} }
const EventCalendar = ({ events }: { events: EventOverviewItemFragment[] }) => { const EventCalendar = ({ events }: { events: EventOverviewItemFragment[] }) => {
@@ -354,7 +354,7 @@ const EventCalendar = ({ events }: { events: EventOverviewItemFragment[] }) => {
> >
<h2 onClick={() => toggleYearMonth(yearMonth)}> <h2 onClick={() => toggleYearMonth(yearMonth)}>
{formatYearMonth(yearMonth)} {formatYearMonth(yearMonth)}
{maybeYear(yearMonth)}{" "} {srOnlyYear(yearMonth)}{" "}
<span className={styles.eventCounter}>({eventCount})</span> <span className={styles.eventCounter}>({eventCount})</span>
</h2> </h2>
{eventCount === 0 && ( {eventCount === 0 && (
+1 -1
View File
@@ -57,7 +57,7 @@ export const EventFilter = ({
data-active={activeCategories?.includes(category.slug)} data-active={activeCategories?.includes(category.slug)}
onClick={() => toggleCategory(category.slug)} onClick={() => toggleCategory(category.slug)}
> >
{category.name} <span>{category.name}</span>
</button> </button>
</li> </li>
))} ))}
+6 -4
View File
@@ -1,6 +1,6 @@
import { Suspense } from "react";
import { VenueFragment } from "@/gql/graphql"; import { VenueFragment } from "@/gql/graphql";
import { getClient } from "@/app/client"; import { getClient } from "@/app/client";
import { cachedUntilOsloMidnight } from "@/lib/revalidation";
import { EventContainer } from "@/components/events/EventContainer"; import { EventContainer } from "@/components/events/EventContainer";
import { PageHeader } from "@/components/general/PageHeader"; import { PageHeader } from "@/components/general/PageHeader";
import { import {
@@ -18,7 +18,11 @@ export type EventIndexViewProps = {
}; };
export async function loadEventIndexProps(): Promise<EventIndexViewProps> { export async function loadEventIndexProps(): Promise<EventIndexViewProps> {
const { data, error } = await getClient().query(eventsOverviewQuery, {}); const { data, error } = await getClient().query(
eventsOverviewQuery,
{},
cachedUntilOsloMidnight()
);
if (error) throw new Error(error.message); if (error) throw new Error(error.message);
if ( if (
!data?.index || !data?.index ||
@@ -46,14 +50,12 @@ export function EventIndexView({
return ( return (
<main className="site-main" id="main"> <main className="site-main" id="main">
<PageHeader heading="Dette skjer på Chateau Neuf" align="left" /> <PageHeader heading="Dette skjer på Chateau Neuf" align="left" />
<Suspense>
<EventContainer <EventContainer
events={events} events={events}
eventCategories={eventCategories} eventCategories={eventCategories}
eventOrganizers={eventOrganizers} eventOrganizers={eventOrganizers}
venues={venues} venues={venues}
/> />
</Suspense>
</main> </main>
); );
} }
+4 -4
View File
@@ -5,6 +5,7 @@ import { EventDetails } from "@/components/events/EventDetails";
import { EventHeader } from "@/components/events/EventHeader"; import { EventHeader } from "@/components/events/EventHeader";
import { BgPig } from "@/components/general/BgPig"; import { BgPig } from "@/components/general/BgPig";
import { PageContent } from "@/components/general/PageContent"; import { PageContent } from "@/components/general/PageContent";
import { RichText } from "@/components/general/RichText";
import { getEventPig } from "@/lib/event"; import { getEventPig } from "@/lib/event";
const eventBySlugQuery = graphql(` const eventBySlugQuery = graphql(`
@@ -45,10 +46,9 @@ export function EventPageView({ event }: EventPageViewProps) {
<EventHeader event={event} /> <EventHeader event={event} />
<EventDetails event={event} /> <EventDetails event={event} />
{event.lead && ( {event.lead && (
<div <div className="lead event-lead">
className="lead event-lead" <RichText content={event.lead} />
dangerouslySetInnerHTML={{ __html: event.lead }} </div>
/>
)} )}
<PageContent blocks={event.body} /> <PageContent blocks={event.body} />
</main> </main>
@@ -1,3 +1,8 @@
.events {
// keep the footer below the fold while the event list renders
min-height: 100vh;
}
.eventWrapper { .eventWrapper {
//margin: calc(var(--spacing-sitepadding-block)*-1) calc(var(--spacing-sitepadding-inline)*-1); //margin: calc(var(--spacing-sitepadding-block)*-1) calc(var(--spacing-sitepadding-inline)*-1);
} }
@@ -185,6 +185,12 @@
font-size: var(--font-size-xs); font-size: var(--font-size-xs);
font-family: var(--font-main); font-family: var(--font-main);
font-weight: 500; font-weight: 500;
@supports (text-box-trim: trim-both) {
text-box-trim: trim-both;
text-box-edge: cap alphabetic;
padding-block: 0.25em;
}
} }
.moreDates { .moreDates {
+41
View File
@@ -0,0 +1,41 @@
import Link from "next/link";
import { Icon } from "@/components/general/Icon";
/**
* A link styled as a button. Internal links use next/link with a forward arrow;
* external links open in a new tab with an external icon. isExternal is inferred
* from the href unless passed explicitly.
*/
export const ButtonLink = ({
href,
isExternal,
children,
}: {
href: string;
isExternal?: boolean;
children: React.ReactNode;
}) => {
const external =
isExternal ?? (!href.startsWith("/") && !href.startsWith("#"));
if (external) {
return (
<a
href={href}
className="button"
target="_blank"
rel="noopener noreferrer"
>
<span>{children}</span>
<Icon type="externalLink" />
</a>
);
}
return (
<Link href={href} className="button">
<span>{children}</span>
<Icon type="arrowRight" />
</Link>
);
};
+89 -5
View File
@@ -6,7 +6,7 @@ export function Icon ({
type?: "doc" | "arrowUp" | "arrowRight" | "externalLink" | "search" | "tickets" | "list" | "calendar" | "filter" | "noFilter" | "email" | "phone" | "instagram" | "facebook" | "flickr" | "tiktok"; type?: "doc" | "arrowUp" | "arrowRight" | "externalLink" | "search" | "tickets" | "list" | "calendar" | "filter" | "noFilter" | "email" | "phone" | "instagram" | "facebook" | "flickr" | "tiktok";
}) { }) {
return ( return (
<div className={styles.icon}> <span className={styles.icon}>
{type === "doc" && ( {type === "doc" && (
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -26,7 +26,8 @@ export function Icon ({
)} )}
{type === "externalLink" && ( {type === "externalLink" && (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="currentColor"><path d="M257.39-235.61 197-296l381-381H238.39v-86h486v486h-86v-339.61l-381 381Z"/></svg> // glyph is drawn ~20u up-and-left of centre, so offset the viewBox to recentre it
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-20 -980 960 960" fill="currentColor"><path d="M257.39-235.61 197-296l381-381H238.39v-86h486v486h-86v-339.61l-381 381Z" /></svg>
)} )}
{type === "search" && ( {type === "search" && (
@@ -89,7 +90,7 @@ export function Icon ({
)} )}
{!type && ( {!type && (
<div> <span>
<svg <svg
viewBox="0 0 49 49" viewBox="0 0 49 49"
fill="none" fill="none"
@@ -100,10 +101,10 @@ export function Icon ({
fill="currentColor" fill="currentColor"
/> />
</svg> </svg>
</div> </span>
)} )}
</div> </span>
); );
} }
@@ -319,6 +320,89 @@ export function DecorativeIcon ({
</svg> </svg>
)} )}
{type === "studioMole" && (
<svg viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M257.791 394.402C257.791 394.402 284.014 388.17 299.771 362.179" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
<path d="M177.811 274.704C177.811 274.704 196.938 339.77 313.099 324.723C325.263 323.148 322.569 336.177 329.843 343.595" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
<path d="M118.754 303.122C92.0982 304.442 90.6013 285.935 96.8662 275.723C102 267.363 114.729 270.9 114.729 270.9L251.005 196.63" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
<path d="M98.0527 272.275C96.0679 223.797 126.25 186.13 138.592 180.397C144.169 177.814 131.284 175.929 120.961 181.495C107.079 188.991 99.3722 196.631 105.759 187.006C133.524 145.203 168.275 152.499 169.439 146.911C184.153 75.8679 269.467 51.9504 260.397 56.5188C224.215 74.7701 196.539 140.513 209.535 123.326C279.79 30.4391 424.37 65.6667 404.533 67.8067C318.599 77.0654 273.436 140.734 293.928 132.141C377.478 97.1019 412.728 158.797 412.728 158.797C412.728 158.797 368.408 142.276 333.812 161.658C328.434 164.674 358.495 158.132 388.899 199.336C392.092 203.661 399.921 199.558 408.292 188.758C411.885 184.123 422.785 171.316 433.097 178.091C448.909 188.492 428.384 214.438 428.384 214.438C428.384 214.438 439.528 288.087 363.784 333.516C353.56 339.647 377.223 355.77 296.356 421.856" stroke="currentColor" strokeWidth="4" strokeLinecap="round" stroke-linejoin="round" />
<path d="M96.6444 297.301C96.6444 301.726 98.3853 337.264 96.6444 345.014" stroke="currentColor" strokeWidth="4" strokeLinecap="round" stroke-linejoin="round" />
<path d="M104.883 284.472C104.883 284.472 115.018 283.94 118.766 292.323" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
<path d="M406.707 209.948C409.756 208.373 414.513 205.202 417.728 199.558" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
<path d="M415.267 216.002C418.915 213.895 423.04 210.136 425.047 205.89" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
<path d="M437.277 198.504C437.277 198.504 429.548 220.836 416.132 218.43C409.024 217.155 399.776 206.078 409.39 187.372" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
<path d="M257.902 192.262C257.902 192.262 244.95 196.941 246.392 219.949C247.068 230.694 253.244 262.706 289.536 266.154C337.637 270.723 351.642 243.8 356.099 208.473C359.038 185.21 282.03 183.502 257.902 192.273V192.262Z" fill="currentColor" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" />
<path d="M429.759 212.564C430.857 203.672 471.296 211.411 471.817 218.718C474.012 249.289 456.316 270.645 426.444 268.039C422.607 267.707 427.453 266.121 430.89 239.675C431.788 232.778 428.451 223.054 429.748 212.564H429.759Z" fill="currentColor" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" />
<path d="M97.8751 343.085C94.5264 345.968 104.351 370.129 116.836 384.433C129.31 398.737 199.3 468.272 282.318 438.877C307.721 429.884 294.67 414.527 287.041 416.002C279.413 417.465 284.092 424.883 274.722 427.511C267.692 429.485 262.603 417.754 257.147 417.709C246.07 417.621 243.121 431.525 233.452 431.392C208.27 431.059 208.426 409.238 189.808 405.922C178.764 403.96 173.575 410.214 165.403 403.028C159.803 398.094 167.454 356.635 136.285 362.567C115.251 366.57 130.342 315.209 97.8973 343.096L97.8751 343.085Z" fill="currentColor" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" />
<path d="M186.382 292.999C186.382 292.999 173.686 311.139 189.099 329.857C200.608 343.839 223.106 351.235 232.354 341.089C235.026 338.162 227.12 321.097 223.106 319.345C197.603 308.201 186.382 292.999 186.382 292.999Z" fill="currentColor" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" />
<path d="M358.439 205.656C358.439 205.656 383.887 206.61 396.916 199.569" stroke="currentColor" strokeWidth="4" strokeLinecap="round" stroke-linejoin="round" />
<path d="M346.309 201.155C346.309 201.155 367.964 201.199 393.323 201.199" stroke="currentColor" strokeWidth="4" strokeLinecap="round" stroke-linejoin="round" />
</svg>
)}
{type === "studioCalendar" && (
<svg viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M205.66 112.882C217.797 112.882 231.419 113.057 246.692 113.109C382.562 113.553 381.541 112.676 382.727 237.235C384.09 380.154 386.69 387.595 286.041 387.595C274.328 387.595 261.202 387.492 246.527 387.492C226.672 387.492 209.778 387.719 195.361 387.719C103.484 387.719 113.123 378.421 110.605 241.436C108.396 121.478 121.017 112.882 205.66 112.882ZM205.66 108.754C168.643 108.754 141.213 109.992 125.445 126.06C107.942 143.883 105.321 178.929 106.477 241.508C106.714 254.635 106.848 266.771 106.962 277.472C107.519 329.195 107.829 357.688 119.129 373.488C131.379 390.608 156.094 391.847 195.351 391.847C201.997 391.847 209.231 391.795 216.899 391.743C225.763 391.682 235.815 391.62 246.516 391.62C254.256 391.62 261.491 391.651 268.477 391.671C274.659 391.692 280.5 391.713 286.031 391.713C328.61 391.713 355.535 390.629 370.375 375.645C388.052 357.791 387.702 323.21 386.948 247.421L386.845 237.184L386.814 234.449C386.226 172.85 385.937 141.932 367.96 125.544C350.137 109.301 316.516 109.188 249.52 108.971H246.682C239.736 108.94 233.183 108.888 226.837 108.847C219.334 108.795 212.255 108.733 205.629 108.733L205.66 108.754Z" fill="currentColor" />
<path d="M182.812 79.126V136.896" stroke="currentColor" strokeWidth="4" strokeLinecap="round" stroke-linejoin="round" />
<path d="M246.692 84.4404V136.896" stroke="currentColor" strokeWidth="4" strokeLinecap="round" stroke-linejoin="round" />
<path d="M312.801 79.126V136.896" stroke="currentColor" strokeWidth="4" strokeLinecap="round" stroke-linejoin="round" />
<path d="M112.122 187.824H384.244" stroke="currentColor" strokeWidth="4" strokeLinecap="round" stroke-linejoin="round" />
<path d="M164.051 250.651C164.051 267.545 161.017 266.09 180.005 266.09C195.774 266.09 196.249 269.65 195.774 250.486C195.289 230.858 197.229 234.17 179.52 234.17C161.812 234.17 164.051 233.758 164.051 250.651Z" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" />
<path d="M230.872 250.651C230.872 267.545 227.837 266.09 246.826 266.09C262.595 266.09 263.069 269.65 262.595 250.486C262.11 230.858 264.05 234.17 246.341 234.17C228.632 234.17 230.872 233.758 230.872 250.651Z" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" />
<path d="M297.692 250.651C297.692 267.545 294.658 266.09 313.647 266.09C329.415 266.09 329.89 269.65 329.415 250.486C328.93 230.858 330.87 234.17 313.162 234.17C295.453 234.17 297.692 233.758 297.692 250.651Z" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" />
<path d="M164.495 314.139C164.495 331.032 161.461 329.577 180.449 329.577C196.218 329.577 196.693 333.137 196.218 313.973C195.743 294.81 197.673 297.658 179.964 297.658C162.255 297.658 164.495 297.245 164.495 314.139Z" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" />
<path d="M231.315 314.139C231.315 331.032 228.281 329.577 247.27 329.577C263.038 329.577 263.513 333.137 263.038 313.973C262.564 294.81 264.494 297.658 246.785 297.658C229.076 297.658 231.315 297.245 231.315 314.139Z" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" />
<path d="M298.136 314.138C298.136 331.032 295.102 329.577 314.09 329.577C329.859 329.577 330.334 333.137 329.859 313.973C329.374 294.345 331.314 297.658 313.605 297.658C295.897 297.658 298.136 297.245 298.136 314.138Z" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" />
</svg>
)}
{type === "studioDisco" && (
<svg viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M255.491 4C255.491 4 231.909 59.2729 251.402 86.8892C262.494 102.599 281.018 86.7551 266.37 81.8483C251.721 76.9415 129.226 102.599 97.8887 181.139C74.4474 239.897 57.5004 389.74 227.466 419.109C448.365 457.271 491.032 106.527 287.956 89.8374" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
<path d="M210.746 105.65C210.746 105.65 91.8173 170.841 120.114 336.157" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
<path d="M234.713 102.599C234.713 102.599 123.444 228.63 171.904 383.751" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
<path d="M244.176 115.361C244.176 115.361 179.738 255.267 229.806 403.276" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
<path d="M267.596 117.887C267.596 117.887 297.542 307.531 284.781 404.863" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
<path d="M283.802 109.475C283.802 109.475 365.29 214.528 331.911 395.183" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
<path d="M311.871 102.599C311.871 102.599 414.749 180.16 381.185 348.053" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
<path d="M167.595 123.216C167.595 123.216 294.605 162.491 335.839 109.475" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
<path d="M123.773 164.913C123.773 164.913 282.193 233.176 381.175 150.698" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
<path d="M101.342 220.899C101.342 220.899 265.154 304.85 401.627 207.652" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
<path d="M101.342 289.913C101.342 289.913 226.858 378.483 406.534 276.378" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
<path d="M131.938 361.31C131.938 361.31 261.216 442.602 395.73 325.612" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
<path d="M175.8 42.7075L186.603 72.1587" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
<path d="M96.5283 85.9097L120.114 109.475" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
<path d="M42.2749 160.522L72.7157 169.316" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
<path d="M3 245.938H57.4903" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
<path d="M30.2449 348.053L64.8605 333.322" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
<path d="M87.436 424.985L108.321 403.275" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
<path d="M162.605 482.557L181.202 441.324" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
<path d="M287.853 494.34L283.802 448.2" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
<path d="M387.916 441.324L372.165 414.13" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
<path d="M454.633 357.681L420.955 348.053" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
<path d="M470.343 267.544H442.86" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
<path d="M437.799 181.139L470.343 160.522" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
<path d="M395.73 102.599L420.955 77.0654" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
<path d="M311.871 68.2314L321.108 35.832" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
</svg>
)}
{type === "studioGuitar" && (
<svg viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M302.752 146.426C302.752 146.426 244.441 260.587 293.837 239.951C336.821 222 339.536 242.362 336.122 251.135C326.893 274.85 304.444 259.503 289.927 289.884C276.889 317.186 306.5 331.49 280.526 382.69C272.087 399.324 226.064 449.895 164.906 414.722C100.041 377.412 115.844 319.546 125.995 298.333C150.622 246.87 202.43 264.497 206.482 234.501C209.551 211.758 204.233 191.922 221.12 186.523C231.828 183.099 253.913 193.786 255.098 256.251C256.435 326.739 230.501 362.743 211.902 359.116C193.302 355.479 197.527 336.231 203.342 321.258C209.156 306.275 312.102 78.1264 344.358 61.6846C368.499 49.376 360.678 87.2742 353.009 99.0559C345.493 110.625 279.725 171.732 301.8 103.979C314.919 63.7006 338.766 44.9692 338.766 44.9692" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
<path d="M309.408 56.5386C309.408 56.5386 309.823 55.1 309.793 56.731" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
<path d="M290.869 97.6782C290.869 97.6782 291.284 96.2397 291.254 97.8707" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
<path d="M299.348 74.7431C299.348 74.7431 299.763 73.3046 299.733 74.9356" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
<path d="M324.685 39.3979C324.685 39.3979 325.1 37.9594 325.07 39.5904" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
</svg>
)}
{type === "studioNote" && (
<svg viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M174.712 301.558C179.079 304.681 215.963 166.096 219.529 150.614C224.366 129.6 230.004 127.231 249.513 127.02C269.022 126.809 360.247 123.426 371.383 122.813C389.119 121.839 412.644 117.743 408.779 143.827C403.691 178.155 366.376 302.211 362.35 321.92C355.76 354.209 320.117 401.438 268.852 370.083C214.401 336.779 260.149 251.779 339.776 288.305C352.976 294.36 374.438 184.51 372.665 177.12C370.892 169.731 242.993 173.275 237.085 177.783C231.186 182.291 196.685 319.179 193.52 339.45C187.922 375.253 129.245 421.147 88.7755 363.958C49.0668 307.833 119.901 262.452 174.712 301.558Z" stroke="currentColor" strokeWidth="4" strokeMiterlimit="10" strokeLinecap="round" />
</svg>
)}
{type === "email" && ( {type === "email" && (
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.79833 18.8515C6.39828 18.9115 6.03892 18.8236 5.72025 18.5879C5.40157 18.3523 5.21228 18.0344 5.15237 17.6344L3.80227 8.61883C3.74237 8.21879 3.83024 7.85943 4.0659 7.54075C4.30157 7.22207 4.61942 7.03278 5.01947 6.97287L17.2026 5.14841C17.6027 5.0885 17.962 5.17638 18.2807 5.41204C18.5994 5.64771 18.7887 5.96556 18.8486 6.36561L20.1987 15.3811C20.2586 15.7812 20.1707 16.1405 19.9351 16.4592C19.6994 16.7779 19.3815 16.9672 18.9815 17.0271L6.79833 18.8515ZM12.0666 12.4417L5.12468 9.33937L6.34024 17.4565C6.35089 17.5276 6.38247 17.5826 6.43498 17.6214C6.48748 17.6602 6.54931 17.6743 6.62045 17.6637L18.8036 15.8392C18.8748 15.8286 18.9297 15.797 18.9686 15.7445C19.0074 15.692 19.0215 15.6302 19.0108 15.559L17.7953 7.44192L12.0666 12.4417ZM11.8819 11.2081L17.5025 6.31802L5.0754 8.179L11.8819 11.2081ZM5.12468 9.33937L4.95364 8.19724L6.34024 17.4565C6.35089 17.5276 6.38247 17.5826 6.43498 17.6214C6.48748 17.6602 6.54931 17.6743 6.62045 17.6637L6.37674 17.7002L5.12468 9.33937Z" fill="currentColor" /> <path d="M6.79833 18.8515C6.39828 18.9115 6.03892 18.8236 5.72025 18.5879C5.40157 18.3523 5.21228 18.0344 5.15237 17.6344L3.80227 8.61883C3.74237 8.21879 3.83024 7.85943 4.0659 7.54075C4.30157 7.22207 4.61942 7.03278 5.01947 6.97287L17.2026 5.14841C17.6027 5.0885 17.962 5.17638 18.2807 5.41204C18.5994 5.64771 18.7887 5.96556 18.8486 6.36561L20.1987 15.3811C20.2586 15.7812 20.1707 16.1405 19.9351 16.4592C19.6994 16.7779 19.3815 16.9672 18.9815 17.0271L6.79833 18.8515ZM12.0666 12.4417L5.12468 9.33937L6.34024 17.4565C6.35089 17.5276 6.38247 17.5826 6.43498 17.6214C6.48748 17.6602 6.54931 17.6743 6.62045 17.6637L18.8036 15.8392C18.8748 15.8286 18.9297 15.797 18.9686 15.7445C19.0074 15.692 19.0215 15.6302 19.0108 15.559L17.7953 7.44192L12.0666 12.4417ZM11.8819 11.2081L17.5025 6.31802L5.0754 8.179L11.8819 11.2081ZM5.12468 9.33937L4.95364 8.19724L6.34024 17.4565C6.35089 17.5276 6.38247 17.5826 6.43498 17.6214C6.48748 17.6602 6.54931 17.6743 6.62045 17.6637L6.37674 17.7002L5.12468 9.33937Z" fill="currentColor" />
+1 -1
View File
@@ -14,7 +14,7 @@ export const Newsletter = ({ url }: { url?: string }) => {
<p>Meld deg vårt nyhetsbrev og hold deg oppdatert arrangementer og siste nytt!</p> <p>Meld deg vårt nyhetsbrev og hold deg oppdatert arrangementer og siste nytt!</p>
</div> </div>
<a href={link} target="_blank" className="button primary"> <a href={link} target="_blank" className="button primary">
Meld deg <span>Meld deg </span>
<Icon type="arrowRight" /> <Icon type="arrowRight" />
</a> </a>
</div> </div>
+9 -3
View File
@@ -1,9 +1,15 @@
import { Blocks } from "@/components/blocks/Blocks"; import { Blocks, type StreamFieldBlock } from "@/components/blocks/Blocks";
import styles from './pageContent.module.scss'; import styles from './pageContent.module.scss';
export const PageContent = ({ blocks }: any) => { export const PageContent = ({
blocks,
className,
}: {
blocks?: Array<StreamFieldBlock | null> | null;
className?: string;
}) => {
return ( return (
<div className={styles.pageContent}> <div className={`${styles.pageContent}${className ? ` ${className}` : ''}`}>
<Blocks blocks={blocks} pageContent /> <Blocks blocks={blocks} pageContent />
</div> </div>
) )
+4 -1
View File
@@ -1,3 +1,4 @@
import { RichText } from "@/components/general/RichText";
import styles from "./pageHeader.module.scss"; import styles from "./pageHeader.module.scss";
export const PageHeader = ({ export const PageHeader = ({
@@ -13,7 +14,9 @@ export const PageHeader = ({
<div className={`${styles.pageHeader} ${align && styles[align]}`}> <div className={`${styles.pageHeader} ${align && styles[align]}`}>
<h1 className={styles.title}>{heading}</h1> <h1 className={styles.title}>{heading}</h1>
{lead && ( {lead && (
<div className="lead" dangerouslySetInnerHTML={{ __html: lead }} /> <div className="lead">
<RichText content={lead} />
</div>
)} )}
</div> </div>
); );
+45
View File
@@ -0,0 +1,45 @@
import parse, {
domToReact,
Element,
type DOMNode,
type HTMLReactParserOptions,
} from "html-react-parser";
import Link from "next/link";
import { ButtonLink } from "@/components/general/ButtonLink";
import { internalHref } from "@/lib/links";
/**
* Renders CMS rich text (HTML) as React. Everything renders as plain HTML,
* except:
*
* - inline button markers (`<a class="button">` from the button-link Draftail
* feature) become the ButtonLink component
* - links pointing at this site become next/link
*/
const options: HTMLReactParserOptions = {
replace: (node) => {
if (!(node instanceof Element) || node.name !== "a") {
return;
}
const href = node.attribs.href ?? "#";
const internal = internalHref(href);
if (node.attribs.class?.split(/\s+/).includes("button")) {
return (
<ButtonLink href={internal ?? href}>
{domToReact(node.children as DOMNode[], options)}
</ButtonLink>
);
}
if (internal) {
return (
<Link href={internal}>
{domToReact(node.children as DOMNode[], options)}
</Link>
);
}
},
};
export const RichText = ({ content }: { content: string }) => {
return <>{parse(content, options)}</>;
};
+5 -1
View File
@@ -1,10 +1,14 @@
import Link from "next/link"; import Link from "next/link";
import styles from "./sectionFooter.module.scss"; import styles from "./sectionFooter.module.scss";
import { Icon } from "./Icon";
export const SectionFooter = ({ link, linkText }: { link: string, linkText: string }) => { export const SectionFooter = ({ link, linkText }: { link: string, linkText: string }) => {
return ( return (
<footer className={styles.sectionFooter}> <footer className={styles.sectionFooter}>
{link && linkText && <Link href={link}>{linkText}</Link>} {link && linkText && <Link href={link} className={styles.link}>
<span>{linkText}</span>
<Icon type="arrowRight" />
</Link>}
</footer> </footer>
); );
}; };
+7 -1
View File
@@ -1,5 +1,6 @@
import Link from "next/link"; import Link from "next/link";
import styles from "./sectionHeader.module.scss"; import styles from "./sectionHeader.module.scss";
import { Icon } from "./Icon";
export const SectionHeader = ({ heading, link, linkText }: { heading: string, link?: string, linkText?: string }) => { export const SectionHeader = ({ heading, link, linkText }: { heading: string, link?: string, linkText?: string }) => {
return ( return (
@@ -10,7 +11,12 @@ export const SectionHeader = ({ heading, link, linkText }: { heading: string, li
<span className="circle"></span> <span className="circle"></span>
{heading} {heading}
</h2> </h2>
{link && linkText && <Link href={link}>{linkText}</Link>} {link && linkText &&
<Link href={link} className={styles.link}>
<span>{linkText}</span>
<Icon type="arrowRight" />
</Link>
}
</header> </header>
); );
}; };
@@ -5,21 +5,20 @@
align-items: center; align-items: center;
flex-wrap: wrap; flex-wrap: wrap;
a {
display: block;
font-family: var(--font-serif);
position: relative;
padding-right: 1.4rem;
&:after {
content: "";
position: absolute;
right: 0;
}
}
display: none; display: none;
@media (max-width: 800px) { @media (max-width: 800px) {
display: flex; display: flex;
} }
} }
.link {
display: flex;
align-items: center;
gap: var(--spacing-xs);
margin-bottom: var(--spacing-s);
position: relative;
span {
font-family: var(--font-serif);
}
}
@@ -4,23 +4,21 @@
align-items: center; align-items: center;
flex-wrap: wrap; flex-wrap: wrap;
a {
display: block;
font-family: var(--font-serif);
margin-bottom: var(--spacing-s);
position: relative;
padding-right: 1.4rem;
&:after {
content: "";
position: absolute;
right: 0;
}
}
@media (max-width: 800px) { @media (max-width: 800px) {
a { .link {
display: none; display: none;
} }
} }
} }
.link {
display: flex;
align-items: center;
gap: var(--spacing-xs);
margin-bottom: var(--spacing-s);
position: relative;
span {
font-family: var(--font-serif);
}
}

Some files were not shown because too many files have changed in this diff Show More