Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1073adacbb | |||
| 7e114adcc3 | |||
| 0e074b5f1f | |||
| e960da6f1c | |||
| a5ebb897f1 | |||
| f91c67f526 | |||
| 0c5a9876d6 | |||
| cf945d8647 | |||
| 0e5f9f7769 | |||
| 10c8ce194c | |||
| 509a50c321 | |||
| 843062bb13 | |||
| f7e0200a0a | |||
| b09ce9808d | |||
| bc8642b1fc | |||
| 2155a149e8 | |||
| 676e58c361 | |||
| a9e59b947a | |||
| 202cfe47f3 | |||
| d0a886a4ae | |||
| cc5b53011d | |||
| f769898698 | |||
| e71db3a0cc | |||
| 355c0b38a5 | |||
| bba98fe868 | |||
| 978aae4fc3 | |||
| ffc3a583b2 | |||
| 12aea04779 | |||
|
6bcf9bbfbd
|
|||
|
2e4ca34f5c
|
|||
|
d76b16781d
|
|||
| 8942bcc9da | |||
| 196f000a2d | |||
| 4655f67a9e | |||
| 8506fd1c2d | |||
| ca341f5f22 | |||
| 1431b8d6ff | |||
| 3c225aa68a | |||
| 16629a2fc0 | |||
| b18f9ec54f | |||
| 2a00d21717 | |||
| 09e69a7093 | |||
| d3f8b8f0bb | |||
| fcd5231c28 |
@@ -0,0 +1,36 @@
|
||||
# neuf-www
|
||||
|
||||
The neuf.no website. Wagtail CMS backend (`dnscms/`) feeding a Next.js frontend (`web/`) over GraphQL.
|
||||
|
||||
Tools are managed by [mise](https://mise.jdx.dev/). Run `mise install` to get python, uv, node, and prek.
|
||||
|
||||
## Backend (`dnscms/`)
|
||||
|
||||
```bash
|
||||
cd dnscms
|
||||
uv sync
|
||||
uv run ./manage.py migrate
|
||||
uv run ./manage.py runserver
|
||||
uv run pytest
|
||||
```
|
||||
|
||||
GraphQL endpoint: <http://127.0.0.1:8000/api/graphql/>.
|
||||
|
||||
## Frontend (`web/`)
|
||||
|
||||
```bash
|
||||
cd web
|
||||
npm install
|
||||
npm run dev # http://localhost:3000
|
||||
npm run codegen # regenerate GraphQL types (needs the backend running)
|
||||
npm run build
|
||||
```
|
||||
|
||||
## Pre-commit hooks
|
||||
|
||||
[prek](https://github.com/j178/prek) runs ruff lint + format on `dnscms/**/*.py` plus a few sanity hooks. Hooks are configured in [prek.toml](prek.toml).
|
||||
|
||||
```bash
|
||||
prek install # registers the git hook
|
||||
prek run --all-files # run on everything
|
||||
```
|
||||
@@ -9,6 +9,7 @@ __pycache__
|
||||
.DS_Store
|
||||
*.swp
|
||||
/venv/
|
||||
/.venv/
|
||||
/tmp/
|
||||
/.vagrant/
|
||||
/Vagrantfile.local
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
.DS_Store
|
||||
*.swp
|
||||
.vscode/
|
||||
.coverage
|
||||
/venv/
|
||||
/.venv/
|
||||
/static/
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
FROM python:3.12-slim-bullseye
|
||||
FROM python:3.14-slim-trixie
|
||||
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
|
||||
|
||||
RUN useradd wagtail
|
||||
@@ -37,4 +37,4 @@ USER wagtail
|
||||
ENV PATH="/app/.venv/bin:$PATH"
|
||||
RUN python manage.py collectstatic --noinput --clear
|
||||
|
||||
CMD ["sh", "-c", "set -xe; python manage.py migrate --noinput && gunicorn dnscms.wsgi:application"]
|
||||
CMD ["sh", "-c", "set -xe; python manage.py migrate --noinput && gunicorn dnscms.wsgi:application --workers 3"]
|
||||
|
||||
@@ -10,13 +10,14 @@ from wagtail.admin.panels import FieldPanel
|
||||
from wagtail.fields import RichTextField
|
||||
from wagtail.models import Page
|
||||
from wagtail.search import index
|
||||
from wagtail_headless_preview.models import HeadlessMixin
|
||||
|
||||
from dnscms.fields import CommonStreamField
|
||||
from dnscms.wordpress.models import WPImportedPageMixin
|
||||
|
||||
|
||||
@register_singular_query_field("associationIndex")
|
||||
class AssociationIndex(Page):
|
||||
class AssociationIndex(HeadlessMixin, Page):
|
||||
max_count = 1
|
||||
subpage_types = ["associations.AssociationPage"]
|
||||
|
||||
@@ -37,7 +38,7 @@ class AssociationIndex(Page):
|
||||
search_fields = Page.search_fields
|
||||
|
||||
|
||||
class AssociationPage(WPImportedPageMixin, Page):
|
||||
class AssociationPage(HeadlessMixin, WPImportedPageMixin, Page):
|
||||
subpage_types = []
|
||||
parent_page_types = ["associations.AssociationIndex"]
|
||||
show_in_menus = False
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
|
||||
# Create your tests here.
|
||||
@@ -11,6 +11,7 @@ from wagtail.fields import RichTextField, StreamField
|
||||
from wagtail.models import Page
|
||||
from wagtail.search import index
|
||||
from wagtail.snippets.models import register_snippet
|
||||
from wagtail_headless_preview.models import HeadlessMixin
|
||||
|
||||
from contacts.blocks import ContactSectionBlock
|
||||
from dnscms.blocks import BASE_BLOCKS
|
||||
@@ -22,7 +23,7 @@ PHONE_REGEX_VALIDATOR = RegexValidator(
|
||||
|
||||
|
||||
@register_singular_query_field("contactIndex")
|
||||
class ContactIndex(Page):
|
||||
class ContactIndex(HeadlessMixin, Page):
|
||||
max_count = 1
|
||||
subpage_types = []
|
||||
|
||||
|
||||
@@ -37,9 +37,11 @@ INSTALLED_APPS = [
|
||||
"news",
|
||||
"openinghours",
|
||||
"sponsors",
|
||||
"studio",
|
||||
# end cms apps
|
||||
"grapple",
|
||||
"graphene_django",
|
||||
"wagtail_headless_preview",
|
||||
"wagtail.contrib.forms",
|
||||
"wagtail.contrib.redirects",
|
||||
"wagtail.contrib.settings",
|
||||
@@ -61,6 +63,7 @@ INSTALLED_APPS = [
|
||||
"django.contrib.sessions",
|
||||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
"django.contrib.postgres",
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
@@ -172,6 +175,7 @@ WAGTAIL_SITE_NAME = "dnscms"
|
||||
WAGTAIL_ALLOW_UNICODE_SLUGS = False
|
||||
|
||||
WAGTAILIMAGES_IMAGE_MODEL = "images.CustomImage"
|
||||
WAGTAILIMAGES_EXTENSIONS = ["avif", "gif", "jpg", "jpeg", "png", "webp", "svg"]
|
||||
|
||||
# Search
|
||||
# https://docs.wagtail.org/en/stable/topics/search/backends.html
|
||||
@@ -182,11 +186,25 @@ WAGTAILSEARCH_BACKENDS = {
|
||||
}
|
||||
|
||||
# Base URL to use when referring to full URLs within the Wagtail admin backend -
|
||||
# e.g. in notification emails. Don't include '/admin' or a trailing slash
|
||||
WAGTAILADMIN_BASE_URL = "http://example.com"
|
||||
# e.g. in notification emails. Don't include '/admin' or a trailing slash.
|
||||
# Also used by wagtail-grapple to make image URLs absolute.
|
||||
WAGTAIL_BASE_URL = os.environ.get("WAGTAIL_BASE_URL", "http://127.0.0.1:8000").rstrip("/")
|
||||
WAGTAILADMIN_BASE_URL = WAGTAIL_BASE_URL
|
||||
BASE_URL = WAGTAIL_BASE_URL
|
||||
|
||||
# Required by wagtail-grapple to make image URLs absolute
|
||||
BASE_URL = "http://example.com"
|
||||
# Public URL of the Next.js frontend. Used to direct preview iframes and to
|
||||
# redirect "View Live" clicks on the CMS host over to the headless frontend.
|
||||
FRONTEND_BASE_URL = os.environ.get("FRONTEND_BASE_URL", "http://localhost:3000").rstrip("/")
|
||||
|
||||
WAGTAIL_HEADLESS_PREVIEW = {
|
||||
"CLIENT_URLS": {"default": f"{FRONTEND_BASE_URL}/api/preview"},
|
||||
"SERVE_BASE_URL": FRONTEND_BASE_URL,
|
||||
"ENFORCE_TRAILING_SLASH": False,
|
||||
"REDIRECT_ON_PREVIEW": False,
|
||||
}
|
||||
|
||||
# https://docs.wagtail.org/en/latest/releases/6.4.html#data-upload-max-number-fields-update
|
||||
DATA_UPLOAD_MAX_NUMBER_FIELDS = 10_000
|
||||
|
||||
# GraphQL
|
||||
GRAPHENE = {"SCHEMA": "grapple.schema.schema"}
|
||||
@@ -202,8 +220,11 @@ GRAPPLE = {
|
||||
"news",
|
||||
"openinghours",
|
||||
"sponsors",
|
||||
"studio",
|
||||
],
|
||||
"EXPOSE_GRAPHIQL": True,
|
||||
"PAGE_SIZE": 100,
|
||||
"MAX_PAGE_SIZE": 5000,
|
||||
}
|
||||
|
||||
# Wgtail WordPress import
|
||||
|
||||
@@ -11,13 +11,6 @@ ALLOWED_HOSTS = ["*"]
|
||||
|
||||
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||
|
||||
# Base URL to use when referring to full URLs within the Wagtail admin backend -
|
||||
# e.g. in notification emails. Don't include '/admin' or a trailing slash
|
||||
WAGTAILADMIN_BASE_URL = "http://127.0.0.1:8000"
|
||||
|
||||
# Required by wagtail-grapple to make image URLs absolute
|
||||
BASE_URL = "http://127.0.0.1:8000"
|
||||
|
||||
try:
|
||||
from .local import *
|
||||
except ImportError:
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
from .base import * # noqa: F401, F403
|
||||
|
||||
SECRET_KEY = "test-secret-key"
|
||||
DEBUG = False
|
||||
ALLOWED_HOSTS = ["*"]
|
||||
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.sqlite3",
|
||||
"NAME": ":memory:",
|
||||
}
|
||||
}
|
||||
|
||||
STORAGES = {
|
||||
"default": {
|
||||
"BACKEND": "django.core.files.storage.FileSystemStorage",
|
||||
},
|
||||
"staticfiles": {
|
||||
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
|
||||
},
|
||||
}
|
||||
|
||||
PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"]
|
||||
+10
-6
@@ -30,6 +30,7 @@ from wagtail.fields import RichTextField
|
||||
from wagtail.models import Orderable, Page, PageManager, PageQuerySet
|
||||
from wagtail.search import index
|
||||
from wagtail.snippets.models import register_snippet
|
||||
from wagtail_headless_preview.models import HeadlessMixin
|
||||
|
||||
from associations.widgets import AssociationChooserWidget
|
||||
from dnscms.fields import CommonStreamField
|
||||
@@ -39,7 +40,7 @@ from venues.models import VenuePage
|
||||
|
||||
|
||||
@register_singular_query_field("eventIndex")
|
||||
class EventIndex(Page):
|
||||
class EventIndex(HeadlessMixin, Page):
|
||||
max_count = 1
|
||||
subpage_types = ["events.EventPage"]
|
||||
|
||||
@@ -209,15 +210,18 @@ class EventOrganizer(ClusterableModel):
|
||||
|
||||
class EventPageQuerySet(PageQuerySet):
|
||||
def future(self):
|
||||
today = timezone.localtime(timezone.now()).date()
|
||||
next_occurrence = Min("occurrences__start", filter=Q(occurrences__start__gte=today))
|
||||
return self.filter(occurrences__start__gte=today).annotate(next_occurrence=next_occurrence)
|
||||
now = timezone.now()
|
||||
today_start = timezone.localtime(now).replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
next_occurrence = Min("occurrences__start", filter=Q(occurrences__start__gte=today_start))
|
||||
return self.filter(occurrences__start__gte=today_start).annotate(
|
||||
next_occurrence=next_occurrence
|
||||
)
|
||||
|
||||
|
||||
EventPageManager = PageManager.from_queryset(EventPageQuerySet)
|
||||
|
||||
|
||||
class EventPage(WPImportedPageMixin, Page):
|
||||
class EventPage(HeadlessMixin, WPImportedPageMixin, Page):
|
||||
subpage_types = []
|
||||
parent_page_types = ["events.EventIndex"]
|
||||
show_in_menus = False
|
||||
@@ -355,7 +359,7 @@ class EventPage(WPImportedPageMixin, Page):
|
||||
GraphQLImage("featured_image"),
|
||||
GraphQLRichText("lead"),
|
||||
GraphQLStreamfield("body"),
|
||||
GraphQLString("pig"),
|
||||
GraphQLString("pig", required=True),
|
||||
GraphQLString("ticket_url"),
|
||||
GraphQLString("facebook_url"),
|
||||
GraphQLBoolean("free"),
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
|
||||
# Create your tests here.
|
||||
@@ -4,13 +4,14 @@ from wagtail.admin.panels import FieldPanel
|
||||
from wagtail.fields import RichTextField, StreamField
|
||||
from wagtail.models import Page
|
||||
from wagtail.search import index
|
||||
from wagtail_headless_preview.models import HeadlessMixin
|
||||
|
||||
from dnscms.blocks import PageSectionBlock
|
||||
from dnscms.fields import BASE_BLOCKS
|
||||
from dnscms.options import ALL_PIGS
|
||||
|
||||
|
||||
class GenericPage(Page):
|
||||
class GenericPage(HeadlessMixin, Page):
|
||||
subpage_types = ["generic.GenericPage"]
|
||||
show_in_menus = True
|
||||
|
||||
|
||||
@@ -10,9 +10,10 @@ from wagtail.admin.panels import (
|
||||
PageChooserPanel,
|
||||
)
|
||||
from wagtail.models import Orderable, Page
|
||||
from wagtail_headless_preview.models import HeadlessMixin
|
||||
|
||||
|
||||
class HomePage(Page):
|
||||
class HomePage(HeadlessMixin, Page):
|
||||
max_count = 1
|
||||
|
||||
content_panels = Page.content_panels + [
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.5 on 2025-08-12 21:00
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('images', '0004_alter_customimage_alt_alter_customimage_attribution'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='customimage',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, default='', max_length=255, verbose_name='description'),
|
||||
),
|
||||
]
|
||||
@@ -1,3 +0,0 @@
|
||||
[tools]
|
||||
python = "3.13"
|
||||
uv = "latest"
|
||||
@@ -5,13 +5,14 @@ from wagtail.admin.panels import FieldPanel
|
||||
from wagtail.fields import RichTextField
|
||||
from wagtail.models import Page
|
||||
from wagtail.search import index
|
||||
from wagtail_headless_preview.models import HeadlessMixin
|
||||
|
||||
from dnscms.fields import CommonStreamField
|
||||
from dnscms.wordpress.models import WPImportedPageMixin
|
||||
|
||||
|
||||
@register_singular_query_field("newsIndex")
|
||||
class NewsIndex(Page):
|
||||
class NewsIndex(HeadlessMixin, Page):
|
||||
max_count = 1
|
||||
subpage_types = ["news.NewsPage"]
|
||||
|
||||
@@ -28,7 +29,7 @@ class NewsIndex(Page):
|
||||
search_fields = []
|
||||
|
||||
|
||||
class NewsPage(WPImportedPageMixin, Page):
|
||||
class NewsPage(HeadlessMixin, WPImportedPageMixin, Page):
|
||||
subpage_types = []
|
||||
parent_page_types = ["news.NewsIndex"]
|
||||
show_in_menus = False
|
||||
|
||||
@@ -6,7 +6,8 @@ from modelcluster.fields import ParentalKey
|
||||
from modelcluster.models import ClusterableModel
|
||||
from wagtail import blocks
|
||||
from wagtail.admin.panels import FieldPanel, InlinePanel, MultiFieldPanel, TitleFieldPanel
|
||||
from wagtail.models import Orderable, StreamField
|
||||
from wagtail.fields import StreamField
|
||||
from wagtail.models import Orderable
|
||||
from wagtail.snippets.models import register_snippet
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
+32
-10
@@ -3,20 +3,26 @@ name = "dnscms"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
authors = [{ name = "EDB", email = "edb@neuf.no" }]
|
||||
requires-python = ">=3.12, <3.13"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.14, <3.15"
|
||||
dependencies = [
|
||||
"wagtail>=6.1.3,<7",
|
||||
"django>=5.0.7,<6",
|
||||
"wagtail-grapple>=0.26.0,<0.27",
|
||||
"psycopg2-binary>=2.9.10,<3",
|
||||
"django-extensions>=3.2.3,<4",
|
||||
"whitenoise>=6.7.0,<7",
|
||||
"gunicorn>=23.0.0",
|
||||
"wagtail>=7.4.1,<8",
|
||||
"wagtail-grapple>=0.31.0,<0.32",
|
||||
"wagtail-headless-preview>=0.8,<0.9",
|
||||
"django>=6.0.5,<7",
|
||||
"django-extensions>=4.1,<5",
|
||||
"psycopg2-binary>=2.9.12,<3",
|
||||
"gunicorn>=26.0.0,<27",
|
||||
"whitenoise>=6.12.0,<7",
|
||||
]
|
||||
|
||||
[dependency-groups]
|
||||
dev = ["ruff"]
|
||||
dev = [
|
||||
"ruff>=0.15.13,<0.16",
|
||||
"pytest>=9.0.3,<10",
|
||||
"pytest-cov>=7.0.0,<8",
|
||||
"pytest-django>=4.12.0,<5",
|
||||
"wagtail-factories>=4.4.0,<5",
|
||||
]
|
||||
|
||||
[tool.uv]
|
||||
package = false
|
||||
@@ -32,3 +38,19 @@ line-length = 99
|
||||
select = ["F", "E", "W", "Q", "UP", "DJ"]
|
||||
ignore = []
|
||||
exclude = ["**/migrations/*.py"]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
DJANGO_SETTINGS_MODULE = "dnscms.settings.test"
|
||||
python_files = ["test_*.py"]
|
||||
testpaths = ["tests"]
|
||||
addopts = "--cov=. --cov-report=term-missing"
|
||||
|
||||
[tool.coverage.run]
|
||||
omit = [
|
||||
"*/migrations/*",
|
||||
"tests/*",
|
||||
"manage.py",
|
||||
"dnscms/settings/*",
|
||||
"dnscms/wsgi.py",
|
||||
"dnscms/asgi.py",
|
||||
]
|
||||
|
||||
@@ -8,6 +8,7 @@ from wagtail.fields import RichTextField, StreamField
|
||||
from wagtail.images.blocks import ImageChooserBlock
|
||||
from wagtail.models import Page
|
||||
from wagtail.search import index
|
||||
from wagtail_headless_preview.models import HeadlessMixin
|
||||
|
||||
from dnscms.blocks import BASE_BLOCKS
|
||||
|
||||
@@ -34,7 +35,7 @@ class SponsorBlock(blocks.StructBlock):
|
||||
|
||||
|
||||
@register_singular_query_field("sponsorsPage")
|
||||
class SponsorsPage(Page):
|
||||
class SponsorsPage(HeadlessMixin, Page):
|
||||
max_count = 1
|
||||
subpage_types = []
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class StudioConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "studio"
|
||||
@@ -0,0 +1,32 @@
|
||||
# Generated by Django 6.0.5 on 2026-05-19 02:59
|
||||
|
||||
import django.db.models.deletion
|
||||
import wagtail.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('images', '0005_customimage_description'),
|
||||
('wagtailcore', '0097_baselogentry_uuid_action_timestamp_indexes'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='StudioPage',
|
||||
fields=[
|
||||
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')),
|
||||
('lead', wagtail.fields.RichTextField(blank=True)),
|
||||
('body', wagtail.fields.StreamField([('paragraph', 0), ('image', 4), ('image_slider', 8), ('horizontal_rule', 10), ('featured', 18), ('page_section_navigation', 19), ('accordion', 23), ('fact_box', 26), ('embed', 27), ('raw_html', 28), ('page_section', 33)], block_lookup={0: ('wagtail.blocks.RichTextBlock', (), {'label': 'Rik tekst'}), 1: ('wagtail.images.blocks.ImageChooserBlock', (), {'label': 'Bilde'}), 2: ('wagtail.blocks.ChoiceBlock', [], {'choices': [('fullwidth', 'Fullbredde'), ('bleed', 'Utfallende'), ('original', 'Uendret størrelse')], 'icon': 'cup', 'label': 'Bildeformat'}), 3: ('wagtail.blocks.CharBlock', (), {'label': 'Bildetekst', 'max_length': 512, 'required': False}), 4: ('wagtail.blocks.StructBlock', [[('image', 1), ('image_format', 2), ('text', 3)]], {}), 5: ('wagtail.blocks.CharBlock', (), {'label': 'Tekst', 'max_length': 512, 'required': False}), 6: ('wagtail.blocks.StructBlock', [[('image', 1), ('text', 5)]], {}), 7: ('wagtail.blocks.ListBlock', (6,), {'label': 'Bilder', 'min_num': 1}), 8: ('wagtail.blocks.StructBlock', [[('images', 7)]], {}), 9: ('wagtail.blocks.ChoiceBlock', [], {'choices': [('deepBrick', 'Dyp tegl'), ('neufPink', 'Griserosa'), ('goldenOrange', 'Gyllen oransje'), ('goldenBeige', 'Gyllen beige'), ('chateauBlue', 'Slottsblå')], 'label': 'Farge', 'required': False}), 10: ('wagtail.blocks.StructBlock', [[('color', 9)]], {}), 11: ('wagtail.blocks.CharBlock', (), {'label': 'Tittel', 'max_length': 64, 'required': True}), 12: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link'], 'label': 'Tekst', 'required': True}), 13: ('wagtail.blocks.PageChooserBlock', (), {'header': 'Fremhevet side', 'required': True}), 14: ('wagtail.blocks.CharBlock', (), {'default': 'Les mer', 'help_text': 'Lenketeksten som tar deg videre til siden. Tips: Ikke start med "Trykk her"', 'label': 'Lenketekst', 'max_length': 64, 'required': True}), 15: ('wagtail.blocks.ChoiceBlock', [], {'choices': [('betongGray', 'Betonggrå'), ('deepBrick', 'Dyp tegl'), ('neufPink', 'Griserosa'), ('goldenOrange', 'Gyllen oransje'), ('goldenBeige', 'Gyllen beige'), ('chateauBlue', 'Slottsblå')], 'label': 'Bakgrunnsfarge'}), 16: ('wagtail.blocks.ChoiceBlock', [], {'choices': [('left', 'Venstre'), ('right', 'Høyre')], 'label': 'Bildeplassering'}), 17: ('wagtail.images.blocks.ImageChooserBlock', (), {'header': 'Overstyr bilde', 'help_text': 'Bildet som er tilknyttet undersiden du vil fremheve, vil automatisk brukes. Om det mangler eller du vil overstyre hvilket bilde som et brukes, kan du velge et her.', 'required': False}), 18: ('wagtail.blocks.StructBlock', [[('title', 11), ('text', 12), ('featured_page', 13), ('link_text', 14), ('background_color', 15), ('image_position', 16), ('featured_image_override', 17)]], {}), 19: ('dnscms.blocks.PageSectionNavigationBlock', (), {}), 20: ('wagtail.blocks.CharBlock', (), {'label': 'Overskrift', 'max_length': 64, 'required': True}), 21: ('wagtail.blocks.StructBlock', [[('image', 1), ('image_format', 2), ('text', 3)]], {'label': 'Bilde'}), 22: ('wagtail.blocks.StreamBlock', [[('paragraph', 0), ('image', 21)]], {}), 23: ('wagtail.blocks.StructBlock', [[('heading', 20), ('body', 22)]], {}), 24: ('wagtail.blocks.ChoiceBlock', [], {'choices': [('betongGray', 'Betonggrå'), ('deepBrick', 'Dyp tegl'), ('neufPink', 'Griserosa'), ('goldenOrange', 'Gyllen oransje'), ('goldenBeige', 'Gyllen beige'), ('chateauBlue', 'Slottsblå')], 'label': 'Bakgrunnsfarge', 'required': False}), 25: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link', 'ol', 'ul', 'h2', 'h3'], 'label': 'Innhold'}), 26: ('wagtail.blocks.StructBlock', [[('background_color', 24), ('body', 25)]], {}), 27: ('wagtail.embeds.blocks.EmbedBlock', (), {}), 28: ('wagtail.blocks.RawHTMLBlock', (), {}), 29: ('wagtail.blocks.ChoiceBlock', [], {'choices': [('pigHeadLogo', 'Grisehodelogo'), ('key', 'Nøkkel'), ('ticket', 'Billett'), ('shield', 'Skjold'), ('bottle', 'Flaske'), ('lostProperty', 'Hittegods'), ('pigsty', 'Grisebinge'), ('wheelchair', 'Rullestol'), ('clock', 'Klokke'), ('parking', 'Parkering'), ('coins', 'Mynter')], 'label': 'Ikon', 'required': False}), 30: ('dnscms.blocks.NeufAddressSectionBlock', (), {}), 31: ('dnscms.blocks.OpeningHoursSectionBlock', (), {}), 32: ('wagtail.blocks.StreamBlock', [[('paragraph', 0), ('image', 4), ('image_slider', 8), ('horizontal_rule', 10), ('featured', 18), ('accordion', 23), ('fact_box', 26), ('embed', 27), ('raw_html', 28), ('neuf_address', 30), ('opening_hours', 31)]], {}), 33: ('wagtail.blocks.StructBlock', [[('title', 11), ('background_color', 24), ('icon', 29), ('body', 32)]], {})})),
|
||||
('pig', models.CharField(blank=True, choices=[('', 'Ingen'), ('logo', 'Logogrisen'), ('music', 'Musikergrisen'), ('drink', 'Drikkegrisen'), ('dance', 'Dansegrisen'), ('point', 'Pekegrisen'), ('student', 'Studentgrisen'), ('listen', 'Lyttegrisen'), ('guard', 'Vaktgrisen'), ('key', 'Nøkkelgrisen'), ('chill', 'Liggegrisen'), ('peek', 'Tittegrisen')], default='', help_text='Grisen nedi hjørnet.', max_length=32)),
|
||||
('logo', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.customimage')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=('wagtailcore.page',),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,64 @@
|
||||
from django.db import models
|
||||
from grapple.helpers import register_singular_query_field
|
||||
from grapple.models import (
|
||||
GraphQLImage,
|
||||
GraphQLRichText,
|
||||
GraphQLStreamfield,
|
||||
GraphQLString,
|
||||
)
|
||||
from wagtail.admin.panels import FieldPanel
|
||||
from wagtail.fields import RichTextField, StreamField
|
||||
from wagtail.models import Page
|
||||
from wagtail.search import index
|
||||
from wagtail_headless_preview.models import HeadlessMixin
|
||||
|
||||
from dnscms.blocks import BASE_BLOCKS, PageSectionBlock
|
||||
from dnscms.options import ALL_PIGS
|
||||
|
||||
|
||||
@register_singular_query_field("studioPage")
|
||||
class StudioPage(HeadlessMixin, Page):
|
||||
max_count = 1
|
||||
subpage_types = []
|
||||
show_in_menus = True
|
||||
|
||||
logo = models.ForeignKey(
|
||||
"images.CustomImage",
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name="+",
|
||||
)
|
||||
lead = RichTextField(features=["link"], blank=True)
|
||||
body = StreamField(BASE_BLOCKS + [("page_section", PageSectionBlock())])
|
||||
|
||||
PIG_CHOICES = [
|
||||
("", "Ingen"),
|
||||
] + ALL_PIGS
|
||||
|
||||
pig = models.CharField(
|
||||
max_length=32,
|
||||
choices=PIG_CHOICES,
|
||||
default="",
|
||||
blank=True,
|
||||
help_text="Grisen nedi hjørnet.",
|
||||
)
|
||||
|
||||
content_panels = Page.content_panels + [
|
||||
FieldPanel("logo"),
|
||||
FieldPanel("lead", heading="Ingress"),
|
||||
FieldPanel("body", heading="Innhold"),
|
||||
FieldPanel("pig", heading="Gris"),
|
||||
]
|
||||
|
||||
graphql_fields = [
|
||||
GraphQLImage("logo"),
|
||||
GraphQLRichText("lead"),
|
||||
GraphQLStreamfield("body"),
|
||||
GraphQLString("pig", required=True),
|
||||
]
|
||||
|
||||
search_fields = Page.search_fields + [
|
||||
index.SearchField("lead"),
|
||||
index.SearchField("body"),
|
||||
]
|
||||
@@ -0,0 +1,144 @@
|
||||
import json
|
||||
|
||||
import factory
|
||||
import pytest
|
||||
import wagtail_factories
|
||||
from wagtail.models import Page
|
||||
|
||||
from associations.models import AssociationIndex, AssociationPage
|
||||
from events.models import EventIndex, EventPage
|
||||
from generic.models import GenericPage
|
||||
from images.models import CustomImage
|
||||
from news.models import NewsIndex, NewsPage
|
||||
from studio.models import StudioPage
|
||||
from venues.models import VenueIndex, VenuePage
|
||||
|
||||
|
||||
class CustomImageFactory(wagtail_factories.ImageFactory):
|
||||
class Meta:
|
||||
model = CustomImage
|
||||
|
||||
|
||||
class AssociationIndexFactory(wagtail_factories.PageFactory):
|
||||
title = factory.Sequence(lambda n: f"Associations {n}")
|
||||
lead = "<p>Foreninger og utvalg.</p>"
|
||||
|
||||
class Meta:
|
||||
model = AssociationIndex
|
||||
|
||||
|
||||
class AssociationPageFactory(wagtail_factories.PageFactory):
|
||||
title = factory.Sequence(lambda n: f"Association {n}")
|
||||
excerpt = "Et utdrag."
|
||||
|
||||
class Meta:
|
||||
model = AssociationPage
|
||||
|
||||
|
||||
class EventIndexFactory(wagtail_factories.PageFactory):
|
||||
title = factory.Sequence(lambda n: f"Events {n}")
|
||||
|
||||
class Meta:
|
||||
model = EventIndex
|
||||
|
||||
|
||||
class EventPageFactory(wagtail_factories.PageFactory):
|
||||
title = factory.Sequence(lambda n: f"Event {n}")
|
||||
|
||||
class Meta:
|
||||
model = EventPage
|
||||
|
||||
|
||||
class GenericPageFactory(wagtail_factories.PageFactory):
|
||||
title = factory.Sequence(lambda n: f"Page {n}")
|
||||
lead = "<p>Ingress.</p>"
|
||||
|
||||
class Meta:
|
||||
model = GenericPage
|
||||
|
||||
|
||||
class StudioPageFactory(wagtail_factories.PageFactory):
|
||||
title = factory.Sequence(lambda n: f"Studio {n}")
|
||||
|
||||
class Meta:
|
||||
model = StudioPage
|
||||
|
||||
|
||||
class NewsIndexFactory(wagtail_factories.PageFactory):
|
||||
title = factory.Sequence(lambda n: f"News {n}")
|
||||
|
||||
class Meta:
|
||||
model = NewsIndex
|
||||
|
||||
|
||||
class NewsPageFactory(wagtail_factories.PageFactory):
|
||||
title = factory.Sequence(lambda n: f"Article {n}")
|
||||
excerpt = "Et utdrag."
|
||||
|
||||
class Meta:
|
||||
model = NewsPage
|
||||
|
||||
|
||||
class VenueIndexFactory(wagtail_factories.PageFactory):
|
||||
title = factory.Sequence(lambda n: f"Venues {n}")
|
||||
|
||||
class Meta:
|
||||
model = VenueIndex
|
||||
|
||||
|
||||
class VenuePageFactory(wagtail_factories.PageFactory):
|
||||
title = factory.Sequence(lambda n: f"Venue {n}")
|
||||
|
||||
class Meta:
|
||||
model = VenuePage
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def root_page(db):
|
||||
return Page.objects.get(depth=1)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def home_page(root_page):
|
||||
# Wagtail's initial migration creates a default "Welcome" page at depth=2.
|
||||
# Reuse it so we don't fight slug collisions across tests.
|
||||
return root_page.get_children().first() or root_page.add_child(
|
||||
instance=Page(title="Home", slug="home")
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def event_index(home_page):
|
||||
return EventIndexFactory(parent=home_page)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def news_index(home_page):
|
||||
return NewsIndexFactory(parent=home_page)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def association_index(home_page):
|
||||
return AssociationIndexFactory(parent=home_page)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def venue(home_page):
|
||||
venue_index = VenueIndexFactory(parent=home_page)
|
||||
return VenuePageFactory(parent=venue_index)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def graphql_post(client):
|
||||
def _post(query, variables=None):
|
||||
payload = {"query": query}
|
||||
if variables is not None:
|
||||
payload["variables"] = variables
|
||||
response = client.post(
|
||||
"/api/graphql/",
|
||||
data=json.dumps(payload),
|
||||
content_type="application/json",
|
||||
)
|
||||
return response, response.json()
|
||||
|
||||
return _post
|
||||
@@ -0,0 +1,389 @@
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import pytest
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils import timezone
|
||||
|
||||
from events.models import (
|
||||
EventCategory,
|
||||
EventOccurrence,
|
||||
EventOrganizer,
|
||||
EventOrganizerLink,
|
||||
EventPage,
|
||||
)
|
||||
from tests.conftest import (
|
||||
AssociationPageFactory,
|
||||
CustomImageFactory,
|
||||
EventPageFactory,
|
||||
)
|
||||
|
||||
|
||||
def test_eventpage_clean_unsets_specific_pricing_when_free():
|
||||
page = EventPage(
|
||||
title="Free event",
|
||||
slug="free-event",
|
||||
free=True,
|
||||
price_regular="100",
|
||||
price_student="50",
|
||||
price_member="25",
|
||||
)
|
||||
|
||||
page.clean()
|
||||
|
||||
assert page.price_regular == ""
|
||||
assert page.price_student == ""
|
||||
assert page.price_member == ""
|
||||
|
||||
|
||||
def test_eventpage_clean_keeps_specific_pricing_when_not_free():
|
||||
page = EventPage(
|
||||
title="Paid event",
|
||||
slug="paid-event",
|
||||
free=False,
|
||||
price_regular="100",
|
||||
price_student="50",
|
||||
price_member="25",
|
||||
)
|
||||
|
||||
page.clean()
|
||||
|
||||
assert page.price_regular == "100"
|
||||
assert page.price_student == "50"
|
||||
assert page.price_member == "25"
|
||||
|
||||
|
||||
def test_eventpage_clean_dedupes_organizers_by_name(event_index):
|
||||
org_a = EventOrganizer.objects.create(name="DNS", slug="dns-a")
|
||||
org_b = EventOrganizer.objects.create(name="DNS", slug="dns-b")
|
||||
|
||||
event = EventPageFactory(parent=event_index)
|
||||
EventOrganizerLink.objects.create(event=event, organizer=org_a)
|
||||
EventOrganizerLink.objects.create(event=event, organizer=org_b)
|
||||
|
||||
event = EventPage.objects.get(pk=event.pk)
|
||||
assert event.organizer_links.count() == 2
|
||||
|
||||
event.clean()
|
||||
|
||||
assert event.organizer_links.count() == 1
|
||||
|
||||
|
||||
def test_eventpage_clean_dedupes_three_duplicates_and_keeps_distinct(event_index):
|
||||
dup_1 = EventOrganizer.objects.create(name="DNS", slug="dns-1")
|
||||
dup_2 = EventOrganizer.objects.create(name="DNS", slug="dns-2")
|
||||
dup_3 = EventOrganizer.objects.create(name="DNS", slug="dns-3")
|
||||
distinct = EventOrganizer.objects.create(name="Studentersamfundet", slug="ss")
|
||||
|
||||
event = EventPageFactory(parent=event_index)
|
||||
for organizer in (dup_1, dup_2, dup_3, distinct):
|
||||
EventOrganizerLink.objects.create(event=event, organizer=organizer)
|
||||
|
||||
event = EventPage.objects.get(pk=event.pk)
|
||||
assert event.organizer_links.count() == 4
|
||||
|
||||
event.clean()
|
||||
|
||||
names = sorted(link.organizer.name for link in event.organizer_links.all())
|
||||
assert names == ["DNS", "Studentersamfundet"]
|
||||
|
||||
|
||||
def test_eventoccurrence_clean_rejects_both_venue_and_venue_custom(event_index, venue):
|
||||
event = EventPageFactory(parent=event_index)
|
||||
occurrence = EventOccurrence(
|
||||
event=event,
|
||||
start=timezone.now(),
|
||||
venue=venue,
|
||||
venue_custom="Frederikkeplassen",
|
||||
)
|
||||
|
||||
with pytest.raises(ValidationError) as exc:
|
||||
occurrence.clean()
|
||||
assert "venue_custom" in exc.value.message_dict
|
||||
|
||||
|
||||
def test_eventoccurrence_clean_requires_venue_or_venue_custom(event_index):
|
||||
event = EventPageFactory(parent=event_index)
|
||||
occurrence = EventOccurrence(event=event, start=timezone.now())
|
||||
|
||||
with pytest.raises(ValidationError) as exc:
|
||||
occurrence.clean()
|
||||
assert "venue" in exc.value.message_dict
|
||||
|
||||
|
||||
def test_eventpage_manager_future_filters_past_and_annotates(event_index):
|
||||
now = timezone.now()
|
||||
|
||||
past = EventPageFactory(parent=event_index, title="Past")
|
||||
EventOccurrence.objects.create(event=past, start=now - timedelta(days=7), venue_custom="Old")
|
||||
|
||||
future = EventPageFactory(parent=event_index, title="Future")
|
||||
EventOccurrence.objects.create(event=future, start=now + timedelta(days=7), venue_custom="New")
|
||||
|
||||
results = list(EventPage.objects.live().future().order_by("next_occurrence"))
|
||||
|
||||
assert [p.pk for p in results] == [future.pk]
|
||||
assert results[0].next_occurrence is not None
|
||||
|
||||
|
||||
def test_future_includes_occurrence_late_today(event_index):
|
||||
today_start = timezone.localtime(timezone.now()).replace(
|
||||
hour=0, minute=0, second=0, microsecond=0
|
||||
)
|
||||
late_today = today_start + timedelta(hours=23, minutes=59)
|
||||
|
||||
event = EventPageFactory(parent=event_index, title="Late today")
|
||||
EventOccurrence.objects.create(event=event, start=late_today, venue_custom="X")
|
||||
|
||||
assert event.pk in EventPage.objects.future().values_list("pk", flat=True)
|
||||
|
||||
|
||||
def test_future_excludes_occurrence_just_before_today(event_index):
|
||||
today_start = timezone.localtime(timezone.now()).replace(
|
||||
hour=0, minute=0, second=0, microsecond=0
|
||||
)
|
||||
just_before_today = today_start - timedelta(seconds=1)
|
||||
|
||||
event = EventPageFactory(parent=event_index, title="Just past")
|
||||
EventOccurrence.objects.create(event=event, start=just_before_today, venue_custom="X")
|
||||
|
||||
assert event.pk not in EventPage.objects.future().values_list("pk", flat=True)
|
||||
|
||||
|
||||
def test_future_next_occurrence_picks_earliest_future_ignoring_past(event_index):
|
||||
now = timezone.now()
|
||||
soonest_future = now + timedelta(days=3)
|
||||
|
||||
event = EventPageFactory(parent=event_index, title="With history")
|
||||
EventOccurrence.objects.create(event=event, start=now - timedelta(days=30), venue_custom="X")
|
||||
EventOccurrence.objects.create(event=event, start=soonest_future, venue_custom="X")
|
||||
EventOccurrence.objects.create(event=event, start=now + timedelta(days=10), venue_custom="X")
|
||||
|
||||
annotated = EventPage.objects.future().filter(pk=event.pk).first()
|
||||
assert annotated is not None
|
||||
assert abs((annotated.next_occurrence - soonest_future).total_seconds()) < 1
|
||||
|
||||
|
||||
def test_graphql_event_index_future_events_query(event_index, graphql_post):
|
||||
upcoming = EventPageFactory(parent=event_index, title="Upcoming gig")
|
||||
EventOccurrence.objects.create(
|
||||
event=upcoming,
|
||||
start=timezone.now() + timedelta(days=3),
|
||||
venue_custom="Storsalen",
|
||||
)
|
||||
|
||||
response, body = graphql_post(
|
||||
"""
|
||||
query {
|
||||
eventIndex {
|
||||
futureEvents { title }
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "errors" not in body, body
|
||||
titles = [e["title"] for e in body["data"]["eventIndex"]["futureEvents"]]
|
||||
assert "Upcoming gig" in titles
|
||||
|
||||
|
||||
def test_graphql_event_index_future_events_ordered_by_next_occurrence(event_index, graphql_post):
|
||||
now = timezone.now()
|
||||
|
||||
later = EventPageFactory(parent=event_index, title="Later gig")
|
||||
EventOccurrence.objects.create(event=later, start=now + timedelta(days=10), venue_custom="X")
|
||||
|
||||
sooner = EventPageFactory(parent=event_index, title="Sooner gig")
|
||||
EventOccurrence.objects.create(event=sooner, start=now + timedelta(days=3), venue_custom="X")
|
||||
|
||||
response, body = graphql_post(
|
||||
"""
|
||||
query {
|
||||
eventIndex {
|
||||
futureEvents { title }
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "errors" not in body, body
|
||||
titles = [e["title"] for e in body["data"]["eventIndex"]["futureEvents"]]
|
||||
assert titles.index("Sooner gig") < titles.index("Later gig")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def comprehensive_event(event_index, venue, association_index):
|
||||
"""A fully-populated paid EventPage exercising every field exposed via GraphQL."""
|
||||
image = CustomImageFactory(
|
||||
title="Cover",
|
||||
alt="Et fotografi av en gris med solbriller",
|
||||
attribution="Foto: Test",
|
||||
)
|
||||
|
||||
konsert = EventCategory.objects.create(
|
||||
name="Konsert", slug="konsert", show_in_filters=True, pig="pigHeadLogo"
|
||||
)
|
||||
klubb = EventCategory.objects.create(name="Klubb", slug="klubb")
|
||||
|
||||
association = AssociationPageFactory(
|
||||
parent=association_index,
|
||||
title="Internal",
|
||||
association_type="forening",
|
||||
)
|
||||
internal_org = EventOrganizer.objects.create(
|
||||
name="Internal", slug="internal", association=association
|
||||
)
|
||||
external_org = EventOrganizer.objects.create(
|
||||
name="External",
|
||||
slug="external",
|
||||
external_url="https://external.example.com",
|
||||
)
|
||||
|
||||
event = EventPageFactory(
|
||||
parent=event_index,
|
||||
title="Et arrangement",
|
||||
slug="et-arrangement",
|
||||
subtitle="En undertekst",
|
||||
lead="<p>Ingress.</p>",
|
||||
body=[("paragraph", "<p>Body content.</p>")],
|
||||
pig="automatic",
|
||||
free=False,
|
||||
price_regular="150",
|
||||
price_student="100",
|
||||
price_member="75",
|
||||
ticket_url="https://example.com/tickets",
|
||||
facebook_url="https://facebook.com/example",
|
||||
featured_image=image,
|
||||
)
|
||||
event.categories.add(konsert, klubb)
|
||||
EventOrganizerLink.objects.create(event=event, organizer=internal_org)
|
||||
EventOrganizerLink.objects.create(event=event, organizer=external_org)
|
||||
|
||||
now = timezone.now()
|
||||
EventOccurrence.objects.create(
|
||||
event=event,
|
||||
start=now + timedelta(days=5),
|
||||
end=now + timedelta(days=5, hours=3),
|
||||
venue=venue,
|
||||
)
|
||||
EventOccurrence.objects.create(
|
||||
event=event,
|
||||
start=now + timedelta(days=12),
|
||||
end=now + timedelta(days=12, hours=2),
|
||||
venue_custom="Frederikkeplassen",
|
||||
)
|
||||
|
||||
event.save()
|
||||
return event
|
||||
|
||||
|
||||
def test_graphql_event_index_returns_all_fields_for_comprehensive_event(
|
||||
comprehensive_event, graphql_post
|
||||
):
|
||||
response, body = graphql_post(
|
||||
"""
|
||||
query {
|
||||
eventIndex {
|
||||
futureEvents {
|
||||
title
|
||||
slug
|
||||
subtitle
|
||||
lead
|
||||
body {
|
||||
blockType
|
||||
field
|
||||
... on RichTextBlock {
|
||||
value
|
||||
}
|
||||
}
|
||||
pig
|
||||
free
|
||||
priceRegular
|
||||
priceStudent
|
||||
priceMember
|
||||
ticketUrl
|
||||
facebookUrl
|
||||
featuredImage {
|
||||
alt
|
||||
attribution
|
||||
}
|
||||
categories {
|
||||
name
|
||||
slug
|
||||
showInFilters
|
||||
pig
|
||||
}
|
||||
organizers {
|
||||
name
|
||||
slug
|
||||
externalUrl
|
||||
association {
|
||||
title
|
||||
}
|
||||
}
|
||||
occurrences {
|
||||
start
|
||||
end
|
||||
venueCustom
|
||||
venue {
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "errors" not in body, body
|
||||
|
||||
events = body["data"]["eventIndex"]["futureEvents"]
|
||||
event = next(e for e in events if e["title"] == "Et arrangement")
|
||||
|
||||
assert event["slug"] == "et-arrangement"
|
||||
assert event["subtitle"] == "En undertekst"
|
||||
assert "Ingress." in event["lead"]
|
||||
assert event["pig"] == "automatic"
|
||||
assert event["free"] is False
|
||||
assert event["priceRegular"] == "150"
|
||||
assert event["priceStudent"] == "100"
|
||||
assert event["priceMember"] == "75"
|
||||
assert event["ticketUrl"] == "https://example.com/tickets"
|
||||
assert event["facebookUrl"] == "https://facebook.com/example"
|
||||
|
||||
assert event["featuredImage"]["alt"] == "Et fotografi av en gris med solbriller"
|
||||
assert event["featuredImage"]["attribution"] == "Foto: Test"
|
||||
|
||||
assert event["body"][0]["blockType"] == "RichTextBlock"
|
||||
assert "Body content." in event["body"][0]["value"]
|
||||
|
||||
categories_by_name = {c["name"]: c for c in event["categories"]}
|
||||
assert set(categories_by_name) == {"Konsert", "Klubb"}
|
||||
assert categories_by_name["Konsert"]["slug"] == "konsert"
|
||||
assert categories_by_name["Konsert"]["showInFilters"] is True
|
||||
assert categories_by_name["Konsert"]["pig"] == "pigHeadLogo"
|
||||
assert categories_by_name["Klubb"]["showInFilters"] is False
|
||||
|
||||
organizers_by_name = {o["name"]: o for o in event["organizers"]}
|
||||
assert set(organizers_by_name) == {"Internal", "External"}
|
||||
assert organizers_by_name["Internal"]["association"]["title"] == "Internal"
|
||||
assert organizers_by_name["Internal"]["externalUrl"] == ""
|
||||
assert organizers_by_name["External"]["association"] is None
|
||||
assert organizers_by_name["External"]["externalUrl"] == "https://external.example.com"
|
||||
|
||||
assert len(event["occurrences"]) == 2
|
||||
venue_occ = next(o for o in event["occurrences"] if o["venue"] is not None)
|
||||
custom_occ = next(o for o in event["occurrences"] if o["venueCustom"])
|
||||
assert venue_occ["venueCustom"] == ""
|
||||
assert venue_occ["venue"]["title"]
|
||||
assert custom_occ["venue"] is None
|
||||
assert custom_occ["venueCustom"] == "Frederikkeplassen"
|
||||
|
||||
venue_occ_db = comprehensive_event.occurrences.exclude(venue=None).get()
|
||||
custom_occ_db = comprehensive_event.occurrences.exclude(venue_custom="").get()
|
||||
assert datetime.fromisoformat(venue_occ["start"]) == venue_occ_db.start
|
||||
assert datetime.fromisoformat(venue_occ["end"]) == venue_occ_db.end
|
||||
assert datetime.fromisoformat(custom_occ["start"]) == custom_occ_db.start
|
||||
assert datetime.fromisoformat(custom_occ["end"]) == custom_occ_db.end
|
||||
@@ -0,0 +1,71 @@
|
||||
from generic.models import GenericPage
|
||||
from tests.conftest import GenericPageFactory
|
||||
|
||||
|
||||
def test_generic_page_persists_via_factory(home_page):
|
||||
page = GenericPageFactory(
|
||||
parent=home_page,
|
||||
title="Om oss",
|
||||
slug="om-oss",
|
||||
lead="<p>Ingress.</p>",
|
||||
body=[("paragraph", "<p>Body content.</p>")],
|
||||
pig="drink",
|
||||
)
|
||||
|
||||
reloaded = GenericPage.objects.get(pk=page.pk)
|
||||
assert reloaded.title == "Om oss"
|
||||
assert reloaded.slug == "om-oss"
|
||||
assert "Ingress." in reloaded.lead
|
||||
assert reloaded.pig == "drink"
|
||||
assert reloaded.body[0].block_type == "paragraph"
|
||||
|
||||
|
||||
def test_generic_page_allows_recursive_children(home_page):
|
||||
parent = GenericPageFactory(parent=home_page, title="Parent", slug="parent")
|
||||
child = GenericPageFactory(parent=parent, title="Child", slug="child")
|
||||
|
||||
assert child.get_parent().specific == parent
|
||||
assert list(parent.get_children().specific()) == [child]
|
||||
|
||||
|
||||
def test_graphql_generic_page_query(home_page, graphql_post):
|
||||
GenericPageFactory(
|
||||
parent=home_page,
|
||||
title="Om oss",
|
||||
slug="om-oss",
|
||||
lead="<p>Ingress text.</p>",
|
||||
body=[("paragraph", "<p>Body content.</p>")],
|
||||
pig="drink",
|
||||
)
|
||||
|
||||
response, body = graphql_post(
|
||||
"""
|
||||
query {
|
||||
page(slug: "om-oss", contentType: "generic.GenericPage") {
|
||||
title
|
||||
slug
|
||||
... on GenericPage {
|
||||
lead
|
||||
pig
|
||||
body {
|
||||
blockType
|
||||
field
|
||||
... on RichTextBlock {
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "errors" not in body, body
|
||||
data = body["data"]["page"]
|
||||
assert data["title"] == "Om oss"
|
||||
assert data["slug"] == "om-oss"
|
||||
assert "Ingress text." in data["lead"]
|
||||
assert data["pig"] == "drink"
|
||||
assert data["body"][0]["blockType"] == "RichTextBlock"
|
||||
assert "Body content." in data["body"][0]["value"]
|
||||
@@ -0,0 +1,6 @@
|
||||
def test_graphql_endpoint_responds(db, graphql_post):
|
||||
response, body = graphql_post("{ __schema { queryType { name } } }")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "errors" not in body
|
||||
assert body["data"]["__schema"]["queryType"]["name"] == "Query"
|
||||
@@ -0,0 +1,26 @@
|
||||
from news.models import NewsPage
|
||||
from tests.conftest import NewsPageFactory
|
||||
|
||||
|
||||
def test_news_page_persists_via_factory(news_index):
|
||||
page = NewsPageFactory(parent=news_index, title="Big news", excerpt="Short summary")
|
||||
|
||||
reloaded = NewsPage.objects.get(pk=page.pk)
|
||||
assert reloaded.title == "Big news"
|
||||
assert reloaded.excerpt == "Short summary"
|
||||
|
||||
|
||||
def test_graphql_news_index_query(news_index, graphql_post):
|
||||
response, body = graphql_post(
|
||||
"""
|
||||
query {
|
||||
newsIndex {
|
||||
title
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "errors" not in body, body
|
||||
assert body["data"]["newsIndex"]["title"] == news_index.title
|
||||
@@ -0,0 +1,91 @@
|
||||
import datetime
|
||||
|
||||
import pytest
|
||||
|
||||
from openinghours.models import OpeningHoursItem, OpeningHoursSet
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def opening_hours_set(db):
|
||||
return OpeningHoursSet.objects.create(
|
||||
name="Vanlige åpningstider",
|
||||
effective_from=datetime.date(2025, 1, 1),
|
||||
)
|
||||
|
||||
|
||||
def test_opening_hours_set_str_with_end_date():
|
||||
ohs = OpeningHoursSet(
|
||||
name="Sommer",
|
||||
effective_from=datetime.date(2025, 6, 1),
|
||||
effective_to=datetime.date(2025, 8, 31),
|
||||
)
|
||||
assert str(ohs) == "Sommer (2025-06-01 - 2025-08-31)"
|
||||
|
||||
|
||||
def test_opening_hours_set_str_uses_infinity_when_open_ended():
|
||||
ohs = OpeningHoursSet(
|
||||
name="Forever",
|
||||
effective_from=datetime.date(2025, 1, 1),
|
||||
effective_to=None,
|
||||
)
|
||||
assert str(ohs) == "Forever (2025-01-01 - ∞)"
|
||||
|
||||
|
||||
def test_opening_hours_streamfield_week_roundtrip(opening_hours_set):
|
||||
OpeningHoursItem.objects.create(
|
||||
opening_hours_set=opening_hours_set,
|
||||
function="glassbaren",
|
||||
week=[
|
||||
(
|
||||
"week",
|
||||
{
|
||||
"monday": {
|
||||
"time_from": datetime.time(15, 0),
|
||||
"time_to": datetime.time(23, 0),
|
||||
"custom": "",
|
||||
},
|
||||
"tuesday": {"time_from": None, "time_to": None, "custom": "Stengt"},
|
||||
"wednesday": {"time_from": None, "time_to": None, "custom": ""},
|
||||
"thursday": {"time_from": None, "time_to": None, "custom": ""},
|
||||
"friday": {"time_from": None, "time_to": None, "custom": ""},
|
||||
"saturday": {"time_from": None, "time_to": None, "custom": ""},
|
||||
"sunday": {"time_from": None, "time_to": None, "custom": ""},
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
reloaded = OpeningHoursSet.objects.get(pk=opening_hours_set.pk)
|
||||
item = reloaded.items.get()
|
||||
assert item.function == "glassbaren"
|
||||
|
||||
week_block = item.week[0]
|
||||
assert week_block.block_type == "week"
|
||||
assert week_block.value["monday"]["time_from"] == datetime.time(15, 0)
|
||||
assert week_block.value["monday"]["time_to"] == datetime.time(23, 0)
|
||||
assert week_block.value["tuesday"]["custom"] == "Stengt"
|
||||
|
||||
|
||||
def test_graphql_opening_hours_sets_query(db, graphql_post):
|
||||
OpeningHoursSet.objects.create(
|
||||
name="Sommer 2025",
|
||||
effective_from=datetime.date(2025, 6, 1),
|
||||
effective_to=datetime.date(2025, 8, 31),
|
||||
)
|
||||
|
||||
response, body = graphql_post(
|
||||
"""
|
||||
query {
|
||||
openingHoursSets {
|
||||
name
|
||||
effectiveFrom
|
||||
effectiveTo
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "errors" not in body, body
|
||||
names = [s["name"] for s in body["data"]["openingHoursSets"]]
|
||||
assert "Sommer 2025" in names
|
||||
@@ -0,0 +1,37 @@
|
||||
"""Round-trip tests for wagtail-headless-preview token resolution via grapple."""
|
||||
|
||||
from generic.models import GenericPage
|
||||
from tests.conftest import GenericPageFactory
|
||||
|
||||
|
||||
def test_generic_page_preview_token_resolves_draft(home_page, graphql_post):
|
||||
"""A minted preview token returns the unsaved draft via grapple's page(token: …)."""
|
||||
# Publish a baseline so there's a live revision to diverge from.
|
||||
page = GenericPageFactory(parent=home_page, title="Original title", slug="generic-preview")
|
||||
|
||||
# Mutate in-memory to simulate unsaved editor state, then mint a token.
|
||||
# create_page_preview() snapshots the current to_json() into a PagePreview row.
|
||||
page.title = "Edited title (draft)"
|
||||
preview = page.create_page_preview()
|
||||
|
||||
response, body = graphql_post(
|
||||
"""
|
||||
query previewPage($token: String!) {
|
||||
page: page(token: $token) {
|
||||
__typename
|
||||
... on GenericPage {
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
""",
|
||||
variables={"token": preview.token},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "errors" not in body, body
|
||||
assert body["data"]["page"]["__typename"] == "GenericPage"
|
||||
assert body["data"]["page"]["title"] == "Edited title (draft)"
|
||||
|
||||
# Live revision is unchanged — token short-circuits the published query.
|
||||
assert GenericPage.objects.get(pk=page.pk).title == "Original title"
|
||||
@@ -0,0 +1,78 @@
|
||||
from studio.models import StudioPage
|
||||
from tests.conftest import CustomImageFactory, StudioPageFactory
|
||||
|
||||
|
||||
def test_studio_page_persists_via_factory(home_page):
|
||||
logo = CustomImageFactory()
|
||||
page = StudioPageFactory(
|
||||
parent=home_page,
|
||||
title="STUDiO",
|
||||
slug="studio",
|
||||
lead="<p>Ingress.</p>",
|
||||
body=[("paragraph", "<p>Body content.</p>")],
|
||||
pig="drink",
|
||||
logo=logo,
|
||||
)
|
||||
|
||||
reloaded = StudioPage.objects.get(pk=page.pk)
|
||||
assert reloaded.title == "STUDiO"
|
||||
assert reloaded.slug == "studio"
|
||||
assert "Ingress." in reloaded.lead
|
||||
assert reloaded.pig == "drink"
|
||||
assert reloaded.body[0].block_type == "paragraph"
|
||||
assert reloaded.logo == logo
|
||||
|
||||
|
||||
def test_studio_page_is_singleton(home_page):
|
||||
StudioPageFactory(parent=home_page, slug="studio")
|
||||
|
||||
assert StudioPage.can_create_at(home_page) is False
|
||||
|
||||
|
||||
def test_graphql_studio_page_query(home_page, graphql_post):
|
||||
logo = CustomImageFactory(alt="STUDiO-logo")
|
||||
StudioPageFactory(
|
||||
parent=home_page,
|
||||
title="STUDiO",
|
||||
slug="studio",
|
||||
lead="<p>Ingress text.</p>",
|
||||
body=[("paragraph", "<p>Body content.</p>")],
|
||||
pig="drink",
|
||||
logo=logo,
|
||||
)
|
||||
|
||||
response, body = graphql_post(
|
||||
"""
|
||||
query {
|
||||
page: studioPage {
|
||||
... on StudioPage {
|
||||
title
|
||||
slug
|
||||
lead
|
||||
pig
|
||||
logo {
|
||||
alt
|
||||
}
|
||||
body {
|
||||
blockType
|
||||
field
|
||||
... on RichTextBlock {
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "errors" not in body, body
|
||||
data = body["data"]["page"]
|
||||
assert data["title"] == "STUDiO"
|
||||
assert data["slug"] == "studio"
|
||||
assert "Ingress text." in data["lead"]
|
||||
assert data["pig"] == "drink"
|
||||
assert data["logo"]["alt"] == "STUDiO-logo"
|
||||
assert data["body"][0]["blockType"] == "RichTextBlock"
|
||||
assert "Body content." in data["body"][0]["value"]
|
||||
Generated
+399
-195
@@ -1,77 +1,113 @@
|
||||
version = 1
|
||||
revision = 2
|
||||
requires-python = "==3.12.*"
|
||||
|
||||
[[package]]
|
||||
name = "aniso8601"
|
||||
version = "9.0.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cb/72/be3db445b03944bfbb2b02b82d00cb2a2bcf96275c4543f14bf60fa79e12/aniso8601-9.0.1.tar.gz", hash = "sha256:72e3117667eedf66951bb2d93f4296a56b94b078a8a95905a052611fb3f1b973", size = 47345, upload-time = "2021-03-02T01:33:22.944Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/04/e97c12dc034791d7b504860acfcdd2963fa21ae61eaca1c9d31245f812c3/aniso8601-9.0.1-py2.py3-none-any.whl", hash = "sha256:1d2b7ef82963909e93c4f24ce48d4de9e66009a21bf1c1e1c85bdd0812fe412f", size = 52754, upload-time = "2021-03-02T01:33:20.669Z" },
|
||||
]
|
||||
revision = 3
|
||||
requires-python = "==3.14.*"
|
||||
|
||||
[[package]]
|
||||
name = "anyascii"
|
||||
version = "0.3.2"
|
||||
version = "0.3.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9f/52/93b9ea99063f7cf37fb67f5e3f49480686cbe7f228c48b9d713326223b6e/anyascii-0.3.2.tar.gz", hash = "sha256:9d5d32ef844fe225b8bc7cba7f950534fae4da27a9bf3a6bea2cb0ea46ce4730", size = 214052, upload-time = "2023-03-16T00:24:42.431Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/db/ba/edebda727008390936da4a9bf677c19cd63b32d51e864656d2cbd1028e25/anyascii-0.3.3.tar.gz", hash = "sha256:c94e9dd9d47b3d9494eca305fef9447d00b4bf1a32aff85aa746fa3ec7fb95c3", size = 264680, upload-time = "2025-06-29T03:33:30.427Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/7b/a9a747e0632271d855da379532b05a62c58e979813814a57fa3b3afeb3a4/anyascii-0.3.2-py3-none-any.whl", hash = "sha256:3b3beef6fc43d9036d3b0529050b0c48bfad8bc960e9e562d7223cfb94fe45d4", size = 289923, upload-time = "2023-03-16T00:24:39.649Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/76/783b75a21ce3563b8709050de030ae253853b147bd52e141edc1025aa268/anyascii-0.3.3-py3-none-any.whl", hash = "sha256:f5ab5e53c8781a36b5a40e1296a0eeda2f48c649ef10c3921c1381b1d00dee7a", size = 345090, upload-time = "2025-06-29T03:33:28.356Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asgiref"
|
||||
version = "3.8.1"
|
||||
version = "3.9.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/29/38/b3395cc9ad1b56d2ddac9970bc8f4141312dbaec28bc7c218b0dfafd0f42/asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590", size = 35186, upload-time = "2024-03-22T14:39:36.863Z" }
|
||||
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" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/39/e3/893e8757be2612e6c266d9bb58ad2e3651524b5b40cf56761e985a28b13e/asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47", size = 23828, upload-time = "2024-03-22T14:39:34.521Z" },
|
||||
{ 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" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "beautifulsoup4"
|
||||
version = "4.12.3"
|
||||
version = "4.13.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "soupsieve" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b3/ca/824b1195773ce6166d388573fc106ce56d4a805bd7427b624e063596ec58/beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051", size = 581181, upload-time = "2024-01-17T16:53:17.902Z" }
|
||||
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" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/fe/e8c672695b37eecc5cbf43e1d0638d88d66ba3a44c4d321c796f4e59167f/beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed", size = 147925, upload-time = "2024-01-17T16:53:12.779Z" },
|
||||
{ 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" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2024.8.30"
|
||||
version = "2025.8.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507, upload-time = "2024-08-30T01:55:04.365Z" }
|
||||
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" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321, upload-time = "2024-08-30T01:55:02.591Z" },
|
||||
{ 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" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "3.3.2"
|
||||
version = "3.4.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/63/09/c1bc53dab74b1816a00d8d030de5bf98f724c52c1635e07681d312f20be8/charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", size = 104809, upload-time = "2023-11-01T04:04:59.997Z" }
|
||||
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" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/b2/fcedc8255ec42afee97f9e6f0145c734bbe104aac28300214593eb326f1d/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", size = 192892, upload-time = "2023-11-01T04:03:24.135Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/7d/2259318c202f3d17f3fe6438149b3b9e706d1070fe3fcbb28049730bb25c/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", size = 122213, upload-time = "2023-11-01T04:03:25.66Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/52/9f9d17c3b54dc238de384c4cb5a2ef0e27985b42a0e5cc8e8a31d918d48d/charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", size = 119404, upload-time = "2023-11-01T04:03:27.04Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/b0/9c365f6d79a9f0f3c379ddb40a256a67aa69c59609608fe7feb6235896e1/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", size = 137275, upload-time = "2023-11-01T04:03:28.466Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/33/749df346e93d7a30cdcb90cbfdd41a06026317bfbfb62cd68307c1a3c543/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", size = 147518, upload-time = "2023-11-01T04:03:29.82Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/1a/641d5c9f59e6af4c7b53da463d07600a695b9824e20849cb6eea8a627761/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", size = 140182, upload-time = "2023-11-01T04:03:31.511Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/fb/14d30eb4956408ee3ae09ad34299131fb383c47df355ddb428a7331cfa1e/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", size = 141869, upload-time = "2023-11-01T04:03:32.887Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/3e/a06b18788ca2eb6695c9b22325b6fde7dde0f1d1838b1792a0076f58fe9d/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", size = 144042, upload-time = "2023-11-01T04:03:34.412Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/59/3d27019d3b447a88fe7e7d004a1e04be220227760264cc41b405e863891b/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", size = 138275, upload-time = "2023-11-01T04:03:35.759Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/ef/5eb105530b4da8ae37d506ccfa25057961b7b63d581def6f99165ea89c7e/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", size = 144819, upload-time = "2023-11-01T04:03:37.216Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/51/e5023f937d7f307c948ed3e5c29c4b7a3e42ed2ee0b8cdf8f3a706089bf0/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", size = 149415, upload-time = "2023-11-01T04:03:38.694Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/9d/2e3ef673dfd5be0154b20363c5cdcc5606f35666544381bee15af3778239/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", size = 141212, upload-time = "2023-11-01T04:03:40.07Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/ae/ce2c12fcac59cb3860b2e2d76dc405253a4475436b1861d95fe75bdea520/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", size = 142167, upload-time = "2023-11-01T04:03:41.491Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/3a/a448bf035dce5da359daf9ae8a16b8a39623cc395a2ffb1620aa1bce62b0/charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", size = 93041, upload-time = "2023-11-01T04:03:42.836Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/7c/8debebb4f90174074b827c63242c23851bdf00a532489fba57fef3416e40/charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", size = 100397, upload-time = "2023-11-01T04:03:44.467Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/76/e6222113b83e3622caa4bb41032d0b1bf785250607392e1b778aca0b8a7d/charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", size = 48543, upload-time = "2023-11-01T04:04:58.622Z" },
|
||||
{ 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/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/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/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/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/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/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/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/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/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/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/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "7.14.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/23/7f/d0720730a397a999ffc0fd3f5bebef347338e3a47b727da66fbb228e2ff2/coverage-7.14.0.tar.gz", hash = "sha256:057a6af2f160a85384cde4ab36f0d2777bae1057bae255f95413cdd382aa5c74", size = 919489, upload-time = "2026-05-10T18:02:31.397Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/18/b9a6586d73992807c26f9a5f274131be3d76b56b18a82b9392e2a25d2e45/coverage-7.14.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9aed9fa983514ca032790f3fe0d1c0e42ca7e16b42432af1706b50a9a46bef5d", size = 220036, upload-time = "2026-05-10T18:01:33.057Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/9b/4165a1d56ddc302a0e2d518fd9d412a4fd0b57562618c78c5f21c57194f5/coverage-7.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ba3b8390db29296dbbf49e91b6fe08f990743a90c8f447ba4c2ffc29670dfa63", size = 220368, upload-time = "2026-05-10T18:01:34.705Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/69/aa/c12e52a5ba148d9995229d557e3be6e554fe469addc0e9241b2f0956d8ea/coverage-7.14.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3a5d8e876dfa2f102e970b183863d6dedd023d3c0eeca1fe7a9787bc5f28b212", size = 251417, upload-time = "2026-05-10T18:01:36.949Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/51/ec641c26e6dca1b25a7d2035ba6ecb7c884ef1a100a9e42fbe4ce4405139/coverage-7.14.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5ebb8f4614a3787d567e610bbfdf96a4798dd69a1afb1bd8ad228d4111fe6ff3", size = 253924, upload-time = "2026-05-10T18:01:38.985Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/c4/59c3de0bd1b538824173fd518fed51c1ce740ca5ed68e74545983f4053a9/coverage-7.14.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b9bf47223dd8db3d4c4b2e443b02bace480d428f0822c3f991600448a176c97", size = 255269, upload-time = "2026-05-10T18:01:40.957Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/a9/36dfa153a62040296f6e7febfdb20a5720622f6ef5a81a41e8237b9a5344/coverage-7.14.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3485a836550b303d006d57cc06e3d5afaabc642c77050b7c985a97b13e3776b8", size = 257583, upload-time = "2026-05-10T18:01:42.607Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/7b/cc2c048d4114d9ab1c2409e9ee365e5ae10736df6dffcfc9444effa6c708/coverage-7.14.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3e7e88110bae996d199d1693ca8ec3fd52441d426401ae963437598667b4c5eb", size = 251434, upload-time = "2026-05-10T18:01:44.537Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/df/6770eaa576e604575e9a78055313250faef5faa84bd6f71a39fece519c43/coverage-7.14.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:15228a6800ce7bdf1b74800595e56db7138cecb338fdbf044806e10dcf182dfe", size = 253280, upload-time = "2026-05-10T18:01:46.175Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/9e/1c0264514a3f98259a6d64765a397b2c8373e3ba59ee722a4802d3ec0c61/coverage-7.14.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9d26ac7f5398bafc5b57421ad994e8a4749e8a7a0e62d05ec7d53014d5963bfa", size = 251241, upload-time = "2026-05-10T18:01:48.732Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/16/4efdf3e3c4079cdbf0ece56a2fea872df9e8a3e15a13a0af4400e1075944/coverage-7.14.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2fb73254ff43c911c967a899e1359bc5049b4b115d6e8fbdde4937d0a2246cd5", size = 255516, upload-time = "2026-05-10T18:01:50.819Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/69/b1de96346603881b3d1bc8d6447c83200e1c9700ffbaff926ba01ff5724c/coverage-7.14.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:454a380af72c6adada298ed270d38c7a391288198dbfb8467f786f588751a90c", size = 251059, upload-time = "2026-05-10T18:01:52.773Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/66/2881853e0363a5e0a724d1103e53650795367471b6afb234f8b49e713bc6/coverage-7.14.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:65c86fb646d2bd2972e96bd1a8b45817ed907cee68655d6295fe7ec031d04cca", size = 252716, upload-time = "2026-05-10T18:01:54.506Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/5c/0d3305d002c41dcde873dbe456491e663dc55152ca526b630b5c47efd62f/coverage-7.14.0-cp314-cp314-win32.whl", hash = "sha256:6a6516b02a6101398e19a3f44820f69bab2590697f7def4331f668b14adaf828", size = 222788, upload-time = "2026-05-10T18:01:56.487Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/58/6e1b8f52fdc3184b47dc5037f5070d83a3d11042db1594b02d2a44d786c8/coverage-7.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:45e0f79d8351fa76e256716df91eab12890d32678b9590df7ae1042e4bd4cf5d", size = 223600, upload-time = "2026-05-10T18:01:58.497Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/70/a18c408e674bc26281cadaedc7351f929bd2094e191e4b15271c30b084cc/coverage-7.14.0-cp314-cp314-win_arm64.whl", hash = "sha256:4b899594a8b2d81e5cc064a0d7f9cac2081fed91049456cae7676787e41549c9", size = 222168, upload-time = "2026-05-10T18:02:00.411Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/89/2681f071d238b62aff8dfc2ab44fc24cfdb38d1c01f391a80522ff5d3a16/coverage-7.14.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f580f8c80acd94ac72e863efe2cab791d8c38d153e0b463b92dfa000d5c84cd1", size = 220766, upload-time = "2026-05-10T18:02:02.313Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/c7/c987babafd9207ffa1995e1ef1f9b26762cf4963aa768a66b6f0501e4616/coverage-7.14.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a2bd259c442cd43c49b30fbafc51776eb19ea396faf159d26a83e6a0a5f13b0c", size = 221035, upload-time = "2026-05-10T18:02:04.017Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/e9/d6a5ac3b333088143d6fc877d398a9a674dc03124a2f776e131f03864823/coverage-7.14.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a706b908dfa85538863504c624b237a3cc34232bf403c057414ebfdb3b4d9f84", size = 262405, upload-time = "2026-05-10T18:02:05.915Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/b1/e70838d29a7c08e22d44398a46db90815bbcbf28de06992bd9210d1a8d8e/coverage-7.14.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7333cd944ee4393b9b3d3c1b598c936d4fc8d70573a4c7dacfec5590dd50e436", size = 264530, upload-time = "2026-05-10T18:02:07.582Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/73/5c31ef97763288d03d9995152b96d5475b527c63d91c84b01caea894b83a/coverage-7.14.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f162bc9a15b82d947b02651b0c7e1609d6f7a8735ca330cfadec8481dd97d5a", size = 266932, upload-time = "2026-05-10T18:02:09.401Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/76/dd56d80f29c5f05b4d76f7e7c6d47cafacae017189c75c5759d24f9ff0cc/coverage-7.14.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:362cb78e01a5dc82009d88004cf60f2e6b6d6fcbfdec05b05af73b0abf40118f", size = 268062, upload-time = "2026-05-10T18:02:11.399Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/c7/27ba85cd5b95614f159ff93ebff1901584a8d192e2e5e24c4943a7453f59/coverage-7.14.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:acebd068fca5512c3a6fde9c045f901613478781a73f0e82b307b214daef23fb", size = 261504, upload-time = "2026-05-10T18:02:13.257Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/2e/e8149f60ab5d5684c6eee881bdf34b127115cddbb958b196768dd9d63473/coverage-7.14.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:29fe3da551dface75deb2ccbf87b6b66e2e7ef38f6d89050b428be94afff3490", size = 264398, upload-time = "2026-05-10T18:02:15.063Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/7f/1261b025285323225f4b4abffa5a643649dfd67e25ddca7ebcbdea3b7cb3/coverage-7.14.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b4cc4fce8672fffcb09b0eafc167b396b3ba53c4a7230f54b7aaffbf6c835fa9", size = 262000, upload-time = "2026-05-10T18:02:16.756Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/dc/829c54f60b9d08389439c00f813c752781c496fc5788c78d8006db4b4f2b/coverage-7.14.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5d4a51aad8ba8bdcd2b8bd8f03d4aca19693fa2327a3470e4718a25b03481020", size = 265732, upload-time = "2026-05-10T18:02:18.817Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/b0/70bd1419941652fa062689cba9c3eeafb8f5e6fbb890bce41c3bdda5dbd6/coverage-7.14.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:9f323af3e1e4f68b60b7b247e37b8515563a61375518fa59de1af48ba28a3db6", size = 260847, upload-time = "2026-05-10T18:02:20.528Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/73/be40b2390656c654d35ea0015ea7ba3d945769cf80790ad5e0bb2d56d2ba/coverage-7.14.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:1a0abc7342ea9711c469dd8b821c6c311e6bc6aac1442e5fbd6b27fae0a8f3db", size = 263166, upload-time = "2026-05-10T18:02:22.337Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/55/4a643f712fcf7cf2881f8ec1e0ccb7b164aff3108f69b51801246c8799f2/coverage-7.14.0-cp314-cp314t-win32.whl", hash = "sha256:a9f864ef57b7172e2db87a096642dd51e179e085ab6b2c371c29e885f65c8fb2", size = 223573, upload-time = "2026-05-10T18:02:24.11Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/96/3acae5da0953be042c0b4dea6d6789d2f080701c77b88e44d5bd41b9219b/coverage-7.14.0-cp314-cp314t-win_amd64.whl", hash = "sha256:29943e552fdc08e082eb51400fb2f58e118a83b5542bd06531214e084399b644", size = 224680, upload-time = "2026-05-10T18:02:25.896Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/3d/6ab5d2dd8325d838737c6f8d83d62eb6230e0d70b87b51b57bbfd08fa767/coverage-7.14.0-cp314-cp314t-win_arm64.whl", hash = "sha256:742a73ea621953b012f2c4c2219b512180dd84489acf5b1596b0aafc55b9100b", size = 222703, upload-time = "2026-05-10T18:02:27.822Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/e8/cb8e80d6f9f55b99588625062822bf946cf03ed06315df4bd8397f5632a1/coverage-7.14.0-py3-none-any.whl", hash = "sha256:8de5b61163aee3d05c8a2beab6f47913df7981dad1baf82c414d99158c286ab1", size = 211764, upload-time = "2026-05-10T18:02:29.538Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -85,53 +121,52 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "django"
|
||||
version = "5.1"
|
||||
version = "6.0.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "asgiref" },
|
||||
{ name = "sqlparse" },
|
||||
{ name = "tzdata", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1e/0c/d854d25bb74a8a3b41e642bbd27fe6af12fadd0edfd07d487809cf0ef719/Django-5.1.tar.gz", hash = "sha256:848a5980e8efb76eea70872fb0e4bc5e371619c70fffbe48e3e1b50b2c09455d", size = 10681050, upload-time = "2024-08-07T13:34:10.857Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5e/f1/bf85f0d29ef76abf901f193fe8fef4769d3da7794197832bc30151c071d8/django-6.0.5.tar.gz", hash = "sha256:bc6d6872e98a2864c836e42edd644b362db311147dd5aa8d5b82ba7a032f5269", size = 10924131, upload-time = "2026-05-05T13:54:39.329Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/28/b4/110532cebfea2244d76119904da98c6fa045ebb202aee9ec7cbf36ea3cad/Django-5.1-py3-none-any.whl", hash = "sha256:d3b811bf5371a26def053d7ee42a9df1267ef7622323fe70a601936725aa4557", size = 8246099, upload-time = "2024-08-07T13:33:52.959Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/5b/1328f8b84fce040c404f76822bf8c57d254e368e8cbd8bd67ec2b26d75f5/django-6.0.5-py3-none-any.whl", hash = "sha256:9d58a7cb49244e74c8e161d5e403a46d6209f1009ba40f5a66d6aa0d0786a8f0", size = 8368680, upload-time = "2026-05-05T13:54:33.532Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "django-extensions"
|
||||
version = "3.2.3"
|
||||
version = "4.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "django" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8a/f1/318684c9466968bf9a9c221663128206e460c1a67f595055be4b284cde8a/django-extensions-3.2.3.tar.gz", hash = "sha256:44d27919d04e23b3f40231c4ab7af4e61ce832ef46d610cc650d53e68328410a", size = 277216, upload-time = "2023-06-05T17:09:01.447Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6d/b3/ed0f54ed706ec0b54fd251cc0364a249c6cd6c6ec97f04dc34be5e929eac/django_extensions-4.1.tar.gz", hash = "sha256:7b70a4d28e9b840f44694e3f7feb54f55d495f8b3fa6c5c0e5e12bcb2aa3cdeb", size = 283078, upload-time = "2025-04-11T01:15:39.617Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/7e/ba12b9660642663f5273141018d2bec0a1cae1711f4f6d1093920e157946/django_extensions-3.2.3-py3-none-any.whl", hash = "sha256:9600b7562f79a92cbf1fde6403c04fee314608fefbb595502e34383ae8203401", size = 229868, upload-time = "2023-06-05T17:08:58.197Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/96/d967ca440d6a8e3861120f51985d8e5aec79b9a8bdda16041206adfe7adc/django_extensions-4.1-py3-none-any.whl", hash = "sha256:0699a7af28f2523bf8db309a80278519362cd4b6e1fd0a8cd4bf063e1e023336", size = 232980, upload-time = "2025-04-11T01:15:37.701Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "django-filter"
|
||||
version = "24.3"
|
||||
version = "25.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "django" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/50/bc/dc19ae39c235332926dd0efe0951f663fa1a9fc6be8430737ff7fd566b20/django_filter-24.3.tar.gz", hash = "sha256:d8ccaf6732afd21ca0542f6733b11591030fa98669f8d15599b358e24a2cd9c3", size = 144444, upload-time = "2024-08-02T13:27:58.132Z" }
|
||||
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" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/09/b1/92f1c30b47c1ebf510c35a2ccad9448f73437e5891bbd2b4febe357cc3de/django_filter-24.3-py3-none-any.whl", hash = "sha256:c4852822928ce17fb699bcfccd644b3574f1a2d80aeb2b4ff4f16b02dd49dc64", size = 95011, upload-time = "2024-08-02T13:27:55.616Z" },
|
||||
{ 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" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "django-modelcluster"
|
||||
version = "6.3"
|
||||
version = "6.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "django" },
|
||||
{ name = "pytz" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3d/dd/db1ab2e256f9fda6796ac312686561cbe2740d960c920f74410559ef7739/django-modelcluster-6.3.tar.gz", hash = "sha256:0caed8a0e889f3abb92f144670878a466ef954ffa6c4c7b9c80e6426b720a49d", size = 28903, upload-time = "2024-02-26T19:26:53.185Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/34/0f/c5fd0c280a10224d619325783bfaab5a54903f82340e41ae21ab3127ebc9/django_modelcluster-6.5.tar.gz", hash = "sha256:459cbf0fb3bbc8c15ea9a2a062abc0d1ef935a38e04aa8ac3cb241c826bbc6dd", size = 29707, upload-time = "2026-05-01T12:25:33.524Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/6d/8deb2dfde2e1177412b759a1c32713be19b1d3af1da686e44e64c165e8c0/django_modelcluster-6.3-py2.py3-none-any.whl", hash = "sha256:a8783d6565a0663f41cd6003ea361c3a5711e8a2a326160f1ec1eceb3e973d4f", size = 29039, upload-time = "2024-02-26T19:26:50.903Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/57/cdcdea9a56d114e9217d52ce9a8af19ad7fe8a0996faf8480d3f286c02e0/django_modelcluster-6.5-py3-none-any.whl", hash = "sha256:469b380a294027d5211d0e5d5746e0381f445b058d2229977a58fe0f1c58a596", size = 29865, upload-time = "2026-05-01T12:25:31.886Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -146,40 +181,67 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/5b/216157ff053f955b15b9dcdc13bfb6e406666445164fee9e332e141be96d/django_permissionedforms-0.1-py2.py3-none-any.whl", hash = "sha256:d341a961a27cc77fde8cc42141c6ab55cc1f0cb886963cc2d6967b9674fa47d6", size = 5744, upload-time = "2022-02-28T19:40:26.337Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "django-stubs-ext"
|
||||
version = "5.2.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "django" },
|
||||
{ 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" }
|
||||
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" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "django-taggit"
|
||||
version = "5.0.1"
|
||||
version = "6.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "django" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/10/eb/269501702e231b552f68d813d0a07ac1ea81fd25a3c92fb0f249818f0f39/django-taggit-5.0.1.tar.gz", hash = "sha256:edcd7db1e0f35c304e082a2f631ddac2e16ef5296029524eb792af7430cab4cc", size = 60372, upload-time = "2023-10-29T11:57:20.124Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/34/a6/f1beaf8f552fe90c153cc039316ebab942c23dfbc88588dde081fefca816/django_taggit-6.1.0.tar.gz", hash = "sha256:c4d1199e6df34125dd36db5eb0efe545b254dec3980ce5dd80e6bab3e78757c3", size = 38151, upload-time = "2024-09-29T08:07:39.477Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/07/18/94a45c8c50592d5cf7d73a9111053917c3684fda031c988041396bb3e588/django_taggit-5.0.1-py3-none-any.whl", hash = "sha256:a0ca8a28b03c4b26c2630fd762cb76ec39b5e41abf727a7b66f897a625c5e647", size = 61141, upload-time = "2023-10-29T11:56:57.954Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/34/4185c345530b91d05cb82e05d07148f481a5eb5dc2ac44e092b3daa6f206/django_taggit-6.1.0-py3-none-any.whl", hash = "sha256:ab776264bbc76cb3d7e49e1bf9054962457831bd21c3a42db9138b41956e4cf0", size = 75749, upload-time = "2024-09-29T08:07:14.612Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "django-tasks"
|
||||
version = "0.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "django" },
|
||||
{ name = "django-stubs-ext" },
|
||||
{ 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" }
|
||||
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" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "django-treebeard"
|
||||
version = "4.7.1"
|
||||
version = "5.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "django" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bb/24/eaccbce17355380cb3a8fe6ad92a85b76453dc1f0ecd04f48bfe8929065b/django-treebeard-4.7.1.tar.gz", hash = "sha256:846e462904b437155f76e04907ba4e48480716855f88b898df4122bdcfbd6e98", size = 294139, upload-time = "2024-01-31T16:35:19.751Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f7/16/aa732ea1033586e4f946d8e47be9116ffb22b050485b179a7cafb82dfc34/django_treebeard-5.1.0.tar.gz", hash = "sha256:8ac2ba41307469a679c98188124933bc3baade36b02480343d1a301a1fca0700", size = 302339, upload-time = "2026-05-12T02:06:37.002Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/87/79/259966820614746cc4d81762edf97a53bf1e3b8e74797c010d310c6f4a8f/django_treebeard-4.7.1-py3-none-any.whl", hash = "sha256:995c7120153ab999898fe3043bbdcd8a0fc77cc106eb94de7350e9d02c885135", size = 93210, upload-time = "2024-01-31T16:35:17.843Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/d3/311b9c43950238744f946540c48ea7068bf9e55bea976908a7be13b73082/django_treebeard-5.1.0-py3-none-any.whl", hash = "sha256:d0f68fcf1b49158e38d2322aa62a37bc0b89452d66a16f88e58dbe076d043c28", size = 78591, upload-time = "2026-05-12T02:06:35.62Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "djangorestframework"
|
||||
version = "3.15.2"
|
||||
version = "3.16.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "django" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2c/ce/31482eb688bdb4e271027076199e1aa8d02507e530b6d272ab8b4481557c/djangorestframework-3.15.2.tar.gz", hash = "sha256:36fe88cd2d6c6bec23dca9804bab2ba5517a8bb9d8f47ebc68981b56840107ad", size = 1067420, upload-time = "2024-06-19T07:59:32.891Z" }
|
||||
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" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/b6/fa99d8f05eff3a9310286ae84c4059b08c301ae4ab33ae32e46e8ef76491/djangorestframework-3.15.2-py3-none-any.whl", hash = "sha256:2b8871b062ba1aefc2de01f773875441a961fefbf79f5eed1e32b2f096944b20", size = 1071235, upload-time = "2024-06-19T07:59:26.106Z" },
|
||||
{ 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" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -193,44 +255,80 @@ dependencies = [
|
||||
{ name = "psycopg2-binary" },
|
||||
{ name = "wagtail" },
|
||||
{ name = "wagtail-grapple" },
|
||||
{ name = "wagtail-headless-preview" },
|
||||
{ name = "whitenoise" },
|
||||
]
|
||||
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "pytest" },
|
||||
{ name = "pytest-cov" },
|
||||
{ name = "pytest-django" },
|
||||
{ name = "ruff" },
|
||||
{ name = "wagtail-factories" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "django", specifier = ">=5.0.7,<6" },
|
||||
{ name = "django-extensions", specifier = ">=3.2.3,<4" },
|
||||
{ name = "gunicorn", specifier = ">=23.0.0" },
|
||||
{ name = "psycopg2-binary", specifier = ">=2.9.10,<3" },
|
||||
{ name = "wagtail", specifier = ">=6.1.3,<7" },
|
||||
{ name = "wagtail-grapple", specifier = ">=0.26.0,<0.27" },
|
||||
{ name = "whitenoise", specifier = ">=6.7.0,<7" },
|
||||
{ name = "django", specifier = ">=6.0.5,<7" },
|
||||
{ name = "django-extensions", specifier = ">=4.1,<5" },
|
||||
{ name = "gunicorn", specifier = ">=26.0.0,<27" },
|
||||
{ name = "psycopg2-binary", specifier = ">=2.9.12,<3" },
|
||||
{ name = "wagtail", specifier = ">=7.4.1,<8" },
|
||||
{ name = "wagtail-grapple", specifier = ">=0.31.0,<0.32" },
|
||||
{ name = "wagtail-headless-preview", specifier = ">=0.8,<0.9" },
|
||||
{ name = "whitenoise", specifier = ">=6.12.0,<7" },
|
||||
]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [{ name = "ruff" }]
|
||||
dev = [
|
||||
{ name = "pytest", specifier = ">=9.0.3,<10" },
|
||||
{ name = "pytest-cov", specifier = ">=7.0.0,<8" },
|
||||
{ name = "pytest-django", specifier = ">=4.12.0,<5" },
|
||||
{ name = "ruff", specifier = ">=0.15.13,<0.16" },
|
||||
{ name = "wagtail-factories", specifier = ">=4.4.0,<5" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "draftjs-exporter"
|
||||
version = "5.0.0"
|
||||
version = "5.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/29/5b/7729b1a95d48d2f51b03feacda1d5b17058e1caec524592e59d0df801a8c/draftjs_exporter-5.0.0.tar.gz", hash = "sha256:2efee45d4bb4c0aaacc3e5ea2983a29a29381e02037f3f92a6b12706d7b87e1e", size = 33271, upload-time = "2022-04-03T22:00:47.094Z" }
|
||||
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" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/58/f4/95ad9b91194156f0e87877d640428b1c7be70a90979796ecdcb77adcfb4f/draftjs_exporter-5.0.0-py3-none-any.whl", hash = "sha256:8cb9d2d51284233decfe274802f1c53e257158c62b9f53ed2399de3fa80ac561", size = 26320, upload-time = "2022-04-03T22:00:44.647Z" },
|
||||
{ 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" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "et-xmlfile"
|
||||
version = "1.1.0"
|
||||
version = "2.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3d/5d/0413a31d184a20c763ad741cc7852a659bf15094c24840c5bdd1754765cd/et_xmlfile-1.1.0.tar.gz", hash = "sha256:8eb9e2bc2f8c97e37a2dc85a09ecdcdec9d8a396530a6d5a33b30b9a92da0c5c", size = 3218, upload-time = "2021-04-26T13:26:05.068Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d3/38/af70d7ab1ae9d4da450eeec1fa3918940a5fafb9055e934af8d6eb0c2313/et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54", size = 17234, upload-time = "2024-10-25T17:25:40.039Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/96/c2/3dd434b0108730014f1b96fd286040dc3bcb70066346f7e01ec2ac95865f/et_xmlfile-1.1.0-py3-none-any.whl", hash = "sha256:a2ba85d1d6a74ef63837eed693bcb89c3f752169b0e3e7ae5b16ca5e1b3deada", size = 4688, upload-time = "2021-04-26T13:26:03.429Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059, upload-time = "2024-10-25T17:25:39.051Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "factory-boy"
|
||||
version = "3.3.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "faker" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ba/98/75cacae9945f67cfe323829fc2ac451f64517a8a330b572a06a323997065/factory_boy-3.3.3.tar.gz", hash = "sha256:866862d226128dfac7f2b4160287e899daf54f2612778327dd03d0e2cb1e3d03", size = 164146, upload-time = "2025-02-03T09:49:04.433Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/27/8d/2bc5f5546ff2ccb3f7de06742853483ab75bf74f36a92254702f8baecc79/factory_boy-3.3.3-py2.py3-none-any.whl", hash = "sha256:1c39e3289f7e667c4285433f305f8d506efc2fe9c73aaea4151ebd5cdea394fc", size = 37036, upload-time = "2025-02-03T09:49:01.659Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "faker"
|
||||
version = "40.18.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "tzdata", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/18/06/70886e82d8f1d2b73454f3a7c1b7405300128df22e70d85a828951366932/faker-40.18.0.tar.gz", hash = "sha256:2207575c0e8f90e6ccd6dbef764de875c614d16d3db4eee9712d9a00087f2e70", size = 1968243, upload-time = "2026-05-14T16:43:04.834Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/84/0b/5c0b2d3a4b7a715f1835dd3f963bfbe841a02ae5cad1df8ee0325dfad235/faker-40.18.0-py3-none-any.whl", hash = "sha256:61a6b94b74605ddb090a065deb197a1c585ae7a874c094cf6693671d271e6083", size = 2006355, upload-time = "2026-05-14T16:43:02.489Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -244,21 +342,22 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "graphene"
|
||||
version = "3.3"
|
||||
version = "3.4.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "aniso8601" },
|
||||
{ name = "graphql-core" },
|
||||
{ name = "graphql-relay" },
|
||||
{ name = "python-dateutil" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/82/75/02875858c7c09fc156840181cdee27b23408fac75720a2e1e9128f3d48a5/graphene-3.3.tar.gz", hash = "sha256:529bf40c2a698954217d3713c6041d69d3f719ad0080857d7ee31327112446b0", size = 57893, upload-time = "2023-07-26T06:48:39.083Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cc/f6/bf62ff950c317ed03e77f3f6ddd7e34aaa98fe89d79ebd660c55343d8054/graphene-3.4.3.tar.gz", hash = "sha256:2a3786948ce75fe7e078443d37f609cbe5bb36ad8d6b828740ad3b95ed1a0aaa", size = 44739, upload-time = "2024-11-09T20:44:25.757Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/24/70/96f6027cdfc9bb89fc07627b615cb43fb1c443c93498412beaeaf157e9f1/graphene-3.3-py2.py3-none-any.whl", hash = "sha256:bb3810be33b54cb3e6969506671eb72319e8d7ba0d5ca9c8066472f75bf35a38", size = 128227, upload-time = "2023-07-26T06:48:37.766Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/e0/61d8e98007182e6b2aca7cf65904721fb2e4bce0192272ab9cb6f69d8812/graphene-3.4.3-py2.py3-none-any.whl", hash = "sha256:820db6289754c181007a150db1f7fff544b94142b556d12e3ebc777a7bf36c71", size = 114894, upload-time = "2024-11-09T20:44:23.851Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "graphene-django"
|
||||
version = "3.2.2"
|
||||
version = "3.2.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "django" },
|
||||
@@ -268,18 +367,18 @@ dependencies = [
|
||||
{ name = "promise" },
|
||||
{ name = "text-unidecode" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/09/f1/4eb1c0ae79814aba03e2347f664cb0bef72816fbe99d4399378bb22bef47/graphene-django-3.2.2.tar.gz", hash = "sha256:059ccf25d9a5159f28d7ebf1a648c993ab34deb064e80b70ca096aa22a609556", size = 87591, upload-time = "2024-06-12T02:58:09.439Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6f/7a/8aef131349329dcd167578720f6364412f1728bfda14bba22c1e7b5d8365/graphene-django-3.2.3.tar.gz", hash = "sha256:d831bfe8e9a6e77e477b7854faef4addb318f386119a69ee4c57b74560f3e07d", size = 88393, upload-time = "2025-03-13T08:33:03.949Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/da/71/6800d05144ee4fb57f471571dd80fc9c6b2196e6243d39ed3d3654082824/graphene_django-3.2.2-py2.py3-none-any.whl", hash = "sha256:0fd95c8c1cbe77ae2a5940045ce276803c3acbf200a156731e0c730f2776ae2c", size = 114204, upload-time = "2024-06-12T02:58:07.897Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/35/ab9c668222f6271a0b71efb147c46816229dbb9fa5d15ee6b025eb08d4b2/graphene_django-3.2.3-py2.py3-none-any.whl", hash = "sha256:0c673a4dad315b26b4d18eb379ad0c7027fd6a36d23a1848b7c7c09a14a9271e", size = 114959, upload-time = "2025-03-13T08:33:02.453Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "graphql-core"
|
||||
version = "3.2.3"
|
||||
version = "3.2.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ee/a6/94df9045ca1bac404c7b394094cd06713f63f49c7a4d54d99b773ae81737/graphql-core-3.2.3.tar.gz", hash = "sha256:06d2aad0ac723e35b1cb47885d3e5c45e956a53bc1b209a9fc5369007fe46676", size = 529552, upload-time = "2022-09-23T08:37:13.684Z" }
|
||||
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" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/39/e5143e7ec70939d2076c1165ae9d4a3815597019c4d797b7f959cf778600/graphql_core-3.2.3-py3-none-any.whl", hash = "sha256:5766780452bd5ec8ba133f8bf287dc92713e3868ddd83aee4faab9fc3e303dc3", size = 202921, upload-time = "2022-09-23T08:37:11.825Z" },
|
||||
{ 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" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -296,48 +395,57 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "gunicorn"
|
||||
version = "23.0.0"
|
||||
version = "26.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "packaging" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031, upload-time = "2024-08-10T20:25:27.378Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6d/b7/a4a3f632f823e432ce6bc65f62961b7980c898c77f075a2f7118cb3846fe/gunicorn-26.0.0.tar.gz", hash = "sha256:ca9346f85e3a4aeeb64d491045c16b9a35647abd37ea15efe53080eb8b090baf", size = 727286, upload-time = "2026-05-05T06:38:25.529Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029, upload-time = "2024-08-10T20:25:24.996Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/40/9c2384fc2be4ad25dd4a49decd5ad9ea5a3639814c11bd40ab77cb9f0a14/gunicorn-26.0.0-py3-none-any.whl", hash = "sha256:40233d26a5f0d1872916188c276e21641155111c2853f0c2cd55260aec0d24fc", size = 212009, upload-time = "2026-05-05T06:38:23.007Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.8"
|
||||
version = "3.10"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e8/ac/e349c5e6d4543326c6883ee9491e3921e0d07b55fdf3cce184b40d63e72a/idna-3.8.tar.gz", hash = "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603", size = 189467, upload-time = "2024-08-23T16:01:51.339Z" }
|
||||
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" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/22/7e/d71db821f177828df9dea8c42ac46473366f191be53080e552e628aad991/idna-3.8-py3-none-any.whl", hash = "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac", size = 66894, upload-time = "2024-08-23T16:01:49.963Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "l18n"
|
||||
version = "2021.3"
|
||||
name = "iniconfig"
|
||||
version = "2.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pytz" },
|
||||
{ name = "six" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ff/9c/13d732e16e8fbdef7e68f4f339f3b86618430d4003c7ffe82e15bf925d7c/l18n-2021.3.tar.gz", hash = "sha256:1956e890d673d17135cc20913253c154f6bc1c00266c22b7d503cc1a5a42d848", size = 50712, upload-time = "2021-11-12T09:32:36.255Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/e7/dfa82d0bb2b314950e457a755463b429090127ffc0ab9d8d14ef4563ad44/l18n-2021.3-py3-none-any.whl", hash = "sha256:78495d1df95b6f7dcc694d1ba8994df709c463a1cbac1bf016e1b9a5ce7280b9", size = 51534, upload-time = "2021-11-12T09:32:34.296Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "laces"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "django" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a2/cb/d69f2cbb248bd6baceb7597d8c6c229797689b546ac2a446fac15b8f455f/laces-0.1.1.tar.gz", hash = "sha256:e45159c46f6adca33010d34e9af869e57201b70675c6dc088e919b16c89456a4", size = 26889, upload-time = "2024-02-10T23:34:22.055Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/74/9a/9192d6a74e2c6db4f705dd98f56be488e47373172c13f4916aeabc4d68b8/laces-0.1.2.tar.gz", hash = "sha256:3218e09c1889ae5cf3fc7a82f5bb63ec0c7879889b6a9760bfc42323c694b84d", size = 29264, upload-time = "2025-01-14T04:37:34.805Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/dc/ceadbdc5e14aec7bd01bdd6ef8d5bf704d6a12d1b5a25bcaaa066bdb820d/laces-0.1.1-py3-none-any.whl", hash = "sha256:ae2c575b9aaa46154e5518c61c9f86f5a9478f753a51e9c5547c7d275d361242", size = 21349, upload-time = "2024-02-10T23:34:20.37Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/fe/31f76f5cb2579bdda208aa257ce5482653f22ab1bad3e128fe2f803fa2f1/laces-0.1.2-py3-none-any.whl", hash = "sha256:980cdaf9a31e883a2b8198132e2388931a4eb8814f5bfa5d8bba13ff9f657b7c", size = 22462, upload-time = "2025-01-14T04:37:30.636Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "modelsearch"
|
||||
version = "1.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "django" },
|
||||
{ name = "django-tasks" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/dd/25/81121175512af4a4b152f6db3ea749535659f5890f0987cd481646527dff/modelsearch-1.3.1.tar.gz", hash = "sha256:f3b2e304dbef9f7c838423f591cfe0c60f4cef584bae8793d2aa8ac35a3c46e0", size = 103935, upload-time = "2026-04-27T23:37:08.841Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/f1/63cf88da72bc557594a543dd19a2c7e219acccc70cb2b1e2639204580fe6/modelsearch-1.3.1-py3-none-any.whl", hash = "sha256:a92847f01788d0d615e8715fabb8e823029288840297fb9e7235ee03ad49b6a8", size = 127463, upload-time = "2026-04-27T23:37:07.651Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -363,39 +471,59 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pillow"
|
||||
version = "10.4.0"
|
||||
version = "11.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cd/74/ad3d526f3bf7b6d3f408b73fde271ec69dfac8b81341a318ce825f2b3812/pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06", size = 46555059, upload-time = "2024-07-01T09:48:43.583Z" }
|
||||
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" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/05/cb/0353013dc30c02a8be34eb91d25e4e4cf594b59e5a55ea1128fde1e5f8ea/pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94", size = 3509350, upload-time = "2024-07-01T09:46:17.177Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/cf/5c558a0f247e0bf9cec92bff9b46ae6474dd736f6d906315e60e4075f737/pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597", size = 3374980, upload-time = "2024-07-01T09:46:19.169Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/48/6e394b86369a4eb68b8a1382c78dc092245af517385c086c5094e3b34428/pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80", size = 4343799, upload-time = "2024-07-01T09:46:21.883Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/f3/a8c6c11fa84b59b9df0cd5694492da8c039a24cd159f0f6918690105c3be/pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca", size = 4459973, upload-time = "2024-07-01T09:46:24.321Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/1b/c14b4197b80150fb64453585247e6fb2e1d93761fa0fa9cf63b102fde822/pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef", size = 4370054, upload-time = "2024-07-01T09:46:26.825Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/77/40daddf677897a923d5d33329acd52a2144d54a9644f2a5422c028c6bf2d/pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a", size = 4539484, upload-time = "2024-07-01T09:46:29.355Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/54/90de3e4256b1207300fb2b1d7168dd912a2fb4b2401e439ba23c2b2cabde/pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b", size = 4477375, upload-time = "2024-07-01T09:46:31.756Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/24/1bfba52f44193860918ff7c93d03d95e3f8748ca1de3ceaf11157a14cf16/pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9", size = 4608773, upload-time = "2024-07-01T09:46:33.73Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/04/5e6de6e6120451ec0c24516c41dbaf80cce1b6451f96561235ef2429da2e/pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42", size = 2235690, upload-time = "2024-07-01T09:46:36.587Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/0a/d4ce3c44bca8635bd29a2eab5aa181b654a734a29b263ca8efe013beea98/pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a", size = 2554951, upload-time = "2024-07-01T09:46:38.777Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/ca/184349ee40f2e92439be9b3502ae6cfc43ac4b50bc4fc6b3de7957563894/pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9", size = 2243427, upload-time = "2024-07-01T09:46:43.15Z" },
|
||||
{ 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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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]]
|
||||
name = "pillow-heif"
|
||||
version = "0.18.0"
|
||||
version = "1.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pillow" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c9/bb/e7797fe7f5cad447bb470f916ead38f0929e8d28bdf6bd5d9f31dfe1ac26/pillow_heif-0.18.0.tar.gz", hash = "sha256:70318dad9faa76121c6592ac0ab59881ff0dac6ab791a922e70d82c7706cce88", size = 16172675, upload-time = "2024-07-27T16:21:21.889Z" }
|
||||
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" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/48/4bdce48d77c307b50bba0c77d6485e156f0fefcca273ce007351ad1deb40/pillow_heif-0.18.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:c795e7ccceea33e01e49ce536139f94cabb1bf017393666f76c05a9daebae2da", size = 5279030, upload-time = "2024-07-27T16:20:00.876Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/d8/8f2f8f44b6fc3689ace680b21655a0eabb1393f8e66b8851b0f95b8aac1c/pillow_heif-0.18.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:4dd5b3ec09be45c1ef63be31773df90e18ee08e5e950018b0a349924b54a24ac", size = 3732529, upload-time = "2024-07-27T16:20:02.849Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/61/f0aac50a7ad051e1cb71ed7a8d3a94b5e54ea8ed1acb03586f0fcdebd730/pillow_heif-0.18.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb2eade59c2654c2643a3b637de37c19e75a77c66a3e9a5e0ae26210e4f48aee", size = 6759746, upload-time = "2024-07-27T16:20:04.776Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/71/a8b3684f64307b96ea0ae14594564b41fdf4d4a6f7df5ef73d180a71db5d/pillow_heif-0.18.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35b59d599bfdb8454739db6b92f0841ecadbe887babb5ed5abd5299587843eef", size = 7586203, upload-time = "2024-07-27T16:20:06.811Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/59/3b1c90f7d366fb653f5f072803ae6350e608bea0d0d0ea65d6e7764c470f/pillow_heif-0.18.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:26a0b8b8e899e7bcc876ee61fcadb0f0b849bd6a0d5c20f0e969c77a43b40568", size = 8119771, upload-time = "2024-07-27T16:20:08.491Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/20/a72297dc260d0bf8fce8f209d36ceb498b2f1e8beb478a0ab3f565c9a3c9/pillow_heif-0.18.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0276a3e0c667677ed0c67f4512cdf2f674065018049307ba4de5cb4648b0a33e", size = 8850943, upload-time = "2024-07-27T16:20:10.262Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/98/2bcb2790618cfb8ce2054256a4d3bc2597288bf82444cc87fdc815484c38/pillow_heif-0.18.0-cp312-cp312-win_amd64.whl", hash = "sha256:5916fa31f2015626dd2372d14e24521ea6caed11b25be14faa9b9c67731087ce", size = 8547707, upload-time = "2024-07-27T16:20:12.594Z" },
|
||||
{ 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/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/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/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/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/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/6b/3c/63cbbe3a6a54e3ec925d2433bb431a4bf61695f34b5f1e689db642fb20c1/pillow_heif-1.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:514c856230995dc2f918fb12d83906885d42829e911868e23035e2d916c4c7c5", size = 5573890, upload-time = "2025-08-02T09:58:05.049Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.6.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -409,36 +537,89 @@ sdist = { url = "https://files.pythonhosted.org/packages/cf/9c/fb5d48abfe5d791cd
|
||||
|
||||
[[package]]
|
||||
name = "psycopg2-binary"
|
||||
version = "2.9.10"
|
||||
version = "2.9.12"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cb/0e/bdc8274dc0585090b4e3432267d7be4dfbfd8971c0fa59167c711105a6bf/psycopg2-binary-2.9.10.tar.gz", hash = "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2", size = 385764, upload-time = "2024-10-16T11:24:58.126Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2a/60/a3624f79acea344c16fbef3a94d28b89a8042ddfb8f3e4ca83f538671409/psycopg2_binary-2.9.12.tar.gz", hash = "sha256:5ac9444edc768c02a6b6a591f070b8aae28ff3a99be57560ac996001580f294c", size = 379686, upload-time = "2026-04-21T09:40:34.304Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/49/7d/465cc9795cf76f6d329efdafca74693714556ea3891813701ac1fee87545/psycopg2_binary-2.9.10-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:880845dfe1f85d9d5f7c412efea7a08946a46894537e4e5d091732eb1d34d9a0", size = 3044771, upload-time = "2024-10-16T11:20:35.234Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/31/6d225b7b641a1a2148e3ed65e1aa74fc86ba3fee850545e27be9e1de893d/psycopg2_binary-2.9.10-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9440fa522a79356aaa482aa4ba500b65f28e5d0e63b801abf6aa152a29bd842a", size = 3275336, upload-time = "2024-10-16T11:20:38.742Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/b7/a68c2b4bff1cbb1728e3ec864b2d92327c77ad52edcd27922535a8366f68/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3923c1d9870c49a2d44f795df0c889a22380d36ef92440ff618ec315757e539", size = 2851637, upload-time = "2024-10-16T11:20:42.145Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/b1/cfedc0e0e6f9ad61f8657fd173b2f831ce261c02a08c0b09c652b127d813/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b2c956c028ea5de47ff3a8d6b3cc3330ab45cf0b7c3da35a2d6ff8420896526", size = 3082097, upload-time = "2024-10-16T11:20:46.185Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/ed/0a8e4153c9b769f59c02fb5e7914f20f0b2483a19dae7bf2db54b743d0d0/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f758ed67cab30b9a8d2833609513ce4d3bd027641673d4ebc9c067e4d208eec1", size = 3264776, upload-time = "2024-10-16T11:20:50.879Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/db/d09da68c6a0cdab41566b74e0a6068a425f077169bed0946559b7348ebe9/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cd9b4f2cfab88ed4a9106192de509464b75a906462fb846b936eabe45c2063e", size = 3020968, upload-time = "2024-10-16T11:20:56.819Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/28/4d6f8c255f0dfffb410db2b3f9ac5218d959a66c715c34cac31081e19b95/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dc08420625b5a20b53551c50deae6e231e6371194fa0651dbe0fb206452ae1f", size = 2872334, upload-time = "2024-10-16T11:21:02.411Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/f7/20d7bf796593c4fea95e12119d6cc384ff1f6141a24fbb7df5a668d29d29/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d7cd730dfa7c36dbe8724426bf5612798734bff2d3c3857f36f2733f5bfc7c00", size = 2822722, upload-time = "2024-10-16T11:21:09.01Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/e4/0c407ae919ef626dbdb32835a03b6737013c3cc7240169843965cada2bdf/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:155e69561d54d02b3c3209545fb08938e27889ff5a10c19de8d23eb5a41be8a5", size = 2920132, upload-time = "2024-10-16T11:21:16.339Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/70/aa69c9f69cf09a01da224909ff6ce8b68faeef476f00f7ec377e8f03be70/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3cc28a6fd5a4a26224007712e79b81dbaee2ffb90ff406256158ec4d7b52b47", size = 2959312, upload-time = "2024-10-16T11:21:25.584Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/bd/213e59854fafe87ba47814bf413ace0dcee33a89c8c8c814faca6bc7cf3c/psycopg2_binary-2.9.10-cp312-cp312-win32.whl", hash = "sha256:ec8a77f521a17506a24a5f626cb2aee7850f9b69a0afe704586f63a464f3cd64", size = 1025191, upload-time = "2024-10-16T11:21:29.912Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/29/06261ea000e2dc1e22907dbbc483a1093665509ea586b29b8986a0e56733/psycopg2_binary-2.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:18c5ee682b9c6dd3696dad6e54cc7ff3a1a9020df6a5c0f861ef8bfd338c3ca0", size = 1164031, upload-time = "2024-10-16T11:21:34.211Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/1b/708c0dca874acfad6d65314271859899a79007686f3a1f74e82a2ed4b645/psycopg2_binary-2.9.12-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6f3b3de8a74ef8db215f22edffb19e32dc6fa41340456de7ec99efdc8a7b3ec2", size = 3712428, upload-time = "2026-04-20T23:35:23.453Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/39/ddbea9d4b4de6aca9431b6ed253f530f8a02d3b8f9bcfd0dbfe2b3de6fe4/psycopg2_binary-2.9.12-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1006fb62f0f0bc5ce256a832356c6262e91be43f5e4eb15b5eaf38079464caf2", size = 3823184, upload-time = "2026-04-20T23:35:25.92Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/a0/bc2fef74b106fa345567122a0659e6d94512ed7dc0131ec44c9e5aba3725/psycopg2_binary-2.9.12-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:840066105706cd2eb29b9a1c2329620056582a4bf3e8169dec5c447042d0869f", size = 4579157, upload-time = "2026-04-20T23:35:28.542Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/d7/d4e3b2005d3de607ca4fbb0e8742e248056e52184a6b94ebda3c1c2c329b/psycopg2_binary-2.9.12-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:863f5d12241ebe1c76a72a04c2113b6dc905f90b9cef0e9be0efd994affd9354", size = 4274970, upload-time = "2026-04-20T23:35:30.418Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/42/c9853f8db3967fe08bcde11f53d53b85d351750cae726ce001cb68afa9c1/psycopg2_binary-2.9.12-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a99eaab34a9010f1a086b126de467466620a750634d114d20455f3a824aae033", size = 5895175, upload-time = "2026-04-20T23:35:33.584Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/fd/b82b5601a97630308bef079f545ffec481bbbc795c2ba5ec416a01d03f60/psycopg2_binary-2.9.12-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ffdd7dc5463ccd61845ac37b7012d0f35a1548df9febe14f8dd549be4a0bc81e", size = 4110658, upload-time = "2026-04-20T23:35:35.638Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/8c/32ca69b0389ef25dd22937bf9e8fbe2ce27aea20b05ded48c4ce4cb42475/psycopg2_binary-2.9.12-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:54a0dfecab1b48731f934e06139dfe11e24219fb6d0ceb32177cf0375f14c7b5", size = 3656251, upload-time = "2026-04-20T23:35:37.854Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/29/96992a2b59e3b9d730fcf9612d0a387305025dc867a9fc490a9e496e074e/psycopg2_binary-2.9.12-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:96937c9c5d891f772430f418a7a8b4691a90c3e6b93cf72b5bd7cad8cbca32a5", size = 3301810, upload-time = "2026-04-20T23:35:39.927Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/56/ad/44b06659949b243ae10112cd3b20a197f9bf3e81d5651379b9eb889bfaad/psycopg2_binary-2.9.12-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:77b348775efd4cdab410ec6609d81ccecd1139c90265fa583a7255c8064bc03d", size = 3048977, upload-time = "2026-04-20T23:35:41.806Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/f2/10a1bcebadb6aa55e280e1f58975c36a7b560ea525184c7aa4064c466633/psycopg2_binary-2.9.12-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:527e6342b3e44c2f0544f6b8e927d60de7f163f5723b8f1dfa7d2a84298738cd", size = 3351466, upload-time = "2026-04-20T23:35:43.993Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/be/b732c8418ffa5bcfda002890f5dc4c869fc17db66ff11f53b17cfe44afc0/psycopg2_binary-2.9.12-cp314-cp314-win_amd64.whl", hash = "sha256:f12ae41fcafadb39b2785e64a40f9db05d6de2ac114077457e0e7c597f3af980", size = 2848762, upload-time = "2026-04-20T23:35:46.421Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytz"
|
||||
version = "2024.1"
|
||||
name = "pygments"
|
||||
version = "2.20.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/90/26/9f1f00a5d021fff16dee3de13d43e5e978f3d58928e129c3a62cf7eb9738/pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812", size = 316214, upload-time = "2024-02-02T01:18:41.693Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/3d/a121f284241f08268b21359bd425f7d4825cffc5ac5cd0e1b3d82ffd2b10/pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319", size = 505474, upload-time = "2024-02-02T01:18:37.283Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "9.0.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
{ name = "iniconfig" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pluggy" },
|
||||
{ name = "pygments" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-cov"
|
||||
version = "7.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "coverage" },
|
||||
{ name = "pluggy" },
|
||||
{ name = "pytest" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-django"
|
||||
version = "4.12.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pytest" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/13/2b/db9a193df89e5660137f5428063bcc2ced7ad790003b26974adf5c5ceb3b/pytest_django-4.12.0.tar.gz", hash = "sha256:df94ec819a83c8979c8f6de13d9cdfbe76e8c21d39473cfe2b40c9fc9be3c758", size = 91156, upload-time = "2026-02-14T18:40:49.235Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/83/a5/41d091f697c09609e7ef1d5d61925494e0454ebf51de7de05f0f0a728f1d/pytest_django-4.12.0-py3-none-any.whl", hash = "sha256:3ff300c49f8350ba2953b90297d23bf5f589db69545f56f1ec5f8cff5da83e85", size = 26123, upload-time = "2026-02-14T18:40:47.381Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dateutil"
|
||||
version = "2.9.0.post0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "six" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.32.3"
|
||||
version = "2.32.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
@@ -446,61 +627,61 @@ dependencies = [
|
||||
{ name = "idna" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload-time = "2024-05-29T15:37:49.536Z" }
|
||||
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" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" },
|
||||
{ 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" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.12.4"
|
||||
version = "0.15.13"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9b/ce/8d7dbedede481245b489b769d27e2934730791a9a82765cb94566c6e6abd/ruff-0.12.4.tar.gz", hash = "sha256:13efa16df6c6eeb7d0f091abae50f58e9522f3843edb40d56ad52a5a4a4b6873", size = 5131435, upload-time = "2025-07-17T17:27:19.138Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/24/21/a7d5c126d5b557715ef81098f3db2fe20f622a039ff2e626af28d674ab80/ruff-0.15.13.tar.gz", hash = "sha256:f9d89f17f7ba7fb2ed42921f0df75da797a9a5d71bc39049e2c687cf2baf44b7", size = 4678180, upload-time = "2026-05-14T13:44:37.869Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/9f/517bc5f61bad205b7f36684ffa5415c013862dee02f55f38a217bdbe7aa4/ruff-0.12.4-py3-none-linux_armv6l.whl", hash = "sha256:cb0d261dac457ab939aeb247e804125a5d521b21adf27e721895b0d3f83a0d0a", size = 10188824, upload-time = "2025-07-17T17:26:31.412Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/83/691baae5a11fbbde91df01c565c650fd17b0eabed259e8b7563de17c6529/ruff-0.12.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:55c0f4ca9769408d9b9bac530c30d3e66490bd2beb2d3dae3e4128a1f05c7442", size = 10884521, upload-time = "2025-07-17T17:26:35.084Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/8d/756d780ff4076e6dd035d058fa220345f8c458391f7edfb1c10731eedc75/ruff-0.12.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a8224cc3722c9ad9044da7f89c4c1ec452aef2cfe3904365025dd2f51daeae0e", size = 10277653, upload-time = "2025-07-17T17:26:37.897Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/97/8eeee0f48ece153206dce730fc9e0e0ca54fd7f261bb3d99c0a4343a1892/ruff-0.12.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9949d01d64fa3672449a51ddb5d7548b33e130240ad418884ee6efa7a229586", size = 10485993, upload-time = "2025-07-17T17:26:40.68Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/b8/22a43d23a1f68df9b88f952616c8508ea6ce4ed4f15353b8168c48b2d7e7/ruff-0.12.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:be0593c69df9ad1465e8a2d10e3defd111fdb62dcd5be23ae2c06da77e8fcffb", size = 10022824, upload-time = "2025-07-17T17:26:43.564Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/70/37c234c220366993e8cffcbd6cadbf332bfc848cbd6f45b02bade17e0149/ruff-0.12.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7dea966bcb55d4ecc4cc3270bccb6f87a337326c9dcd3c07d5b97000dbff41c", size = 11524414, upload-time = "2025-07-17T17:26:46.219Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/77/c30f9964f481b5e0e29dd6a1fae1f769ac3fd468eb76fdd5661936edd262/ruff-0.12.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:afcfa3ab5ab5dd0e1c39bf286d829e042a15e966b3726eea79528e2e24d8371a", size = 12419216, upload-time = "2025-07-17T17:26:48.883Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/79/af7fe0a4202dce4ef62c5e33fecbed07f0178f5b4dd9c0d2fcff5ab4a47c/ruff-0.12.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c057ce464b1413c926cdb203a0f858cd52f3e73dcb3270a3318d1630f6395bb3", size = 11976756, upload-time = "2025-07-17T17:26:51.754Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/d1/33fb1fc00e20a939c305dbe2f80df7c28ba9193f7a85470b982815a2dc6a/ruff-0.12.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e64b90d1122dc2713330350626b10d60818930819623abbb56535c6466cce045", size = 11020019, upload-time = "2025-07-17T17:26:54.265Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/f4/e3cd7f7bda646526f09693e2e02bd83d85fff8a8222c52cf9681c0d30843/ruff-0.12.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2abc48f3d9667fdc74022380b5c745873499ff827393a636f7a59da1515e7c57", size = 11277890, upload-time = "2025-07-17T17:26:56.914Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/d0/69a85fb8b94501ff1a4f95b7591505e8983f38823da6941eb5b6badb1e3a/ruff-0.12.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2b2449dc0c138d877d629bea151bee8c0ae3b8e9c43f5fcaafcd0c0d0726b184", size = 10348539, upload-time = "2025-07-17T17:26:59.381Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/a0/91372d1cb1678f7d42d4893b88c252b01ff1dffcad09ae0c51aa2542275f/ruff-0.12.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:56e45bb11f625db55f9b70477062e6a1a04d53628eda7784dce6e0f55fd549eb", size = 10009579, upload-time = "2025-07-17T17:27:02.462Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/1b/c4a833e3114d2cc0f677e58f1df6c3b20f62328dbfa710b87a1636a5e8eb/ruff-0.12.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:478fccdb82ca148a98a9ff43658944f7ab5ec41c3c49d77cd99d44da019371a1", size = 10942982, upload-time = "2025-07-17T17:27:05.343Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/ce/ce85e445cf0a5dd8842f2f0c6f0018eedb164a92bdf3eda51984ffd4d989/ruff-0.12.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:0fc426bec2e4e5f4c4f182b9d2ce6a75c85ba9bcdbe5c6f2a74fcb8df437df4b", size = 11343331, upload-time = "2025-07-17T17:27:08.652Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/cf/441b7fc58368455233cfb5b77206c849b6dfb48b23de532adcc2e50ccc06/ruff-0.12.4-py3-none-win32.whl", hash = "sha256:4de27977827893cdfb1211d42d84bc180fceb7b72471104671c59be37041cf93", size = 10267904, upload-time = "2025-07-17T17:27:11.814Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/7e/20af4a0df5e1299e7368d5ea4350412226afb03d95507faae94c80f00afd/ruff-0.12.4-py3-none-win_amd64.whl", hash = "sha256:fe0b9e9eb23736b453143d72d2ceca5db323963330d5b7859d60d101147d461a", size = 11209038, upload-time = "2025-07-17T17:27:14.417Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/02/8857d0dfb8f44ef299a5dfd898f673edefb71e3b533b3b9d2db4c832dd13/ruff-0.12.4-py3-none-win_arm64.whl", hash = "sha256:0618ec4442a83ab545e5b71202a5c0ed7791e8471435b94e655b570a5031a98e", size = 10469336, upload-time = "2025-07-17T17:27:16.913Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/61/11d458dc6ac22504fd8e237b29dfd40504c7fbbcc8930402cfe51a8e63ed/ruff-0.15.13-py3-none-linux_armv6l.whl", hash = "sha256:444b580fc72fd6887e650acd3e575e18cdc79dbcf42fb4030b491057921f61f8", size = 10738279, upload-time = "2026-05-14T13:44:18.7Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/ca/caa871ee7be718c45256fada4e16a218ee3e33f0c4a46b729a60a24912e6/ruff-0.15.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6590d009e7cb7ebf36f83dbdd44a3fa48a0994ff6f1cdc1b08006abe58f98dc7", size = 11124798, upload-time = "2026-05-14T13:44:06.427Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/19/43f5f2e568dddde567fc41f8471f9432c09563e19d3e617a48cfa52f8f0a/ruff-0.15.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1c26d2f66163deeb6e08d8b39fbbe983ce3c71cea06a6d7591cfd1421793c629", size = 10460761, upload-time = "2026-05-14T13:44:04.375Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/df/cf938cd6de3003178f03ad7c1ea2a6c099468c03a35037985070b37e76be/ruff-0.15.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbd6f94b434f896308e4d57fb7bfde0d02b99f7a64b3bdab0fdfa6a864203a5", size = 10804451, upload-time = "2026-05-14T13:44:25.221Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/7d/5d0973129b154ded2225729169d7068f26b467760b146493fde138415f23/ruff-0.15.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf3259f3be4d181bda591da5db2571aed6853c6a048157756448020bc6c5cd22", size = 10534285, upload-time = "2026-05-14T13:44:08.888Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/e3/6b999bbc66cd51e5f073842bc2a3995e99c5e0e72e16b15e7261f7abf57a/ruff-0.15.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae9c17e5eb4430c154e76abc25d79a318190f5a997f38fb6b114416c5319ffc9", size = 11312063, upload-time = "2026-05-14T13:44:11.274Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/5a/642639e9f5db04f1e97fbd6e091c6fd20725bdf072fb114d00eefb9e6eb8/ruff-0.15.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e2e39bff6c341f4b577a21b801326fab0b11847f48fcaa83f00a113c9b3cb55", size = 12183079, upload-time = "2026-05-14T13:44:01.634Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/4c/7585735f6b53b0f12de13618b2f7d250a844f018822efc899df2e7b8295f/ruff-0.15.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e8d9a8e08013542e94d3220bc5b62cc3e5ef87c5f74bff367d3fac14fab013e6", size = 11440833, upload-time = "2026-05-14T13:43:59.043Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/31/bf1a0803d077e679cfeee5f2f67290a0fa79c7385b5d9a8c17b9db2c48f0/ruff-0.15.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc411dfebe5eebe55ce041c6ae080eb7668955e866daa2fbb16692a784f1c4ca", size = 11434486, upload-time = "2026-05-14T13:44:27.761Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/4e/62c9b999875d4f14db80f277c030578f5e249c9852d65b7ac7ad0b43c041/ruff-0.15.13-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:768494eb08b9cee54e2fd27969966f74db5a57f6eaa7a90fcb3306af34dfc4bd", size = 11385189, upload-time = "2026-05-14T13:44:13.704Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/89/7e959047a104df3eb12863447c110140191fc5b6c4f379ea2e803fcdb0e4/ruff-0.15.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:fb75f9a3a7e42ffe117d734494e6c5e5cb3565d66e12612cb63d0e572a41a5b6", size = 10781380, upload-time = "2026-05-14T13:43:56.734Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/52/5fd18f3b88cab63e88aa11516b3b4e1e5f720e5c330f8dbe5c26210f41f8/ruff-0.15.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8cb74dd33bb2f6613faf7fc03b660053b5ac4f80e706d5788c6335e2a8048d51", size = 10540605, upload-time = "2026-05-14T13:44:20.748Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/e0/9e35f338990d3e41a82875ff7053ffe97541dae81c9d02143177f381d572/ruff-0.15.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:7ef823f817fcd191dc934e984be9cf4094f808effa16f2542ad8e821ba02bbf2", size = 11036554, upload-time = "2026-05-14T13:44:16.256Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/13/070fb048c24080fba188f66371e2a92785be257ad02242066dc7255ac6e9/ruff-0.15.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f345a13937bd7f09f6f5d19fa0721b0c103e00e7f62bc67089a8e5e037719e0b", size = 11528133, upload-time = "2026-05-14T13:44:22.808Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/8c/b1e1666aef7fc6555094d73ae6cd981701781ae85b97ceefc0eebd0b4668/ruff-0.15.13-py3-none-win32.whl", hash = "sha256:4044f94208b3b05ba0fc4a4abd0558cf4d6459bd18325eead7fd8cc66f909b41", size = 10721455, upload-time = "2026-05-14T13:44:35.697Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/a6/870a3e8a50590bb92be184ad928c2922f088b00d9dc5c5ec7b924ee08c22/ruff-0.15.13-py3-none-win_amd64.whl", hash = "sha256:7064884d442b7d477b4e7473d12da7f08851d2b1982763c5d3f388a19468a1a4", size = 11900409, upload-time = "2026-05-14T13:44:30.389Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/36/9c015cd052fca743dae8cb2aeb16b551444787467db42ceab0fc968865af/ruff-0.15.13-py3-none-win_arm64.whl", hash = "sha256:2471da9bd1068c8c064b5fd9c0c4b6dddffd6369cb1cd68b29993b1709ff1b21", size = 11179336, upload-time = "2026-05-14T13:44:33.026Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.16.0"
|
||||
version = "1.17.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041, upload-time = "2021-05-05T14:18:18.379Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053, upload-time = "2021-05-05T14:18:17.237Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "soupsieve"
|
||||
version = "2.6"
|
||||
version = "2.7"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d7/ce/fbaeed4f9fb8b2daa961f90591662df6a86c1abf25c548329a86920aedfb/soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb", size = 101569, upload-time = "2024-08-13T13:39:12.166Z" }
|
||||
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" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/c2/fe97d779f3ef3b15f05c94a2f1e3d21732574ed441687474db9d342a7315/soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9", size = 36186, upload-time = "2024-08-13T13:39:10.986Z" },
|
||||
{ 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" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlparse"
|
||||
version = "0.5.1"
|
||||
version = "0.5.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/73/82/dfa23ec2cbed08a801deab02fe7c904bfb00765256b155941d789a338c68/sqlparse-0.5.1.tar.gz", hash = "sha256:bb6b4df465655ef332548e24f08e205afc81b9ab86cb1c45657a7ff173a3a00e", size = 84502, upload-time = "2024-07-15T19:30:27.085Z" }
|
||||
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" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/a5/b2860373aa8de1e626b2bdfdd6df4355f0565b47e51f7d0c54fe70faf8fe/sqlparse-0.5.1-py3-none-any.whl", hash = "sha256:773dcbf9a5ab44a090f3441e2180efe2560220203dc2f8c0b0fa141e18b505e4", size = 44156, upload-time = "2024-07-15T19:30:25.033Z" },
|
||||
{ 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" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -522,26 +703,35 @@ wheels = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tzdata"
|
||||
version = "2024.1"
|
||||
name = "typing-extensions"
|
||||
version = "4.14.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/74/5b/e025d02cb3b66b7b76093404392d4b44343c69101cc85f4d180dd5784717/tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd", size = 190559, upload-time = "2024-02-11T23:22:40.2Z" }
|
||||
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" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/65/58/f9c9e6be752e9fcb8b6a0ee9fb87e6e7a1f6bcab2cdc73f02bb7ba91ada0/tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252", size = 345370, upload-time = "2024-02-11T23:22:38.223Z" },
|
||||
{ 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" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tzdata"
|
||||
version = "2025.2"
|
||||
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" }
|
||||
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" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.2.2"
|
||||
version = "2.5.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/43/6d/fa469ae21497ddc8bc93e5877702dca7cb8f911e337aca7452b5724f1bb6/urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168", size = 292266, upload-time = "2024-06-17T13:40:11.401Z" }
|
||||
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" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/1c/89ffc63a9605b583d5df2be791a27bc1a42b7c32bab68d3c8f2f73a98cd4/urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472", size = 121444, upload-time = "2024-06-17T13:40:07.795Z" },
|
||||
{ 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" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wagtail"
|
||||
version = "6.2.1"
|
||||
version = "7.4.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyascii" },
|
||||
@@ -551,34 +741,48 @@ dependencies = [
|
||||
{ name = "django-modelcluster" },
|
||||
{ name = "django-permissionedforms" },
|
||||
{ name = "django-taggit" },
|
||||
{ name = "django-tasks" },
|
||||
{ name = "django-treebeard" },
|
||||
{ name = "djangorestframework" },
|
||||
{ name = "draftjs-exporter" },
|
||||
{ name = "l18n" },
|
||||
{ name = "laces" },
|
||||
{ name = "modelsearch" },
|
||||
{ name = "openpyxl" },
|
||||
{ name = "pillow" },
|
||||
{ name = "requests" },
|
||||
{ name = "telepath" },
|
||||
{ name = "willow", extra = ["heif"] },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6d/97/97b59c6bcd913f394e9f475dd952466954e9959ca9e10a45d4b251141eae/wagtail-6.2.1.tar.gz", hash = "sha256:0f136ef23b157997a44fa46543a320a31437350951cf13add8ea8b69cc5e8385", size = 6516190, upload-time = "2024-08-20T15:40:59.778Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/81/da/c060bd9ec00335336e04a610cd1d6762e459ade9c4983f7ecddae15af36d/wagtail-7.4.1.tar.gz", hash = "sha256:c7232cbe16afa5842c236e069b2baead3b3e87e9dc90a7493330b54b69e18b83", size = 6944979, upload-time = "2026-05-19T17:26:18.762Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/b0/942f6bca14bfc2fbe786ebbaa90a831b71cbe3592e3da3f34d6be9f775b1/wagtail-6.2.1-py3-none-any.whl", hash = "sha256:31d073ea8acdc973ef45c5719977a1bb122ad0fc3f01348f37e922128200b42a", size = 8976424, upload-time = "2024-08-20T15:40:54.365Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/c8/162fff3c5ef9e158127c9f4aa1f0d38edd8d1fa242629ae1d8af3b6306c7/wagtail-7.4.1-py3-none-any.whl", hash = "sha256:a42dc18b0fb818649c0d704ea50a2dea06d1b75614abdf837e90766263645c76", size = 9597601, upload-time = "2026-05-19T17:26:12.53Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wagtail-factories"
|
||||
version = "4.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "factory-boy" },
|
||||
{ name = "wagtail" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/42/8c/2ece22c2757bb3f6ef34f36cf185246b136134cf594b7b1dde92cb0a331f/wagtail_factories-4.4.0.tar.gz", hash = "sha256:c77c13d438a2e999a9220ff1829f060116013aa519a81944577353f460ba8cba", size = 9939, upload-time = "2026-02-10T15:14:13.7Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/85/bd5aeaa54a10b6defca927f75bc3890acb7c3a9a23df8e10f3aa42e75807/wagtail_factories-4.4.0-py2.py3-none-any.whl", hash = "sha256:be0abb96d36bf0e3c733d7520fc1944441a379ab06d93af447fc4e71b67e2a01", size = 10754, upload-time = "2026-02-10T15:14:12.773Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wagtail-grapple"
|
||||
version = "0.26.0"
|
||||
version = "0.31.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "graphene-django" },
|
||||
{ name = "wagtail" },
|
||||
{ name = "wagtail-headless-preview" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fc/36/2ad789a09d78b786eccf359ff88a596bcacfff4b9dd6505b68ad3c7f62f5/wagtail_grapple-0.26.0.tar.gz", hash = "sha256:68be091f7feb95bfbe0377bd03c1814a64a6150ae964ec593670d995f11780e5", size = 38362, upload-time = "2024-06-26T10:39:30.511Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9c/29/8cab682abde7f3a8d78a29aac6e441b7a9a20d9baf850c7fc438ad086c48/wagtail_grapple-0.31.0.tar.gz", hash = "sha256:5a70e88dca5188181e42306b207b1e38cf5df4452aa62e764b9e997e2ea19797", size = 39240, upload-time = "2026-04-21T09:24:28.195Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/81/189560b651d61d71c81841f56249dcea3ab441bfcb2adb5afe7b5e516085/wagtail_grapple-0.26.0-py3-none-any.whl", hash = "sha256:de9d3c336d151279976d67f781c758ac5efc7d2bb003c2e311e6fbeea1554c41", size = 47759, upload-time = "2024-06-26T10:39:28.849Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/08/05/d80a7675e359fa62d45b92f14e42850fcb5f42c4fec3950951585b0a0975/wagtail_grapple-0.31.0-py3-none-any.whl", hash = "sha256:fed9205df68861d0ec6c5be67ac5aeb00775ebabde6baa38d582e723076befc0", size = 49251, upload-time = "2026-04-21T09:24:26.18Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -595,24 +799,24 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "whitenoise"
|
||||
version = "6.7.0"
|
||||
version = "6.12.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/64/b8/86451d63ef5e1a9c480b52759d9db25ba85c3420ebdaf039057ed152a4c1/whitenoise-6.7.0.tar.gz", hash = "sha256:58c7a6cd811e275a6c91af22e96e87da0b1109e9a53bb7464116ef4c963bf636", size = 24973, upload-time = "2024-06-19T16:20:09.11Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cb/2a/55b3f3a4ec326cd077c1c3defeee656b9298372a69229134d930151acd01/whitenoise-6.12.0.tar.gz", hash = "sha256:f723ebb76a112e98816ff80fcea0a6c9b8ecde835f8ddda25df7a30a3c2db6ad", size = 26841, upload-time = "2026-02-27T00:05:42.028Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/42/68400d8ad59f67a1f7e12c2f39089ce005f08f73333f3e215f3d5ed6453c/whitenoise-6.7.0-py3-none-any.whl", hash = "sha256:a1ae85e01fdc9815d12fa33f17765bc132ed2c54fa76daf9e39e879dd93566f6", size = 19905, upload-time = "2024-06-19T16:20:03.269Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/eb/d5583a11486211f3ebd4b385545ae787f32363d453c19fffd81106c9c138/whitenoise-6.12.0-py3-none-any.whl", hash = "sha256:fc5e8c572e33ebf24795b47b6a7da8da3c00cff2349f5b04c02f28d0cc5a3cc2", size = 20302, upload-time = "2026-02-27T00:05:40.086Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "willow"
|
||||
version = "1.8.0"
|
||||
version = "1.11.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "defusedxml" },
|
||||
{ name = "filetype" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/77/c3/9d19db05d3abe0375e8d6bf2d3f7ea6f8df266d5ddabcc5a114c8a7cce1f/willow-1.8.0.tar.gz", hash = "sha256:ef3df6cde80d4914e719188147bef1d71c240edb118340e0c5957ecc8fe08315", size = 113313, upload-time = "2024-01-17T19:11:58.712Z" }
|
||||
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" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/95/7a/a48cf90bbf226f793d830c3bbdb7f0c4aa8087c08ce84cc864a1c882a457/willow-1.8.0-py3-none-any.whl", hash = "sha256:48ccf5ce48ccd29c37a32497cd7af50983f8570543c4de2988b15d583efc66be", size = 119223, upload-time = "2024-01-17T19:11:56.316Z" },
|
||||
{ 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" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
|
||||
@@ -11,6 +11,7 @@ from wagtail.admin.panels import FieldPanel, FieldRowPanel, MultiFieldPanel
|
||||
from wagtail.fields import RichTextField, StreamField
|
||||
from wagtail.models import Page
|
||||
from wagtail.search import index
|
||||
from wagtail_headless_preview.models import HeadlessMixin
|
||||
|
||||
from dnscms.blocks import ImageSliderBlock
|
||||
from dnscms.fields import CommonStreamField
|
||||
@@ -18,7 +19,7 @@ from dnscms.wordpress.models import WPImportedPageMixin
|
||||
|
||||
|
||||
@register_singular_query_field("venueIndex")
|
||||
class VenueIndex(Page):
|
||||
class VenueIndex(HeadlessMixin, Page):
|
||||
# there can only be one venue index page
|
||||
max_count = 1
|
||||
subpage_types = ["venues.VenuePage"]
|
||||
@@ -35,7 +36,7 @@ class VenueIndex(Page):
|
||||
|
||||
|
||||
@register_singular_query_field("venueRentalIndex")
|
||||
class VenueRentalIndex(Page):
|
||||
class VenueRentalIndex(HeadlessMixin, Page):
|
||||
# there can only be one venue index page
|
||||
max_count = 1
|
||||
subpage_types = []
|
||||
@@ -51,7 +52,7 @@ class VenueRentalIndex(Page):
|
||||
graphql_fields = [GraphQLRichText("lead"), GraphQLStreamfield("body")]
|
||||
|
||||
|
||||
class VenuePage(WPImportedPageMixin, Page):
|
||||
class VenuePage(HeadlessMixin, WPImportedPageMixin, Page):
|
||||
# no children
|
||||
subpage_types = []
|
||||
parent_page_types = ["venues.VenueIndex"]
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
[tools]
|
||||
python = "3.14"
|
||||
uv = "latest"
|
||||
node = "24"
|
||||
prek = "latest"
|
||||
@@ -0,0 +1,29 @@
|
||||
exclude = '^web/src/gql/'
|
||||
|
||||
[[repos]]
|
||||
repo = "https://github.com/pre-commit/pre-commit-hooks"
|
||||
rev = "v6.0.0"
|
||||
hooks = [
|
||||
{ id = "end-of-file-fixer" },
|
||||
{ id = "trailing-whitespace" },
|
||||
{ id = "check-yaml" },
|
||||
{ id = "check-toml" },
|
||||
{ id = "check-merge-conflict" },
|
||||
{ id = "check-added-large-files" },
|
||||
{ id = "debug-statements" },
|
||||
]
|
||||
|
||||
[[repos]]
|
||||
repo = "https://github.com/astral-sh/ruff-pre-commit"
|
||||
rev = "v0.15.13"
|
||||
|
||||
[[repos.hooks]]
|
||||
id = "ruff-check"
|
||||
args = ["--fix"]
|
||||
files = '^dnscms/.*\.py$'
|
||||
exclude = '/migrations/'
|
||||
|
||||
[[repos.hooks]]
|
||||
id = "ruff-format"
|
||||
files = '^dnscms/.*\.py$'
|
||||
exclude = '/migrations/'
|
||||
@@ -1,2 +1,2 @@
|
||||
GRAPHQL_ENDPOINT=https://cms.neuf.no/api/graphql/
|
||||
WAGTAIL_BASE_URL=https://cms.neuf.no
|
||||
URL=http://localhost:3000
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# Based on https://github.com/vercel/next.js/tree/canary/examples/with-docker
|
||||
|
||||
FROM node:22-alpine AS base
|
||||
FROM node:24-alpine AS base
|
||||
|
||||
# Install dependencies only when needed
|
||||
FROM base AS deps
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
||||
|
||||
## Development
|
||||
|
||||
Run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Update GraphQL definitions from `http://127.0.0.1:8000/api/graphql/`:
|
||||
|
||||
```bash
|
||||
npm run codegen
|
||||
```
|
||||
+19
-1
@@ -3,13 +3,31 @@ import { CodegenConfig } from "@graphql-codegen/cli";
|
||||
import { loadEnvConfig } from "@next/env";
|
||||
loadEnvConfig(process.cwd());
|
||||
|
||||
const wagtailBaseUrl = process.env.WAGTAIL_BASE_URL;
|
||||
if (!wagtailBaseUrl) {
|
||||
throw new Error("WAGTAIL_BASE_URL is not set");
|
||||
}
|
||||
const graphqlEndpoint = `${wagtailBaseUrl.replace(/\/$/, "")}/api/graphql/`;
|
||||
|
||||
const config: CodegenConfig = {
|
||||
schema: process.env.GRAPHQL_ENDPOINT,
|
||||
schema: graphqlEndpoint,
|
||||
documents: ["src/**/*.tsx", "src/**/*.ts"],
|
||||
ignoreNoDocuments: true, // for better experience with the watcher
|
||||
generates: {
|
||||
"./src/gql/": {
|
||||
preset: "client",
|
||||
presetConfig: {
|
||||
fragmentMasking: { unmaskFunctionName: "unmaskFragment" },
|
||||
},
|
||||
config: {
|
||||
scalars: {
|
||||
DateTime: "string",
|
||||
JSONString: "string",
|
||||
PositiveInt: "number",
|
||||
RichText: "string",
|
||||
UUID: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
[tools]
|
||||
node = "22"
|
||||
@@ -1,4 +1,8 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
const nextConfig = {
|
||||
output: "standalone",
|
||||
images: {
|
||||
@@ -12,6 +16,10 @@ const nextConfig = {
|
||||
hostname: "**",
|
||||
},
|
||||
],
|
||||
dangerouslyAllowLocalIP: process.env.NODE_ENV === "development",
|
||||
},
|
||||
turbopack: {
|
||||
root: __dirname,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Generated
+2517
-2590
File diff suppressed because it is too large
Load Diff
+26
-21
@@ -10,31 +10,36 @@
|
||||
"codegen": "graphql-codegen"
|
||||
},
|
||||
"dependencies": {
|
||||
"@graphql-codegen/cli": "^5.0.7",
|
||||
"@graphql-codegen/client-preset": "^4.8.3",
|
||||
"@parcel/watcher": "^2.5.1",
|
||||
"@sindresorhus/slugify": "^2.2.1",
|
||||
"@urql/next": "^1.1.5",
|
||||
"@graphql-codegen/cli": "^7.0.0",
|
||||
"@graphql-codegen/client-preset": "^6.0.0",
|
||||
"@parcel/watcher": "^2.5.6",
|
||||
"@sindresorhus/slugify": "^3.0.0",
|
||||
"@urql/next": "^2.0.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"date-fns-tz": "^3.2.0",
|
||||
"graphql": "^16.11.0",
|
||||
"next": "^15.4.6",
|
||||
"nuqs": "^2.4.3",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"react-intersection-observer": "^9.16.0",
|
||||
"sass": "^1.90.0",
|
||||
"sharp": "^0.34.3",
|
||||
"swiper": "^11.2.10",
|
||||
"urql": "^4.2.2",
|
||||
"use-debounce": "^10.0.5"
|
||||
"graphql": "^16.14.0",
|
||||
"next": "^16.2.6",
|
||||
"nuqs": "^2.8.9",
|
||||
"react": "19.2.6",
|
||||
"react-dom": "19.2.6",
|
||||
"react-intersection-observer": "^10.0.3",
|
||||
"sass": "^1.99.0",
|
||||
"sharp": "^0.34.5",
|
||||
"swiper": "^12.1.4",
|
||||
"urql": "^5.0.2",
|
||||
"use-debounce": "^10.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22",
|
||||
"@types/react": "^19.1.9",
|
||||
"@types/react-dom": "^19.1.7",
|
||||
"@types/node": "^24",
|
||||
"@types/react": "19.2.14",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"baseline-browser-mapping": "^2.10.29",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.4.6",
|
||||
"typescript": "^5"
|
||||
"eslint-config-next": "16.2.6",
|
||||
"typescript": "^6"
|
||||
},
|
||||
"overrides": {
|
||||
"@types/react": "19.2.14",
|
||||
"@types/react-dom": "19.2.3"
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 32 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 17 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 15 KiB |
@@ -1,41 +1,15 @@
|
||||
import { graphql } from "@/gql";
|
||||
import { GenericFragment } from "@/gql/graphql";
|
||||
import { getClient } from "@/app/client";
|
||||
import { Metadata, ResolvingMetadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
import { PageHeader } from "@/components/general/PageHeader";
|
||||
import { PageContent } from "@/components/general/PageContent";
|
||||
import { BgPig } from "@/components/general/BgPig";
|
||||
import {
|
||||
GenericPageView,
|
||||
loadGenericPageProps,
|
||||
} from "@/components/general/GenericPageView";
|
||||
import { getSeoMetadata } from "@/lib/seo";
|
||||
|
||||
export const dynamicParams = false;
|
||||
|
||||
const GenericFragmentDefinition = graphql(`
|
||||
fragment Generic on GenericPage {
|
||||
__typename
|
||||
id
|
||||
urlPath
|
||||
seoTitle
|
||||
searchDescription
|
||||
title
|
||||
lead
|
||||
pig
|
||||
body {
|
||||
...Blocks
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const genericPageByUrlPathQuery = graphql(`
|
||||
query genericPageByUrl($urlPath: String!) {
|
||||
page: page(contentType: "generic.GenericPage", urlPath: $urlPath) {
|
||||
... on GenericPage {
|
||||
...Generic
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
function getWagtailUrlPath(url: string[]): string {
|
||||
// for the page /foo/bar we need to look for `/home/foo/bar/`
|
||||
return `/home/${url.join("/")}/`;
|
||||
@@ -78,46 +52,14 @@ export async function generateMetadata(
|
||||
parent: ResolvingMetadata
|
||||
): Promise<Metadata | null> {
|
||||
const { url } = await params;
|
||||
const urlPath = getWagtailUrlPath(url);
|
||||
const { data, error } = await getClient().query(genericPageByUrlPathQuery, {
|
||||
urlPath: urlPath,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
if (!data?.page) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const page = data.page as GenericFragment;
|
||||
const metadata = await getSeoMetadata(page, parent);
|
||||
return metadata;
|
||||
const props = await loadGenericPageProps({ urlPath: getWagtailUrlPath(url) });
|
||||
if (!props) return null;
|
||||
return getSeoMetadata(props.page, parent);
|
||||
}
|
||||
|
||||
export default async function Page({ params }: { params: Params }) {
|
||||
const { url } = await params;
|
||||
const urlPath = getWagtailUrlPath(url);
|
||||
const { data, error } = await getClient().query(genericPageByUrlPathQuery, {
|
||||
urlPath: urlPath,
|
||||
});
|
||||
if (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
|
||||
if (!data?.page) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
const page = data?.page as GenericFragment;
|
||||
|
||||
return (
|
||||
<>
|
||||
<main className="site-main" id="main">
|
||||
<PageHeader heading={page.title} lead={page.lead} />
|
||||
<PageContent blocks={page.body} />
|
||||
</main>
|
||||
{page.pig && <BgPig type={page.pig} color="white" />}
|
||||
</>
|
||||
);
|
||||
const props = await loadGenericPageProps({ urlPath: getWagtailUrlPath(url) });
|
||||
if (!props) return notFound();
|
||||
return <GenericPageView {...props} />;
|
||||
}
|
||||
|
||||
@@ -1,24 +1,13 @@
|
||||
import { Metadata, ResolvingMetadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getClient } from "@/app/client";
|
||||
import { Breadcrumb } from "@/components/general/Breadcrumb";
|
||||
import { ImageFigure } from "@/components/general/Image";
|
||||
import { PageContent } from "@/components/general/PageContent";
|
||||
import {
|
||||
NewsPageView,
|
||||
loadNewsPageProps,
|
||||
} from "@/components/news/NewsPageView";
|
||||
import { graphql } from "@/gql";
|
||||
import { NewsFragment } from "@/gql/graphql";
|
||||
import { formatDate } from "@/lib/date";
|
||||
import { getSeoMetadata } from "@/lib/seo";
|
||||
|
||||
const newsBySlugQuery = graphql(`
|
||||
query newsBySlug($slug: String!) {
|
||||
news: page(contentType: "news.NewsPage", slug: $slug) {
|
||||
... on NewsPage {
|
||||
...News
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export async function generateStaticParams() {
|
||||
const allNewsSlugsQuery = graphql(`
|
||||
query allNewsSlugs {
|
||||
@@ -51,62 +40,14 @@ export async function generateMetadata(
|
||||
parent: ResolvingMetadata
|
||||
): Promise<Metadata | null> {
|
||||
const { slug } = await params;
|
||||
const { data, error } = await getClient().query(newsBySlugQuery, {
|
||||
slug,
|
||||
});
|
||||
if (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
if (!data?.news) {
|
||||
return null;
|
||||
}
|
||||
const news = data.news as NewsFragment;
|
||||
const metadata = await getSeoMetadata(news, parent);
|
||||
return metadata;
|
||||
const props = await loadNewsPageProps({ slug });
|
||||
if (!props) return null;
|
||||
return getSeoMetadata(props.news, parent);
|
||||
}
|
||||
|
||||
export default async function Page({ params }: { params: Params }) {
|
||||
const { slug } = await params;
|
||||
const { data, error } = await getClient().query(newsBySlugQuery, {
|
||||
slug,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
if (!data?.news) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
const news = data?.news as NewsFragment;
|
||||
const featuredImage: any = news.featuredImage;
|
||||
return (
|
||||
<main className="site-main" id="main">
|
||||
<section className="news-header">
|
||||
<Breadcrumb
|
||||
link="/aktuelt"
|
||||
text="Nyhet"
|
||||
date={formatDate(news.firstPublishedAt, "d. MMMM yyyy")}
|
||||
/>
|
||||
<h1 className="news-title">{news.title}</h1>
|
||||
{news.lead && (
|
||||
<div
|
||||
className="lead"
|
||||
dangerouslySetInnerHTML={{ __html: news.lead }}
|
||||
/>
|
||||
)}
|
||||
{featuredImage && (
|
||||
<ImageFigure
|
||||
src={featuredImage.url}
|
||||
alt={featuredImage.alt ?? ""}
|
||||
width={featuredImage.width}
|
||||
height={featuredImage.height}
|
||||
attribution={featuredImage.attribution}
|
||||
sizes="100vw"
|
||||
/>
|
||||
)}
|
||||
</section>
|
||||
<PageContent blocks={news.body} />
|
||||
</main>
|
||||
);
|
||||
const props = await loadNewsPageProps({ slug });
|
||||
if (!props) return notFound();
|
||||
return <NewsPageView {...props} />;
|
||||
}
|
||||
|
||||
@@ -1,39 +1,19 @@
|
||||
import { getClient } from "@/app/client";
|
||||
import { NewsList } from "@/components/news/NewsList";
|
||||
import { Metadata, ResolvingMetadata } from "next";
|
||||
import { PageHeader } from "@/components/general/PageHeader";
|
||||
import { newsQuery, NewsFragment, NewsIndexFragment } from "@/lib/news";
|
||||
import {
|
||||
NewsIndexView,
|
||||
loadNewsIndexProps,
|
||||
} from "@/components/news/NewsIndexView";
|
||||
import { getSeoMetadata } from "@/lib/seo";
|
||||
|
||||
export async function generateMetadata(
|
||||
{ params }: { params: Promise<{}> },
|
||||
_: unknown,
|
||||
parent: ResolvingMetadata
|
||||
): Promise<Metadata | null> {
|
||||
const { data, error } = await getClient().query(newsQuery, {});
|
||||
if (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
if (!data?.index) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const index = data.index as NewsIndexFragment;
|
||||
const metadata = await getSeoMetadata(index, parent);
|
||||
return metadata;
|
||||
const { index } = await loadNewsIndexProps();
|
||||
return getSeoMetadata(index, parent);
|
||||
}
|
||||
|
||||
export default async function Page() {
|
||||
const { data, error } = await getClient().query(newsQuery, {});
|
||||
if (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
const news = (data?.news ?? []) as NewsFragment[];
|
||||
const index = data?.index as NewsIndexFragment;
|
||||
|
||||
return (
|
||||
<main className="site-main" id="main">
|
||||
<PageHeader heading={index.title} lead={index.lead} align="left" />
|
||||
<NewsList news={news} />
|
||||
</main>
|
||||
);
|
||||
const props = await loadNewsIndexProps();
|
||||
return <NewsIndexView {...props} />;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { cookies, draftMode } from "next/headers";
|
||||
|
||||
export async function POST() {
|
||||
(await draftMode()).disable();
|
||||
(await cookies()).delete("preview-token");
|
||||
return new Response(null, { status: 204 });
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { cookies, draftMode } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
// Wagtail-headless-preview directs the editor's preview iframe here with
|
||||
// ?content_type=app.Model&token=<signed>. We stash the token in a cookie,
|
||||
// enable Next.js draft mode, and redirect to the type-dispatching renderer.
|
||||
export async function GET(req: NextRequest) {
|
||||
const token = req.nextUrl.searchParams.get("token");
|
||||
const contentType = req.nextUrl.searchParams.get("content_type");
|
||||
if (!token || !contentType) {
|
||||
return new Response("missing token/content_type", { status: 400 });
|
||||
}
|
||||
|
||||
(await draftMode()).enable();
|
||||
(await cookies()).set("preview-token", token, {
|
||||
httpOnly: true,
|
||||
sameSite: "lax",
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
maxAge: 60 * 60 * 24,
|
||||
path: "/",
|
||||
});
|
||||
|
||||
redirect("/preview/render");
|
||||
}
|
||||
@@ -1,25 +1,13 @@
|
||||
import { Metadata, ResolvingMetadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getClient } from "@/app/client";
|
||||
import { EventDetails } from "@/components/events/EventDetails";
|
||||
import { EventHeader } from "@/components/events/EventHeader";
|
||||
import { BgPig } from "@/components/general/BgPig";
|
||||
import { PageContent } from "@/components/general/PageContent";
|
||||
import {
|
||||
EventPageView,
|
||||
loadEventPageProps,
|
||||
} from "@/components/events/EventPageView";
|
||||
import { graphql } from "@/gql";
|
||||
import { EventFragment } from "@/gql/graphql";
|
||||
import { getEventPig } from "@/lib/event";
|
||||
import { getSeoMetadata } from "@/lib/seo";
|
||||
|
||||
const eventBySlugQuery = graphql(`
|
||||
query eventBySlug($slug: String!) {
|
||||
event: page(contentType: "events.EventPage", slug: $slug) {
|
||||
... on EventPage {
|
||||
...Event
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export async function generateStaticParams() {
|
||||
const allEventSlugsQuery = graphql(`
|
||||
query allEventSlugs {
|
||||
@@ -52,52 +40,14 @@ export async function generateMetadata(
|
||||
parent: ResolvingMetadata
|
||||
): Promise<Metadata | null> {
|
||||
const { slug } = await params;
|
||||
const { data, error } = await getClient().query(eventBySlugQuery, {
|
||||
slug,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
if (!data?.event) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const event = data.event as EventFragment;
|
||||
const metadata = await getSeoMetadata(event, parent);
|
||||
return metadata;
|
||||
const props = await loadEventPageProps({ slug });
|
||||
if (!props) return null;
|
||||
return getSeoMetadata(props.event, parent);
|
||||
}
|
||||
|
||||
export default async function Page({ params }: { params: Params }) {
|
||||
const { slug } = await params;
|
||||
const { data, error } = await getClient().query(eventBySlugQuery, {
|
||||
slug,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
if (!data?.event) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
const event = data.event as EventFragment;
|
||||
const eventPig = getEventPig(event);
|
||||
|
||||
return (
|
||||
<>
|
||||
<main className="site-main" id="main">
|
||||
<EventHeader event={event} />
|
||||
<EventDetails event={event} />
|
||||
{event.lead && (
|
||||
<div
|
||||
className="lead event-lead"
|
||||
dangerouslySetInnerHTML={{ __html: event.lead }}
|
||||
/>
|
||||
)}
|
||||
<PageContent blocks={event.body} />
|
||||
</main>
|
||||
{eventPig && <BgPig type={eventPig} color="white" />}
|
||||
</>
|
||||
);
|
||||
const props = await loadEventPageProps({ slug });
|
||||
if (!props) return notFound();
|
||||
return <EventPageView {...props} />;
|
||||
}
|
||||
|
||||
@@ -1,68 +1,24 @@
|
||||
import { Suspense } from "react";
|
||||
import { Metadata, ResolvingMetadata } from "next";
|
||||
import { getClient } from "@/app/client";
|
||||
import { EventContainer } from "@/components/events/EventContainer";
|
||||
import {
|
||||
eventsOverviewQuery,
|
||||
eventIndexMetadataQuery,
|
||||
EventFragment,
|
||||
EventCategory,
|
||||
EventOrganizer,
|
||||
} from "@/lib/event";
|
||||
import { PageHeader } from "@/components/general/PageHeader";
|
||||
import { EventIndexFragment, VenueFragment } from "@/gql/graphql";
|
||||
EventIndexView,
|
||||
loadEventIndexProps,
|
||||
} from "@/components/events/EventIndexView";
|
||||
import { EventIndexFragment } from "@/gql/graphql";
|
||||
import { eventIndexMetadataQuery } from "@/lib/event";
|
||||
import { getSeoMetadata } from "@/lib/seo";
|
||||
|
||||
export async function generateMetadata(
|
||||
{ params }: { params: Promise<{}> },
|
||||
_: unknown,
|
||||
parent: ResolvingMetadata
|
||||
): Promise<Metadata | null> {
|
||||
const { data, error } = await getClient().query(eventIndexMetadataQuery, {});
|
||||
|
||||
if (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
if (!data?.index) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const index = data.index as EventIndexFragment;
|
||||
const metadata = await getSeoMetadata(index, parent);
|
||||
return metadata;
|
||||
if (error) throw new Error(error.message);
|
||||
if (!data?.index) return null;
|
||||
return getSeoMetadata(data.index as EventIndexFragment, parent);
|
||||
}
|
||||
|
||||
export default async function Page() {
|
||||
const { data, error } = await getClient().query(eventsOverviewQuery, {});
|
||||
if (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
if (
|
||||
!data?.index ||
|
||||
!data?.events?.futureEvents ||
|
||||
!data?.eventCategories ||
|
||||
!data?.eventOrganizers ||
|
||||
!data?.venues
|
||||
) {
|
||||
throw new Error("Failed to render /arrangementer");
|
||||
}
|
||||
|
||||
const index = data?.index as EventIndexFragment;
|
||||
const events = (data?.events?.futureEvents ?? []) as EventFragment[];
|
||||
const eventCategories = (data?.eventCategories ?? []) as EventCategory[];
|
||||
const eventOrganizers = (data?.eventOrganizers ?? []) as EventOrganizer[];
|
||||
const venues = (data?.venues ?? []) as VenueFragment[];
|
||||
|
||||
return (
|
||||
<main className="site-main" id="main">
|
||||
<PageHeader heading="Dette skjer på Chateau Neuf" align="left" />
|
||||
<Suspense>
|
||||
<EventContainer
|
||||
events={events}
|
||||
eventCategories={eventCategories}
|
||||
eventOrganizers={eventOrganizers}
|
||||
venues={venues}
|
||||
/>
|
||||
</Suspense>
|
||||
</main>
|
||||
);
|
||||
const props = await loadEventIndexProps();
|
||||
return <EventIndexView {...props} />;
|
||||
}
|
||||
|
||||
@@ -3,9 +3,15 @@ import "server-only";
|
||||
import { cacheExchange, createClient, fetchExchange } from "@urql/core";
|
||||
import { registerUrql } from "@urql/next/rsc";
|
||||
|
||||
const wagtailBaseUrl = process.env.WAGTAIL_BASE_URL;
|
||||
if (!wagtailBaseUrl) {
|
||||
throw new Error("WAGTAIL_BASE_URL is not set");
|
||||
}
|
||||
const graphqlEndpoint = `${wagtailBaseUrl.replace(/\/$/, "")}/api/graphql/`;
|
||||
|
||||
const makeClient = () => {
|
||||
return createClient({
|
||||
url: process.env.GRAPHQL_ENDPOINT ?? "",
|
||||
url: graphqlEndpoint,
|
||||
exchanges: [cacheExchange, fetchExchange],
|
||||
// requestPolicy: "network-only",
|
||||
fetchOptions: { next: { revalidate: 0 } },
|
||||
|
||||
@@ -1,25 +1,13 @@
|
||||
import { Metadata, ResolvingMetadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getClient } from "@/app/client";
|
||||
import { AssociationHeader } from "@/components/associations/AssociationHeader";
|
||||
import { PageContent } from "@/components/general/PageContent";
|
||||
import {
|
||||
AssociationPageView,
|
||||
loadAssociationPageProps,
|
||||
} from "@/components/associations/AssociationPageView";
|
||||
import { graphql } from "@/gql";
|
||||
import { AssociationFragment } from "@/gql/graphql";
|
||||
import { getSeoMetadata } from "@/lib/seo";
|
||||
|
||||
const associationBySlugQuery = graphql(`
|
||||
query associationBySlug($slug: String!) {
|
||||
association: page(
|
||||
contentType: "associations.AssociationPage"
|
||||
slug: $slug
|
||||
) {
|
||||
... on AssociationPage {
|
||||
...Association
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
type Params = Promise<{ slug: string }>;
|
||||
|
||||
export async function generateMetadata(
|
||||
@@ -27,20 +15,9 @@ export async function generateMetadata(
|
||||
parent: ResolvingMetadata
|
||||
): Promise<Metadata | null> {
|
||||
const { slug } = await params;
|
||||
const { data, error } = await getClient().query(associationBySlugQuery, {
|
||||
slug,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
if (!data?.association) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const association = data.association as AssociationFragment;
|
||||
const metadata = await getSeoMetadata(association, parent);
|
||||
return metadata;
|
||||
const props = await loadAssociationPageProps({ slug });
|
||||
if (!props) return null;
|
||||
return getSeoMetadata(props.association, parent);
|
||||
}
|
||||
|
||||
export async function generateStaticParams() {
|
||||
@@ -70,23 +47,7 @@ export async function generateStaticParams() {
|
||||
|
||||
export default async function Page({ params }: { params: Params }) {
|
||||
const { slug } = await params;
|
||||
const { data, error } = await getClient().query(associationBySlugQuery, {
|
||||
slug,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
if (!data?.association) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
const association = data.association as AssociationFragment;
|
||||
|
||||
return (
|
||||
<main className="site-main" id="main">
|
||||
<AssociationHeader association={association} />
|
||||
<PageContent blocks={association.body} />
|
||||
</main>
|
||||
);
|
||||
const props = await loadAssociationPageProps({ slug });
|
||||
if (!props) return notFound();
|
||||
return <AssociationPageView {...props} />;
|
||||
}
|
||||
|
||||
@@ -1,106 +1,19 @@
|
||||
import { Metadata, ResolvingMetadata } from "next";
|
||||
import { graphql } from "@/gql";
|
||||
import { AssociationFragment, AssociationIndexFragment } from "@/gql/graphql";
|
||||
import { getClient } from "@/app/client";
|
||||
import { AssociationList } from "@/components/associations/AssociationList";
|
||||
import { PageHeader } from "@/components/general/PageHeader";
|
||||
import { PageContent } from "@/components/general/PageContent";
|
||||
import {
|
||||
AssociationIndexView,
|
||||
loadAssociationIndexProps,
|
||||
} from "@/components/associations/AssociationIndexView";
|
||||
import { getSeoMetadata } from "@/lib/seo";
|
||||
|
||||
const allAssociationsQuery = graphql(`
|
||||
query allAssociations {
|
||||
index: associationIndex {
|
||||
... on AssociationIndex {
|
||||
...AssociationIndex
|
||||
}
|
||||
}
|
||||
associations: pages(
|
||||
contentType: "associations.AssociationPage"
|
||||
limit: 1000
|
||||
) {
|
||||
... on AssociationPage {
|
||||
...Association
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export async function generateMetadata(
|
||||
{ params }: { params: Promise<{}> },
|
||||
_: unknown,
|
||||
parent: ResolvingMetadata
|
||||
): Promise<Metadata | null> {
|
||||
const { data, error } = await getClient().query(allAssociationsQuery, {});
|
||||
|
||||
if (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
if (!data?.index) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const index = data.index as AssociationIndexFragment;
|
||||
const metadata = await getSeoMetadata(index, parent);
|
||||
return metadata;
|
||||
const { index } = await loadAssociationIndexProps();
|
||||
return getSeoMetadata(index, parent);
|
||||
}
|
||||
|
||||
const AssociationIndexDefinition = graphql(`
|
||||
fragment AssociationIndex on AssociationIndex {
|
||||
... on AssociationIndex {
|
||||
title
|
||||
seoTitle
|
||||
searchDescription
|
||||
lead
|
||||
body {
|
||||
...Blocks
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const AssociationFragmentDefinition = graphql(`
|
||||
fragment Association on AssociationPage {
|
||||
__typename
|
||||
id
|
||||
slug
|
||||
title
|
||||
seoTitle
|
||||
searchDescription
|
||||
excerpt
|
||||
lead
|
||||
body {
|
||||
...Blocks
|
||||
}
|
||||
logo {
|
||||
url
|
||||
width
|
||||
height
|
||||
}
|
||||
associationType
|
||||
websiteUrl
|
||||
}
|
||||
`);
|
||||
|
||||
export default async function Page() {
|
||||
const { data, error } = await getClient().query(allAssociationsQuery, {});
|
||||
|
||||
if (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
if (!data?.associations || !data.index) {
|
||||
throw new Error("Failed to render /foreninger");
|
||||
}
|
||||
|
||||
const associations = data.associations as AssociationFragment[];
|
||||
const index = data.index as AssociationIndexFragment;
|
||||
|
||||
return (
|
||||
<main className="site-main" id="main">
|
||||
<PageHeader heading={index.title} lead={index.lead} />
|
||||
{index.body && <PageContent blocks={index.body} />}
|
||||
<AssociationList
|
||||
associations={associations}
|
||||
heading="Foreninger og utvalg"
|
||||
/>
|
||||
</main>
|
||||
);
|
||||
const props = await loadAssociationIndexProps();
|
||||
return <AssociationIndexView {...props} />;
|
||||
}
|
||||
|
||||
@@ -1,72 +1,19 @@
|
||||
import { Metadata, ResolvingMetadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
import { graphql } from "@/gql";
|
||||
import { ContactIndexFragment } from "@/gql/graphql";
|
||||
import { getClient } from "@/app/client";
|
||||
import { PageHeader } from "@/components/general/PageHeader";
|
||||
import { PageContent } from "@/components/general/PageContent";
|
||||
import { GeneralContactBlock } from "@/components/blocks/GeneralContactBlock";
|
||||
import {
|
||||
ContactIndexView,
|
||||
loadContactIndexProps,
|
||||
} from "@/components/contact/ContactIndexView";
|
||||
import { getSeoMetadata } from "@/lib/seo";
|
||||
|
||||
const contactQuery = graphql(`
|
||||
query contacts {
|
||||
index: contactIndex {
|
||||
... on ContactIndex {
|
||||
...ContactIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const ContactIndexDefinition = graphql(`
|
||||
fragment ContactIndex on ContactIndex {
|
||||
... on ContactIndex {
|
||||
title
|
||||
seoTitle
|
||||
searchDescription
|
||||
lead
|
||||
body {
|
||||
...Blocks
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export async function generateMetadata(
|
||||
{ params }: { params: Promise<{}> },
|
||||
_: unknown,
|
||||
parent: ResolvingMetadata
|
||||
): Promise<Metadata | null> {
|
||||
const { data, error } = await getClient().query(contactQuery, {});
|
||||
|
||||
if (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
if (!data?.index) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const index = data.index as ContactIndexFragment;
|
||||
const metadata = await getSeoMetadata(index, parent);
|
||||
return metadata;
|
||||
const { index } = await loadContactIndexProps();
|
||||
return getSeoMetadata(index, parent);
|
||||
}
|
||||
|
||||
export default async function Page() {
|
||||
const { data, error } = await getClient().query(contactQuery, {});
|
||||
|
||||
if (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
if (!data?.index) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
const index = data.index as ContactIndexFragment;
|
||||
|
||||
return (
|
||||
<main className="site-main" id="main">
|
||||
<PageHeader heading={index.title} lead={index.lead} />
|
||||
<GeneralContactBlock />
|
||||
{index.body && <PageContent blocks={index.body} />}
|
||||
</main>
|
||||
);
|
||||
const props = await loadContactIndexProps();
|
||||
return <ContactIndexView {...props} />;
|
||||
}
|
||||
|
||||
@@ -1,25 +1,13 @@
|
||||
import { Metadata, ResolvingMetadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getClient } from "@/app/client";
|
||||
import { ImageSliderBlock } from "@/components/blocks/ImageSliderBlock";
|
||||
import { Breadcrumb } from "@/components/general/Breadcrumb";
|
||||
import { PageContent } from "@/components/general/PageContent";
|
||||
import { NeufMap } from "@/components/venues/NeufMap";
|
||||
import { VenueInfo } from "@/components/venues/VenueInfo";
|
||||
import {
|
||||
VenuePageView,
|
||||
loadVenuePageProps,
|
||||
} from "@/components/venues/VenuePageView";
|
||||
import { graphql } from "@/gql";
|
||||
import { VenueFragment } from "@/gql/graphql";
|
||||
import { getSeoMetadata } from "@/lib/seo";
|
||||
|
||||
const venueBySlugQuery = graphql(`
|
||||
query venueBySlug($slug: String!) {
|
||||
venue: page(contentType: "venues.VenuePage", slug: $slug) {
|
||||
... on VenuePage {
|
||||
...Venue
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
type Params = Promise<{ slug: string }>;
|
||||
|
||||
export async function generateMetadata(
|
||||
@@ -27,20 +15,9 @@ export async function generateMetadata(
|
||||
parent: ResolvingMetadata
|
||||
): Promise<Metadata | null> {
|
||||
const { slug } = await params;
|
||||
const { data, error } = await getClient().query(venueBySlugQuery, {
|
||||
slug,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
if (!data?.venue) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const venue = data.venue as VenueFragment;
|
||||
const metadata = await getSeoMetadata(venue, parent);
|
||||
return metadata;
|
||||
const props = await loadVenuePageProps({ slug });
|
||||
if (!props) return null;
|
||||
return getSeoMetadata(props.venue, parent);
|
||||
}
|
||||
|
||||
export async function generateStaticParams() {
|
||||
@@ -70,32 +47,7 @@ export async function generateStaticParams() {
|
||||
|
||||
export default async function Page({ params }: { params: Params }) {
|
||||
const { slug } = await params;
|
||||
const { data, error } = await getClient().query(venueBySlugQuery, {
|
||||
slug,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
if (!data?.venue) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
const venue = data.venue as VenueFragment;
|
||||
const featuredImage: any = venue.featuredImage;
|
||||
|
||||
return (
|
||||
<main className="site-main" id="main">
|
||||
{venue.images && venue.images.length !== 0 && (
|
||||
<ImageSliderBlock block={venue.images[0]} hero />
|
||||
)}
|
||||
<div className="page-header-small">
|
||||
<Breadcrumb link="/utleie" text="Lokale" />
|
||||
<h1 className="page-title">{venue.title}</h1>
|
||||
</div>
|
||||
<PageContent blocks={venue.body} />
|
||||
<VenueInfo venue={venue} />
|
||||
<NeufMap venueSlug={venue.slug} />
|
||||
</main>
|
||||
);
|
||||
const props = await loadVenuePageProps({ slug });
|
||||
if (!props) return notFound();
|
||||
return <VenuePageView {...props} />;
|
||||
}
|
||||
|
||||
@@ -1,111 +1,19 @@
|
||||
import { Metadata, ResolvingMetadata } from "next";
|
||||
import { graphql } from "@/gql";
|
||||
import { VenueFragment, VenueIndexFragment } from "@/gql/graphql";
|
||||
import { getClient } from "@/app/client";
|
||||
import { VenueList } from "@/components/venues/VenueList";
|
||||
import { PageHeader } from "@/components/general/PageHeader";
|
||||
import { PageContent } from "@/components/general/PageContent";
|
||||
import {
|
||||
VenueIndexView,
|
||||
loadVenueIndexProps,
|
||||
} from "@/components/venues/VenueIndexView";
|
||||
import { getSeoMetadata } from "@/lib/seo";
|
||||
|
||||
const venueIndexQuery = graphql(`
|
||||
query venueIndex {
|
||||
index: venueIndex {
|
||||
... on VenueIndex {
|
||||
...VenueIndex
|
||||
}
|
||||
}
|
||||
venues: pages(contentType: "venues.VenuePage", limit: 100) {
|
||||
... on VenuePage {
|
||||
...Venue
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const VenueIndexDefinition = graphql(`
|
||||
fragment VenueIndex on VenueIndex {
|
||||
... on VenueIndex {
|
||||
title
|
||||
seoTitle
|
||||
searchDescription
|
||||
lead
|
||||
body {
|
||||
...Blocks
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const VenueFragmentDefinition = graphql(`
|
||||
fragment Venue on VenuePage {
|
||||
__typename
|
||||
id
|
||||
slug
|
||||
title
|
||||
seoTitle
|
||||
searchDescription
|
||||
images {
|
||||
...Blocks
|
||||
}
|
||||
body {
|
||||
...Blocks
|
||||
}
|
||||
featuredImage {
|
||||
...Image
|
||||
}
|
||||
showAsBookable
|
||||
showInOverview
|
||||
floor
|
||||
preposition
|
||||
usedFor
|
||||
techSpecsUrl
|
||||
capabilityAudio
|
||||
capabilityAudioVideo
|
||||
capabilityBar
|
||||
capabilityLighting
|
||||
capacityLegal
|
||||
capacityStanding
|
||||
capacitySitting
|
||||
}
|
||||
`);
|
||||
|
||||
export async function generateMetadata(
|
||||
{ params }: { params: Promise<{}> },
|
||||
_: unknown,
|
||||
parent: ResolvingMetadata
|
||||
): Promise<Metadata | null> {
|
||||
const { data, error } = await getClient().query(venueIndexQuery, {});
|
||||
|
||||
if (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
if (!data?.index) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const index = (data?.index ?? []) as VenueIndexFragment;
|
||||
const metadata = await getSeoMetadata(index, parent);
|
||||
return metadata;
|
||||
const { index } = await loadVenueIndexProps();
|
||||
return getSeoMetadata(index, parent);
|
||||
}
|
||||
|
||||
export default async function Page() {
|
||||
const { data, error } = await getClient().query(venueIndexQuery, {});
|
||||
|
||||
if (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
if (!data?.index || !data?.venues) {
|
||||
throw new Error("Failed to render /lokaler");
|
||||
}
|
||||
|
||||
const index = data.index as VenueIndexFragment;
|
||||
const venues = (data?.venues ?? []) as VenueFragment[];
|
||||
const visibleVenues = venues.filter((x) => x.showInOverview);
|
||||
|
||||
return (
|
||||
<main className="site-main" id="main">
|
||||
<PageHeader heading={index.title} lead={index.lead} />
|
||||
<PageContent blocks={index.body} />
|
||||
<VenueList venues={visibleVenues} />
|
||||
</main>
|
||||
);
|
||||
const props = await loadVenueIndexProps();
|
||||
return <VenueIndexView {...props} />;
|
||||
}
|
||||
|
||||
+6
-94
@@ -1,97 +1,9 @@
|
||||
import { graphql } from "@/gql";
|
||||
import { EventFragment } from "@/lib/event";
|
||||
import { NewsFragment } from "@/lib/news";
|
||||
import { HomeFragment } from "@/gql/graphql";
|
||||
import { getClient } from "@/app/client";
|
||||
import { FeaturedEvents } from "@/components/events/FeaturedEvents";
|
||||
import { NewsList } from "@/components/news/NewsList";
|
||||
import { Newsletter } from "@/components/general/Newsletter";
|
||||
import { UpcomingEvents } from "@/components/events/UpcomingEvents";
|
||||
import { Pig } from "@/components/general/Pig";
|
||||
import Link from "next/link";
|
||||
import { Icon } from "@/components/general/Icon";
|
||||
import { SectionHeader } from "@/components/general/SectionHeader";
|
||||
import { SectionFooter } from "@/components/general/SectionFooter";
|
||||
|
||||
const HomeFragmentDefinition = graphql(`
|
||||
fragment Home on HomePage {
|
||||
... on HomePage {
|
||||
featuredEvents {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
import {
|
||||
HomePageView,
|
||||
loadHomePageProps,
|
||||
} from "@/components/home/HomePageView";
|
||||
|
||||
export default async function Home() {
|
||||
const homeQuery = graphql(`
|
||||
query home {
|
||||
events: eventIndex {
|
||||
... on EventIndex {
|
||||
futureEvents {
|
||||
... on EventPage {
|
||||
...Event
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
home: page(contentType: "home.HomePage", urlPath: "/home/") {
|
||||
... on HomePage {
|
||||
...Home
|
||||
}
|
||||
}
|
||||
news: pages(contentType: "news.newsPage", order: "-first_published_at", limit: 4) {
|
||||
... on NewsPage {
|
||||
...News
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
const { data, error } = await getClient().query(homeQuery, {});
|
||||
const home = (data?.home ?? []) as HomeFragment;
|
||||
const events = (data?.events?.futureEvents ?? []) as EventFragment[];
|
||||
const news = (data?.news ?? []) as NewsFragment[];
|
||||
|
||||
const featuredEventIds = home.featuredEvents.map((x) => x.id);
|
||||
const featuredEvents = [
|
||||
...events.filter((x) => featuredEventIds.includes(x.id)),
|
||||
...events.filter((x) => !featuredEventIds.includes(x.id)),
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<main className="site-main index" id="main">
|
||||
<FeaturedEvents events={featuredEvents} />
|
||||
<UpcomingEvents events={events} />
|
||||
<div className="infoBlock">
|
||||
<SectionHeader heading="Besøk oss" link="/praktisk" linkText="Praktisk info" />
|
||||
<div>
|
||||
<h2 className="title">Skal du besøke Chateau Neuf?</h2>
|
||||
<p>
|
||||
Vi hjelper deg med å finne frem, og sørger for at du har en fin
|
||||
opplevelse.
|
||||
</p>
|
||||
<Link href="/praktisk#adkomst" className="button">
|
||||
<span>Adresse og adkomst</span>
|
||||
<Icon type="arrowRight" />
|
||||
</Link>
|
||||
<Link href="/praktisk#billetter" className="button">
|
||||
<span>Billetter</span>
|
||||
<Icon type="arrowRight" />
|
||||
</Link>
|
||||
<Link href="/praktisk#apningstider" className="button">
|
||||
<span>Åpningstider</span>
|
||||
<Icon type="arrowRight" />
|
||||
</Link>
|
||||
</div>
|
||||
<div className="pig">
|
||||
<Pig type="point" />
|
||||
</div>
|
||||
<SectionFooter link="/praktisk" linkText="Praktisk info" />
|
||||
</div>
|
||||
<NewsList heading="Siste nytt" featured news={news} />
|
||||
</main>
|
||||
<Newsletter />
|
||||
</>
|
||||
);
|
||||
const props = await loadHomePageProps();
|
||||
return <HomePageView {...props} />;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
import { getClient } from "@/app/client";
|
||||
import { PreviewBanner } from "@/components/general/PreviewBanner";
|
||||
import {
|
||||
AssociationIndexView,
|
||||
loadAssociationIndexProps,
|
||||
} from "@/components/associations/AssociationIndexView";
|
||||
import {
|
||||
AssociationPageView,
|
||||
loadAssociationPageProps,
|
||||
} from "@/components/associations/AssociationPageView";
|
||||
import {
|
||||
ContactIndexView,
|
||||
loadContactIndexProps,
|
||||
} from "@/components/contact/ContactIndexView";
|
||||
import {
|
||||
EventIndexView,
|
||||
loadEventIndexProps,
|
||||
} from "@/components/events/EventIndexView";
|
||||
import {
|
||||
EventPageView,
|
||||
loadEventPageProps,
|
||||
} from "@/components/events/EventPageView";
|
||||
import {
|
||||
GenericPageView,
|
||||
loadGenericPageProps,
|
||||
} from "@/components/general/GenericPageView";
|
||||
import {
|
||||
HomePageView,
|
||||
loadHomePageProps,
|
||||
} from "@/components/home/HomePageView";
|
||||
import {
|
||||
NewsIndexView,
|
||||
loadNewsIndexProps,
|
||||
} from "@/components/news/NewsIndexView";
|
||||
import {
|
||||
NewsPageView,
|
||||
loadNewsPageProps,
|
||||
} from "@/components/news/NewsPageView";
|
||||
import {
|
||||
SponsorsPageView,
|
||||
loadSponsorsPageProps,
|
||||
} from "@/components/sponsor/SponsorsPageView";
|
||||
import {
|
||||
StudioPageView,
|
||||
loadStudioPageProps,
|
||||
} from "@/components/studio/StudioPageView";
|
||||
import {
|
||||
VenueIndexView,
|
||||
loadVenueIndexProps,
|
||||
} from "@/components/venues/VenueIndexView";
|
||||
import {
|
||||
VenuePageView,
|
||||
loadVenuePageProps,
|
||||
} from "@/components/venues/VenuePageView";
|
||||
import {
|
||||
VenueRentalIndexView,
|
||||
loadVenueRentalIndexProps,
|
||||
} from "@/components/venues/VenueRentalIndexView";
|
||||
import { graphql } from "@/gql";
|
||||
import {
|
||||
AssociationFragment,
|
||||
AssociationIndexFragment,
|
||||
ContactIndexFragment,
|
||||
EventFragment,
|
||||
GenericFragment,
|
||||
HomeFragment,
|
||||
NewsFragment,
|
||||
NewsIndexFragment,
|
||||
SponsorsPageFragment,
|
||||
StudioFragment,
|
||||
VenueFragment,
|
||||
VenueIndexFragment,
|
||||
VenueRentalIndexFragment,
|
||||
} from "@/gql/graphql";
|
||||
import { cookies } from "next/headers";
|
||||
export const dynamic = "force-dynamic";
|
||||
export const revalidate = 0;
|
||||
|
||||
const previewPageQuery = graphql(`
|
||||
query previewPage($token: String!) {
|
||||
page: page(token: $token) {
|
||||
__typename
|
||||
... on GenericPage {
|
||||
...Generic
|
||||
}
|
||||
... on StudioPage {
|
||||
...Studio
|
||||
}
|
||||
... on SponsorsPage {
|
||||
...SponsorsPage
|
||||
}
|
||||
... on HomePage {
|
||||
...Home
|
||||
}
|
||||
... on EventPage {
|
||||
...Event
|
||||
}
|
||||
... on NewsPage {
|
||||
...News
|
||||
}
|
||||
... on AssociationPage {
|
||||
...Association
|
||||
}
|
||||
... on VenuePage {
|
||||
...Venue
|
||||
}
|
||||
... on NewsIndex {
|
||||
...NewsIndex
|
||||
}
|
||||
... on AssociationIndex {
|
||||
...AssociationIndex
|
||||
}
|
||||
... on VenueIndex {
|
||||
...VenueIndex
|
||||
}
|
||||
... on VenueRentalIndex {
|
||||
...VenueRentalIndex
|
||||
}
|
||||
... on ContactIndex {
|
||||
...ContactIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
function ExpiredPreview() {
|
||||
return (
|
||||
<main className="site-main" id="main">
|
||||
<h1>Preview session expired</h1>
|
||||
<p>Click Preview again in the Wagtail admin to start a new session.</p>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
function UnsupportedType({ typename }: { typename: string }) {
|
||||
return (
|
||||
<main className="site-main" id="main">
|
||||
<h1>Preview not available</h1>
|
||||
<p>
|
||||
Type <code>{typename}</code> cannot be previewed.
|
||||
</p>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
export default async function PreviewRender() {
|
||||
const token = (await cookies()).get("preview-token")?.value;
|
||||
if (!token) {
|
||||
return <ExpiredPreview />;
|
||||
}
|
||||
|
||||
const { data, error } = await getClient().query(previewPageQuery, { token });
|
||||
if (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
if (!data?.page) {
|
||||
return <ExpiredPreview />;
|
||||
}
|
||||
|
||||
const page = data.page;
|
||||
const view = await (async () => {
|
||||
switch (page.__typename) {
|
||||
case "GenericPage": {
|
||||
const props = await loadGenericPageProps({
|
||||
pageOverride: page as GenericFragment,
|
||||
});
|
||||
return <GenericPageView {...props!} />;
|
||||
}
|
||||
case "StudioPage": {
|
||||
const props = await loadStudioPageProps({
|
||||
pageOverride: page as StudioFragment,
|
||||
});
|
||||
return <StudioPageView {...props} />;
|
||||
}
|
||||
case "SponsorsPage": {
|
||||
const props = await loadSponsorsPageProps({
|
||||
pageOverride: page as SponsorsPageFragment,
|
||||
});
|
||||
return <SponsorsPageView {...props} />;
|
||||
}
|
||||
case "EventPage": {
|
||||
const props = await loadEventPageProps({
|
||||
eventOverride: page as EventFragment,
|
||||
});
|
||||
return <EventPageView {...props!} />;
|
||||
}
|
||||
case "NewsPage": {
|
||||
const props = await loadNewsPageProps({
|
||||
newsOverride: page as NewsFragment,
|
||||
});
|
||||
return <NewsPageView {...props!} />;
|
||||
}
|
||||
case "AssociationPage": {
|
||||
const props = await loadAssociationPageProps({
|
||||
associationOverride: page as AssociationFragment,
|
||||
});
|
||||
return <AssociationPageView {...props!} />;
|
||||
}
|
||||
case "VenuePage": {
|
||||
const props = await loadVenuePageProps({
|
||||
venueOverride: page as VenueFragment,
|
||||
});
|
||||
return <VenuePageView {...props!} />;
|
||||
}
|
||||
case "HomePage": {
|
||||
const props = await loadHomePageProps({
|
||||
homeOverride: page as HomeFragment,
|
||||
});
|
||||
return <HomePageView {...props} />;
|
||||
}
|
||||
case "EventIndex": {
|
||||
const props = await loadEventIndexProps();
|
||||
return <EventIndexView {...props} />;
|
||||
}
|
||||
case "NewsIndex": {
|
||||
const props = await loadNewsIndexProps({
|
||||
indexOverride: page as NewsIndexFragment,
|
||||
});
|
||||
return <NewsIndexView {...props} />;
|
||||
}
|
||||
case "AssociationIndex": {
|
||||
const props = await loadAssociationIndexProps({
|
||||
indexOverride: page as AssociationIndexFragment,
|
||||
});
|
||||
return <AssociationIndexView {...props} />;
|
||||
}
|
||||
case "VenueIndex": {
|
||||
const props = await loadVenueIndexProps({
|
||||
indexOverride: page as VenueIndexFragment,
|
||||
});
|
||||
return <VenueIndexView {...props} />;
|
||||
}
|
||||
case "VenueRentalIndex": {
|
||||
const props = await loadVenueRentalIndexProps({
|
||||
indexOverride: page as VenueRentalIndexFragment,
|
||||
});
|
||||
return <VenueRentalIndexView {...props} />;
|
||||
}
|
||||
case "ContactIndex": {
|
||||
const props = await loadContactIndexProps({
|
||||
indexOverride: page as ContactIndexFragment,
|
||||
});
|
||||
return <ContactIndexView {...props} />;
|
||||
}
|
||||
default:
|
||||
return <UnsupportedType typename={page.__typename ?? "unknown"} />;
|
||||
}
|
||||
})();
|
||||
|
||||
return (
|
||||
<>
|
||||
<PreviewBanner />
|
||||
{view}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,82 +1,19 @@
|
||||
import { Metadata, ResolvingMetadata } from "next";
|
||||
import { graphql } from "@/gql";
|
||||
import { SponsorsPage, SponsorBlock } from "@/gql/graphql";
|
||||
import { getClient } from "@/app/client";
|
||||
import { PageHeader } from "@/components/general/PageHeader";
|
||||
import { PageContent } from "@/components/general/PageContent";
|
||||
import {
|
||||
SponsorsPageView,
|
||||
loadSponsorsPageProps,
|
||||
} from "@/components/sponsor/SponsorsPageView";
|
||||
import { getSeoMetadata } from "@/lib/seo";
|
||||
import { SponsorList } from "@/components/sponsor/SponsorList";
|
||||
|
||||
const sponsorsPageQuery = graphql(`
|
||||
query sponsors {
|
||||
page: sponsorsPage {
|
||||
... on SponsorsPage {
|
||||
...SponsorsPage
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export async function generateMetadata(
|
||||
{ params }: { params: Promise<{}> },
|
||||
_: unknown,
|
||||
parent: ResolvingMetadata
|
||||
): Promise<Metadata | null> {
|
||||
const { data, error } = await getClient().query(sponsorsPageQuery, {});
|
||||
|
||||
if (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
if (!data?.page) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const index = data.page as SponsorsPage;
|
||||
const metadata = await getSeoMetadata(index, parent);
|
||||
return metadata;
|
||||
const { page } = await loadSponsorsPageProps();
|
||||
return getSeoMetadata(page, parent);
|
||||
}
|
||||
|
||||
const SponsorsPageFragmentDefinition = graphql(`
|
||||
fragment SponsorsPage on SponsorsPage {
|
||||
... on SponsorsPage {
|
||||
title
|
||||
seoTitle
|
||||
searchDescription
|
||||
lead
|
||||
body {
|
||||
...Blocks
|
||||
}
|
||||
sponsors {
|
||||
... on SponsorBlock {
|
||||
id
|
||||
name
|
||||
logo {
|
||||
...Image
|
||||
}
|
||||
text
|
||||
website
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export default async function Page() {
|
||||
const { data, error } = await getClient().query(sponsorsPageQuery, {});
|
||||
|
||||
if (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
if (!data?.page) {
|
||||
throw new Error("Failed to render /sponsorer");
|
||||
}
|
||||
|
||||
const page = data.page as SponsorsPage;
|
||||
|
||||
return (
|
||||
<main className="site-main" id="main">
|
||||
<PageHeader heading={page.title} lead={page.lead} />
|
||||
{page.body && <PageContent blocks={page.body} />}
|
||||
<SponsorList sponsors={page.sponsors as SponsorBlock[]} />
|
||||
</main>
|
||||
);
|
||||
const props = await loadSponsorsPageProps();
|
||||
return <SponsorsPageView {...props} />;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Metadata, ResolvingMetadata } from "next";
|
||||
import {
|
||||
StudioPageView,
|
||||
loadStudioPageProps,
|
||||
} from "@/components/studio/StudioPageView";
|
||||
import { getSeoMetadata } from "@/lib/seo";
|
||||
|
||||
export async function generateMetadata(
|
||||
_: unknown,
|
||||
parent: ResolvingMetadata
|
||||
): Promise<Metadata | null> {
|
||||
const { page } = await loadStudioPageProps();
|
||||
return getSeoMetadata(page, parent);
|
||||
}
|
||||
|
||||
export default async function Page() {
|
||||
const props = await loadStudioPageProps();
|
||||
return <StudioPageView {...props} />;
|
||||
}
|
||||
@@ -1,82 +1,19 @@
|
||||
import { Metadata, ResolvingMetadata } from "next";
|
||||
import { graphql } from "@/gql";
|
||||
import { VenueFragment, VenueRentalIndexFragment } from "@/gql/graphql";
|
||||
import { getClient } from "@/app/client";
|
||||
import { VenueList } from "@/components/venues/VenueList";
|
||||
import { PageHeader } from "@/components/general/PageHeader";
|
||||
import { BgPig } from "@/components/general/BgPig";
|
||||
import { PageContent } from "@/components/general/PageContent";
|
||||
import {
|
||||
VenueRentalIndexView,
|
||||
loadVenueRentalIndexProps,
|
||||
} from "@/components/venues/VenueRentalIndexView";
|
||||
import { getSeoMetadata } from "@/lib/seo";
|
||||
|
||||
const venueRentalIndexQuery = graphql(`
|
||||
query venueRentalIndex {
|
||||
index: venueRentalIndex {
|
||||
... on VenueRentalIndex {
|
||||
...VenueRentalIndex
|
||||
}
|
||||
}
|
||||
venues: pages(contentType: "venues.VenuePage", limit: 100) {
|
||||
... on VenuePage {
|
||||
...Venue
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const VenueRentalIndexDefinition = graphql(`
|
||||
fragment VenueRentalIndex on VenueRentalIndex {
|
||||
... on VenueRentalIndex {
|
||||
title
|
||||
seoTitle
|
||||
searchDescription
|
||||
lead
|
||||
body {
|
||||
...Blocks
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export async function generateMetadata(
|
||||
{ params }: { params: Promise<{}> },
|
||||
_: unknown,
|
||||
parent: ResolvingMetadata
|
||||
): Promise<Metadata | null> {
|
||||
const { data, error } = await getClient().query(venueRentalIndexQuery, {});
|
||||
|
||||
if (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
if (!data?.index) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const index = data.index as VenueRentalIndexFragment;
|
||||
const metadata = await getSeoMetadata(index, parent);
|
||||
return metadata;
|
||||
const { index } = await loadVenueRentalIndexProps();
|
||||
return getSeoMetadata(index, parent);
|
||||
}
|
||||
|
||||
export default async function Page() {
|
||||
const { data, error } = await getClient().query(venueRentalIndexQuery, {});
|
||||
|
||||
if (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
if (!data?.index || !data?.venues) {
|
||||
throw new Error("Failed to render /utleie");
|
||||
}
|
||||
|
||||
const index = data.index as VenueRentalIndexFragment;
|
||||
const venues = (data?.venues ?? []) as VenueFragment[];
|
||||
const bookableVenues = venues.filter((venue) => venue.showAsBookable);
|
||||
|
||||
return (
|
||||
<>
|
||||
<main className="site-main" id="main">
|
||||
<PageHeader heading={index.title} lead={index.lead} />
|
||||
{index.body && <PageContent blocks={index.body} />}
|
||||
<VenueList venues={bookableVenues} heading="Våre lokaler" />
|
||||
</main>
|
||||
<BgPig type="key" />
|
||||
</>
|
||||
);
|
||||
const props = await loadVenueRentalIndexProps();
|
||||
return <VenueRentalIndexView {...props} />;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import { graphql } from "@/gql";
|
||||
import { AssociationFragment, AssociationIndexFragment } from "@/gql/graphql";
|
||||
import { getClient } from "@/app/client";
|
||||
import { AssociationList } from "@/components/associations/AssociationList";
|
||||
import { PageHeader } from "@/components/general/PageHeader";
|
||||
import { PageContent } from "@/components/general/PageContent";
|
||||
|
||||
const AssociationIndexDefinition = graphql(`
|
||||
fragment AssociationIndex on AssociationIndex {
|
||||
__typename
|
||||
title
|
||||
seoTitle
|
||||
searchDescription
|
||||
lead
|
||||
body {
|
||||
...Blocks
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const AssociationFragmentDefinition = graphql(`
|
||||
fragment Association on AssociationPage {
|
||||
__typename
|
||||
id
|
||||
slug
|
||||
title
|
||||
seoTitle
|
||||
searchDescription
|
||||
excerpt
|
||||
lead
|
||||
body {
|
||||
...Blocks
|
||||
}
|
||||
logo {
|
||||
url
|
||||
width
|
||||
height
|
||||
}
|
||||
associationType
|
||||
websiteUrl
|
||||
}
|
||||
`);
|
||||
|
||||
const allAssociationsQuery = graphql(`
|
||||
query allAssociations {
|
||||
index: associationIndex {
|
||||
... on AssociationIndex {
|
||||
...AssociationIndex
|
||||
}
|
||||
}
|
||||
associations: pages(
|
||||
contentType: "associations.AssociationPage"
|
||||
limit: 1000
|
||||
) {
|
||||
... on AssociationPage {
|
||||
...Association
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export type AssociationIndexViewProps = {
|
||||
index: AssociationIndexFragment;
|
||||
associations: AssociationFragment[];
|
||||
};
|
||||
|
||||
export async function loadAssociationIndexProps(overrides?: {
|
||||
indexOverride?: AssociationIndexFragment;
|
||||
}): Promise<AssociationIndexViewProps> {
|
||||
const { data, error } = await getClient().query(allAssociationsQuery, {});
|
||||
if (error) throw new Error(error.message);
|
||||
const index =
|
||||
overrides?.indexOverride ?? (data?.index as AssociationIndexFragment | undefined);
|
||||
if (!index) throw new Error("Failed to load /foreninger");
|
||||
const associations = (data?.associations ?? []) as AssociationFragment[];
|
||||
return { index, associations };
|
||||
}
|
||||
|
||||
export function AssociationIndexView({
|
||||
index,
|
||||
associations,
|
||||
}: AssociationIndexViewProps) {
|
||||
return (
|
||||
<main className="site-main" id="main">
|
||||
<PageHeader heading={index.title} lead={index.lead} />
|
||||
{index.body && <PageContent blocks={index.body} />}
|
||||
<AssociationList
|
||||
associations={associations}
|
||||
heading="Foreninger og utvalg"
|
||||
/>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { graphql } from "@/gql";
|
||||
import { AssociationFragment } from "@/gql/graphql";
|
||||
import { getClient } from "@/app/client";
|
||||
import { AssociationHeader } from "@/components/associations/AssociationHeader";
|
||||
import { PageContent } from "@/components/general/PageContent";
|
||||
|
||||
const associationBySlugQuery = graphql(`
|
||||
query associationBySlug($slug: String!) {
|
||||
association: page(
|
||||
contentType: "associations.AssociationPage"
|
||||
slug: $slug
|
||||
) {
|
||||
... on AssociationPage {
|
||||
...Association
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export type AssociationPageViewProps = { association: AssociationFragment };
|
||||
|
||||
export async function loadAssociationPageProps(args: {
|
||||
slug?: string;
|
||||
associationOverride?: AssociationFragment;
|
||||
}): Promise<AssociationPageViewProps | null> {
|
||||
if (args.associationOverride) {
|
||||
return { association: args.associationOverride };
|
||||
}
|
||||
if (!args.slug) throw new Error("loadAssociationPageProps needs slug or associationOverride");
|
||||
const { data, error } = await getClient().query(associationBySlugQuery, {
|
||||
slug: args.slug,
|
||||
});
|
||||
if (error) throw new Error(error.message);
|
||||
const association = data?.association as AssociationFragment | undefined;
|
||||
if (!association) return null;
|
||||
return { association };
|
||||
}
|
||||
|
||||
export function AssociationPageView({ association }: AssociationPageViewProps) {
|
||||
return (
|
||||
<main className="site-main" id="main">
|
||||
<AssociationHeader association={association} />
|
||||
<PageContent blocks={association.body} />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -1,11 +1,22 @@
|
||||
import { AccordionBlock as AccordionBlockType } from "@/gql/graphql";
|
||||
import { graphql } from "@/gql";
|
||||
import { type AccordionBlockFragment } from "@/gql/graphql";
|
||||
import { Blocks } from "./Blocks";
|
||||
import { Accordion } from "@/components/general/Accordion";
|
||||
|
||||
const AccordionBlockFragmentDefinition = graphql(`
|
||||
fragment AccordionBlock on AccordionBlock {
|
||||
heading
|
||||
body {
|
||||
id
|
||||
blockType
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export const AccordionBlock = ({
|
||||
block,
|
||||
}: {
|
||||
block: AccordionBlockType;
|
||||
block: AccordionBlockFragment;
|
||||
}) => {
|
||||
return (
|
||||
<Accordion heading={block.heading}>
|
||||
|
||||
@@ -1,15 +1,33 @@
|
||||
import { ContactEntityBlock as ContactEntityBlockType } from "@/gql/graphql";
|
||||
import { graphql, unmaskFragment } from "@/gql";
|
||||
import { type ContactEntityBlockFragment } from "@/gql/graphql";
|
||||
import styles from "./contactEntityBlock.module.scss";
|
||||
import { formatNorwegianPhoneNumber, formatPhoneE164 } from "@/lib/common";
|
||||
import {
|
||||
ContactEntityFragmentDefinition,
|
||||
ImageFragmentDefinition,
|
||||
formatNorwegianPhoneNumber,
|
||||
formatPhoneE164,
|
||||
} from "@/lib/common";
|
||||
import { Icon } from "../general/Icon";
|
||||
import { Image } from "../general/Image";
|
||||
|
||||
const ContactEntityBlockFragmentDefinition = graphql(`
|
||||
fragment ContactEntityBlock on ContactEntityBlock {
|
||||
contactEntity {
|
||||
...ContactEntity
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export const ContactEntityBlock = ({
|
||||
block,
|
||||
}: {
|
||||
block: ContactEntityBlockType;
|
||||
block: ContactEntityBlockFragment;
|
||||
}) => {
|
||||
const contact = block?.contactEntity;
|
||||
const contact = unmaskFragment(
|
||||
ContactEntityFragmentDefinition,
|
||||
block?.contactEntity
|
||||
);
|
||||
const image = unmaskFragment(ImageFragmentDefinition, contact?.image);
|
||||
|
||||
if (!contact) {
|
||||
return <></>;
|
||||
@@ -21,18 +39,18 @@ export const ContactEntityBlock = ({
|
||||
return (
|
||||
<li className={styles.contactItem}>
|
||||
<div className={styles.image}>
|
||||
{!contact.image && (
|
||||
{!image && (
|
||||
<img
|
||||
src="/assets/graphics/portrait-pig.svg"
|
||||
className={styles.portraitPlaceholder}
|
||||
/>
|
||||
)}
|
||||
{contact.image && (
|
||||
{image && (
|
||||
<Image
|
||||
src={contact.image.url}
|
||||
alt={contact.image.alt ?? ""}
|
||||
width={contact.image.width}
|
||||
height={contact.image.height}
|
||||
src={image.url}
|
||||
alt={image.alt ?? ""}
|
||||
width={image.width}
|
||||
height={image.height}
|
||||
sizes="25vw"
|
||||
className={styles.portrait}
|
||||
/>
|
||||
|
||||
@@ -1,11 +1,25 @@
|
||||
import { ContactListBlock as ContactListBlockType } from "@/gql/graphql";
|
||||
import { graphql } from "@/gql";
|
||||
import { type ContactListBlockFragment } from "@/gql/graphql";
|
||||
import styles from "./contactListBlock.module.scss";
|
||||
import { Blocks } from "./Blocks";
|
||||
|
||||
const ContactListBlockFragmentDefinition = graphql(`
|
||||
fragment ContactListBlock on ContactListBlock {
|
||||
items {
|
||||
blockType
|
||||
... on ContactEntityBlock {
|
||||
contactEntity {
|
||||
...ContactEntity
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export const ContactListBlock = ({
|
||||
block,
|
||||
}: {
|
||||
block: ContactListBlockType;
|
||||
block: ContactListBlockFragment;
|
||||
}) => {
|
||||
return (
|
||||
<ul className={styles.contactList}>
|
||||
|
||||
@@ -1,11 +1,37 @@
|
||||
import { ContactSectionBlock as ContactSectionBlockType } from "@/gql/graphql";
|
||||
import { graphql } from "@/gql";
|
||||
import {
|
||||
type ContactSectionBlockFragment,
|
||||
type ContactSubsectionBlockFragment,
|
||||
} from "@/gql/graphql";
|
||||
import styles from "./contactSection.module.scss";
|
||||
import { Blocks } from "./Blocks";
|
||||
|
||||
const ContactSectionBlockFragmentDefinition = graphql(`
|
||||
fragment ContactSectionBlock on ContactSectionBlock {
|
||||
title
|
||||
text
|
||||
blocks {
|
||||
id
|
||||
blockType
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const ContactSubsectionBlockFragmentDefinition = graphql(`
|
||||
fragment ContactSubsectionBlock on ContactSubsectionBlock {
|
||||
title
|
||||
text
|
||||
blocks {
|
||||
id
|
||||
blockType
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export const ContactSectionBlock = ({
|
||||
block,
|
||||
}: {
|
||||
block: ContactSectionBlockType;
|
||||
block: ContactSectionBlockFragment;
|
||||
}) => {
|
||||
return (
|
||||
<section className={styles.contactSection}>
|
||||
@@ -24,7 +50,7 @@ export const ContactSectionBlock = ({
|
||||
export const ContactSubsectionBlock = ({
|
||||
block,
|
||||
}: {
|
||||
block: ContactSectionBlockType;
|
||||
block: ContactSubsectionBlockFragment;
|
||||
}) => {
|
||||
return (
|
||||
<section className={styles.contactSubsection}>
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
import { EmbedBlock as EmbedBlockType } from "@/gql/graphql";
|
||||
import { graphql } from "@/gql";
|
||||
import { type EmbedBlockFragment } from "@/gql/graphql";
|
||||
import styles from "./embedBlock.module.scss";
|
||||
|
||||
export const EmbedBlock = ({ block }: { block: EmbedBlockType }) => {
|
||||
const EmbedBlockFragmentDefinition = graphql(`
|
||||
fragment EmbedBlock on EmbedBlock {
|
||||
url
|
||||
embed
|
||||
rawEmbed
|
||||
}
|
||||
`);
|
||||
|
||||
export const EmbedBlock = ({ block }: { block: EmbedBlockFragment }) => {
|
||||
if (!block.embed) {
|
||||
return <></>;
|
||||
}
|
||||
@@ -18,7 +27,7 @@ export const EmbedBlock = ({ block }: { block: EmbedBlockType }) => {
|
||||
*/
|
||||
let embedData: any = {};
|
||||
try {
|
||||
embedData = JSON.parse(block.rawEmbed);
|
||||
embedData = block.rawEmbed ? JSON.parse(block.rawEmbed) : {};
|
||||
} catch (e) {
|
||||
embedData = {};
|
||||
}
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import { FactBoxBlock as FactBoxBlockType } from "@/gql/graphql";
|
||||
import { graphql } from "@/gql";
|
||||
import { type FactBoxBlockFragment } from "@/gql/graphql";
|
||||
import styles from "./factBoxBlock.module.scss";
|
||||
|
||||
type FactBoxBlockTypeWithAlias = FactBoxBlockType & {
|
||||
factBoxBody?: string;
|
||||
};
|
||||
const FactBoxBlockFragmentDefinition = graphql(`
|
||||
fragment FactBoxBlock on FactBoxBlock {
|
||||
backgroundColor
|
||||
factBoxBody: body
|
||||
}
|
||||
`);
|
||||
|
||||
export const FactBoxBlock = ({
|
||||
block,
|
||||
}: {
|
||||
block: FactBoxBlockTypeWithAlias;
|
||||
block: FactBoxBlockFragment;
|
||||
}) => {
|
||||
if (!block.factBoxBody) {
|
||||
return <></>;
|
||||
|
||||
@@ -1,22 +1,47 @@
|
||||
import { FeaturedBlock as FeaturedBlockType } from "@/gql/graphql";
|
||||
import { graphql, unmaskFragment } from "@/gql";
|
||||
import { type FeaturedBlockFragment } from "@/gql/graphql";
|
||||
import Link from "next/link";
|
||||
import { Image } from "@/components/general/Image";
|
||||
import { ImageFragmentDefinition } from "@/lib/common";
|
||||
import styles from "./featuredBlock.module.scss";
|
||||
|
||||
// the 'text' field has been aliased to 'featuredBlockText' and i'm
|
||||
// using codegen the wrong way. let's specify the field here and move on
|
||||
type FeaturedBlockTypeWithAlias = FeaturedBlockType & {
|
||||
featuredBlockText: string;
|
||||
};
|
||||
const FeaturedBlockFragmentDefinition = graphql(`
|
||||
fragment FeaturedBlock on FeaturedBlock {
|
||||
title
|
||||
featuredBlockText: text
|
||||
linkText
|
||||
imagePosition
|
||||
backgroundColor
|
||||
featuredPage {
|
||||
contentType
|
||||
pageType
|
||||
url
|
||||
... on EventPage {
|
||||
featuredImage {
|
||||
...Image
|
||||
}
|
||||
}
|
||||
... on NewsPage {
|
||||
featuredImage {
|
||||
...Image
|
||||
}
|
||||
}
|
||||
}
|
||||
featuredImageOverride {
|
||||
...Image
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export const FeaturedBlock = ({
|
||||
block,
|
||||
}: {
|
||||
block: FeaturedBlockTypeWithAlias;
|
||||
block: FeaturedBlockFragment;
|
||||
}) => {
|
||||
const image = !!block.featuredImageOverride
|
||||
? block.featuredImageOverride
|
||||
: null;
|
||||
const image = unmaskFragment(
|
||||
ImageFragmentDefinition,
|
||||
block.featuredImageOverride
|
||||
);
|
||||
// TODO: fetch image from target page
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
import { HorizontalRuleBlock as HorizontalRuleBlockType } from "@/gql/graphql";
|
||||
import { graphql } from "@/gql";
|
||||
import { type HorizontalRuleBlockFragment } from "@/gql/graphql";
|
||||
import { Image } from "@/components/general/Image";
|
||||
import styles from "./horizontalRuleBlock.module.scss";
|
||||
|
||||
const HorizontalRuleBlockFragmentDefinition = graphql(`
|
||||
fragment HorizontalRuleBlock on HorizontalRuleBlock {
|
||||
color
|
||||
}
|
||||
`);
|
||||
|
||||
export const HorizontalRuleBlock = ({
|
||||
block,
|
||||
}: {
|
||||
block: HorizontalRuleBlockType;
|
||||
block: HorizontalRuleBlockFragment;
|
||||
}) => {
|
||||
const knownColors = [
|
||||
"deepBrick",
|
||||
|
||||
@@ -1,8 +1,30 @@
|
||||
"use client";
|
||||
import { ImageSliderBlock as ImageSliderBlockType } from "@/gql/graphql";
|
||||
import { graphql, unmaskFragment, type FragmentType } from "@/gql";
|
||||
import { type ImageSliderBlockFragment } from "@/gql/graphql";
|
||||
import { ImageFigure } from "@/components/general/Image";
|
||||
import { ImageFragmentDefinition } from "@/lib/common";
|
||||
import styles from "./imageSliderBlock.module.scss";
|
||||
|
||||
const ImageSliderItemFragmentDefinition = graphql(`
|
||||
fragment ImageSliderItem on ImageSliderItemBlock {
|
||||
image {
|
||||
...Image
|
||||
}
|
||||
text
|
||||
}
|
||||
`);
|
||||
|
||||
export const ImageSliderBlockFragmentDefinition = graphql(`
|
||||
fragment ImageSliderBlock on ImageSliderBlock {
|
||||
images {
|
||||
__typename
|
||||
... on ImageSliderItemBlock {
|
||||
...ImageSliderItem
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
// import swiper modules & styles
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
import { Pagination, Navigation } from "swiper/modules";
|
||||
@@ -11,17 +33,42 @@ import "swiper/css/pagination";
|
||||
import "swiper/css/navigation";
|
||||
import "./swiper.scss";
|
||||
|
||||
const Slide = ({
|
||||
item: maskedItem,
|
||||
}: {
|
||||
item: FragmentType<typeof ImageSliderItemFragmentDefinition>;
|
||||
}) => {
|
||||
const item = unmaskFragment(ImageSliderItemFragmentDefinition, maskedItem);
|
||||
const image = unmaskFragment(ImageFragmentDefinition, item.image);
|
||||
return (
|
||||
<ImageFigure
|
||||
key={image.id}
|
||||
src={image.url}
|
||||
alt={image.alt ?? ""}
|
||||
width={image.width}
|
||||
height={image.height}
|
||||
attribution={image.attribution}
|
||||
caption={item.text}
|
||||
sizes="100vw"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const ImageSliderBlock = ({
|
||||
block,
|
||||
hero,
|
||||
pageContent
|
||||
pageContent,
|
||||
}: {
|
||||
block: ImageSliderBlockType | any;
|
||||
block: ImageSliderBlockFragment;
|
||||
hero?: boolean;
|
||||
pageContent?: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<div className={styles.imageSliderBlock} data-hero={hero} data-pagecontent={pageContent}>
|
||||
<div
|
||||
className={styles.imageSliderBlock}
|
||||
data-hero={hero}
|
||||
data-pagecontent={pageContent}
|
||||
>
|
||||
<Swiper
|
||||
pagination={{
|
||||
type: "fraction",
|
||||
@@ -30,21 +77,16 @@ export const ImageSliderBlock = ({
|
||||
modules={[Pagination, Navigation]}
|
||||
className="mySwiper"
|
||||
>
|
||||
{block.images &&
|
||||
block.images.map((imageItem: any, index: number) => (
|
||||
{block.images?.map((item, index) => {
|
||||
if (item?.__typename !== "ImageSliderItemBlock") {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<SwiperSlide key={index}>
|
||||
<ImageFigure
|
||||
key={imageItem.image.id}
|
||||
src={imageItem.image.url}
|
||||
alt={imageItem.image.alt ?? ""}
|
||||
width={imageItem.image.width}
|
||||
height={imageItem.image.height}
|
||||
attribution={imageItem.image.attribution}
|
||||
caption={imageItem.text}
|
||||
sizes="100vw"
|
||||
/>
|
||||
<Slide item={item} />
|
||||
</SwiperSlide>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</Swiper>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,18 +1,31 @@
|
||||
import { ImageWithTextBlock as ImageWithTextBlockType } from "@/gql/graphql";
|
||||
import { graphql, unmaskFragment } from "@/gql";
|
||||
import { type ImageWithTextBlockFragment } from "@/gql/graphql";
|
||||
import { ImageFigure } from "@/components/general/Image";
|
||||
import { ImageFragmentDefinition } from "@/lib/common";
|
||||
|
||||
const ImageWithTextBlockFragmentDefinition = graphql(`
|
||||
fragment ImageWithTextBlock on ImageWithTextBlock {
|
||||
image {
|
||||
...Image
|
||||
}
|
||||
imageFormat
|
||||
text
|
||||
}
|
||||
`);
|
||||
|
||||
export function ImageWithTextBlock({
|
||||
block,
|
||||
}: {
|
||||
block: ImageWithTextBlockType;
|
||||
block: ImageWithTextBlockFragment;
|
||||
}) {
|
||||
const image = unmaskFragment(ImageFragmentDefinition, block.image);
|
||||
return (
|
||||
<ImageFigure
|
||||
src={block.image.url}
|
||||
alt={block.image.alt ?? ""}
|
||||
width={block.image.width}
|
||||
height={block.image.height}
|
||||
attribution={block.image.attribution}
|
||||
src={image.url}
|
||||
alt={image.alt ?? ""}
|
||||
width={image.width}
|
||||
height={image.height}
|
||||
attribution={image.attribution}
|
||||
caption={block.text}
|
||||
imageFormat={block.imageFormat}
|
||||
/>
|
||||
|
||||
@@ -1,13 +1,26 @@
|
||||
import { PageSectionBlock as PageSectionBlockType } from "@/gql/graphql";
|
||||
import { graphql } from "@/gql";
|
||||
import { type PageSectionBlockFragment } from "@/gql/graphql";
|
||||
import styles from "./pageSection.module.scss";
|
||||
import { Blocks } from "./Blocks";
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
import { DecorativeIcon } from "../general/Icon";
|
||||
|
||||
const PageSectionBlockFragmentDefinition = graphql(`
|
||||
fragment PageSectionBlock on PageSectionBlock {
|
||||
title
|
||||
backgroundColor
|
||||
icon
|
||||
body {
|
||||
id
|
||||
blockType
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export const PageSectionBlock = ({
|
||||
block,
|
||||
}: {
|
||||
block: PageSectionBlockType;
|
||||
block: PageSectionBlockFragment;
|
||||
}) => {
|
||||
const anchor = slugify(block.title);
|
||||
|
||||
@@ -31,7 +44,7 @@ export const PageSectionBlock = ({
|
||||
export const PageSectionNavigationBlock = ({
|
||||
sections,
|
||||
}: {
|
||||
sections: PageSectionBlockType[];
|
||||
sections: PageSectionBlockFragment[];
|
||||
}) => {
|
||||
if (!sections.length) {
|
||||
return <></>;
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import { graphql } from "@/gql";
|
||||
import { type RichTextBlockFragment } from "@/gql/graphql";
|
||||
import styles from "./richTextBlock.module.scss";
|
||||
|
||||
export const RichTextBlock = ({ block }: any) => {
|
||||
const RichTextBlockFragmentDefinition = graphql(`
|
||||
fragment RichTextBlock on RichTextBlock {
|
||||
rawValue
|
||||
value
|
||||
}
|
||||
`);
|
||||
|
||||
export const RichTextBlock = ({ block }: { block: RichTextBlockFragment }) => {
|
||||
return (
|
||||
<div
|
||||
className={styles.richTextBlock}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { graphql } from "@/gql";
|
||||
import { ContactIndexFragment } from "@/gql/graphql";
|
||||
import { getClient } from "@/app/client";
|
||||
import { GeneralContactBlock } from "@/components/blocks/GeneralContactBlock";
|
||||
import { PageContent } from "@/components/general/PageContent";
|
||||
import { PageHeader } from "@/components/general/PageHeader";
|
||||
|
||||
const ContactIndexDefinition = graphql(`
|
||||
fragment ContactIndex on ContactIndex {
|
||||
__typename
|
||||
title
|
||||
seoTitle
|
||||
searchDescription
|
||||
lead
|
||||
body {
|
||||
...Blocks
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const contactQuery = graphql(`
|
||||
query contacts {
|
||||
index: contactIndex {
|
||||
... on ContactIndex {
|
||||
...ContactIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export type ContactIndexViewProps = { index: ContactIndexFragment };
|
||||
|
||||
export async function loadContactIndexProps(overrides?: {
|
||||
indexOverride?: ContactIndexFragment;
|
||||
}): Promise<ContactIndexViewProps> {
|
||||
if (overrides?.indexOverride) {
|
||||
return { index: overrides.indexOverride };
|
||||
}
|
||||
const { data, error } = await getClient().query(contactQuery, {});
|
||||
if (error) throw new Error(error.message);
|
||||
const index = data?.index as ContactIndexFragment | undefined;
|
||||
if (!index) throw new Error("Failed to load /kontakt");
|
||||
return { index };
|
||||
}
|
||||
|
||||
export function ContactIndexView({ index }: ContactIndexViewProps) {
|
||||
return (
|
||||
<main className="site-main" id="main">
|
||||
<PageHeader heading={index.title} lead={index.lead} />
|
||||
<GeneralContactBlock />
|
||||
{index.body && <PageContent blocks={index.body} />}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import {
|
||||
useQueryState,
|
||||
parseAsStringLiteral,
|
||||
@@ -8,7 +9,10 @@ import {
|
||||
} from "nuqs";
|
||||
import { EventItem } from "./EventItem";
|
||||
import { EventFilter, EventFilterExplained } from "./EventFilter";
|
||||
import { unmaskFragment } from "@/gql";
|
||||
import {
|
||||
EventCategoryFragmentDefinition,
|
||||
EventOrganizerFragmentDefinition,
|
||||
EventFragment,
|
||||
EventCategory,
|
||||
SingularEvent,
|
||||
@@ -47,11 +51,11 @@ export const EventContainer = ({
|
||||
}) => {
|
||||
const [mode, setMode] = useQueryState(
|
||||
"mode",
|
||||
parseAsStringLiteral(["list", "calendar"]).withDefault("list")
|
||||
parseAsStringLiteral(["list", "calendar"]).withDefault("list"),
|
||||
);
|
||||
const [categories, setCategories] = useQueryState(
|
||||
"category",
|
||||
parseAsArrayOf(parseAsString, ",")
|
||||
parseAsArrayOf(parseAsString, ","),
|
||||
);
|
||||
const [organizer, setOrganizer] = useQueryState("organizer", parseAsString);
|
||||
const [venue, setVenue] = useQueryState("venue", parseAsString);
|
||||
@@ -71,20 +75,43 @@ export const EventContainer = ({
|
||||
|
||||
/*
|
||||
Allow filtering on all organizers that have upcoming events
|
||||
Filtering on an organizer with no upcoming events will work, but be hidden from dropdown
|
||||
Filtering on an organizer with no upcoming events will work,
|
||||
and in that case it's included in the dropdown
|
||||
*/
|
||||
const allOrganizers = unmaskFragment(
|
||||
EventOrganizerFragmentDefinition,
|
||||
events.flatMap((x) => x.organizers),
|
||||
);
|
||||
const uniqueOrganizers: string[] = unique(
|
||||
events
|
||||
.map((x) => x.organizers)
|
||||
.flat()
|
||||
allOrganizers
|
||||
.filter((x) => x.__typename === "EventOrganizer")
|
||||
.map((x) => x.slug)
|
||||
.filter((x) => typeof x === "string")
|
||||
.filter((x) => typeof x === "string" && x !== ""),
|
||||
);
|
||||
const filterableOrganizers = uniqueOrganizers
|
||||
.map((slug) => eventOrganizers.find((haystack) => haystack.slug === slug))
|
||||
.filter((x) => x !== undefined) as EventOrganizer[];
|
||||
|
||||
// If an organizer is selected that's not in the upcoming list, add it
|
||||
if (
|
||||
organizer &&
|
||||
!filterableOrganizers.some((org) => org.slug === organizer)
|
||||
) {
|
||||
const selectedOrganizer = eventOrganizers.find(
|
||||
(org) => org.slug === organizer,
|
||||
);
|
||||
if (selectedOrganizer) {
|
||||
filterableOrganizers.push(selectedOrganizer);
|
||||
}
|
||||
}
|
||||
|
||||
/* Reset organizer if slug is invalid */
|
||||
useEffect(() => {
|
||||
if (!eventOrganizers.find((haystack) => haystack.slug === organizer)) {
|
||||
setOrganizer(null);
|
||||
}
|
||||
}, [eventOrganizers, organizer]);
|
||||
|
||||
/*
|
||||
Allow filtering on all venues that have upcoming events
|
||||
Filtering on a venue with no upcoming events will work,
|
||||
@@ -96,36 +123,48 @@ export const EventContainer = ({
|
||||
.flat()
|
||||
.filter((x) => x.venue?.__typename === "VenuePage")
|
||||
.map((x) => x.venue?.slug)
|
||||
.filter((x) => typeof x === "string")
|
||||
.filter((x) => typeof x === "string"),
|
||||
);
|
||||
const filterableVenues = venues
|
||||
.filter(
|
||||
(x) => venueSlugsWithUpcomingEvents.includes(x.slug) || x.slug === venue
|
||||
(x) => venueSlugsWithUpcomingEvents.includes(x.slug) || x.slug === venue,
|
||||
)
|
||||
.map((x) => venues.find((haystack) => haystack.slug === x.slug))
|
||||
.filter((x) => x !== undefined) as VenueFragment[];
|
||||
|
||||
const filteredEvents = events
|
||||
.filter(
|
||||
(x) =>
|
||||
!organizer ||
|
||||
x.organizers.map((organizer) => organizer.slug).includes(organizer)
|
||||
)
|
||||
.filter(
|
||||
(x) =>
|
||||
!categories ||
|
||||
x.categories
|
||||
.map((eventCategory) => eventCategory.slug)
|
||||
.filter((x) => categories.includes(x)).length !== 0
|
||||
)
|
||||
.filter(
|
||||
(x) =>
|
||||
!venue ||
|
||||
x.occurrences
|
||||
.map((occurrence) => occurrence.venue?.slug)
|
||||
.filter((x) => typeof x === "string")
|
||||
.includes(venue)
|
||||
);
|
||||
/* Reset venue if slug is invalid */
|
||||
useEffect(() => {
|
||||
if (!venues.find((haystack) => haystack.slug === venue)) {
|
||||
setVenue(null);
|
||||
}
|
||||
}, [venues, venue]);
|
||||
|
||||
const filteredEvents = events.filter((event) => {
|
||||
if (organizer) {
|
||||
const organizers = unmaskFragment(
|
||||
EventOrganizerFragmentDefinition,
|
||||
event.organizers,
|
||||
);
|
||||
if (!organizers.some((o) => o.slug === organizer)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (categories) {
|
||||
const eventCategories = unmaskFragment(
|
||||
EventCategoryFragmentDefinition,
|
||||
event.categories,
|
||||
);
|
||||
if (!eventCategories.some((c) => categories.includes(c.slug))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (venue) {
|
||||
if (!event.occurrences.some((occ) => occ.venue?.slug === venue)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
const [showFilter, setShowFilter] = useState(false);
|
||||
function toggleFilter() {
|
||||
@@ -273,12 +312,12 @@ function maybeYear(yearMonthString: string) {
|
||||
|
||||
const EventCalendar = ({ events }: { events: EventFragment[] }) => {
|
||||
const futureSingularEvents = getSingularEvents(events).filter(
|
||||
(x) => x.occurrence?.start && isTodayOrFuture(x.occurrence.start)
|
||||
(x) => x.occurrence?.start && isTodayOrFuture(x.occurrence.start),
|
||||
);
|
||||
const eventsByDate = organizeEventsInCalendar(futureSingularEvents);
|
||||
const yearMonths = Object.keys(eventsByDate);
|
||||
const [visibleYearMonths, setVisibleYearMonths] = useState(
|
||||
yearMonths.slice(0, 2)
|
||||
yearMonths.slice(0, 2),
|
||||
);
|
||||
|
||||
const toggleYearMonth = (yearMonth: string) => {
|
||||
@@ -298,9 +337,9 @@ const EventCalendar = ({ events }: { events: EventFragment[] }) => {
|
||||
yearMonthSum +
|
||||
Object.values(week).reduce(
|
||||
(weekSum, day) => weekSum + day.length,
|
||||
0
|
||||
0,
|
||||
),
|
||||
0
|
||||
0,
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,19 +1,30 @@
|
||||
import { EventFragment } from "@/lib/event";
|
||||
import { unmaskFragment } from "@/gql";
|
||||
import {
|
||||
EventCategoryFragmentDefinition,
|
||||
EventFragment,
|
||||
} from "@/lib/event";
|
||||
import { ImageFragmentDefinition } from "@/lib/common";
|
||||
import styles from "./eventHeader.module.scss";
|
||||
import { ImageFigure } from "@/components/general/Image";
|
||||
import { Breadcrumb } from "../general/Breadcrumb";
|
||||
import { Icon } from "../general/Icon";
|
||||
|
||||
export const EventHeader = ({ event }: { event: EventFragment }) => {
|
||||
const featuredImage: any = event.featuredImage;
|
||||
const featuredImage = unmaskFragment(
|
||||
ImageFragmentDefinition,
|
||||
event.featuredImage
|
||||
);
|
||||
const categories = unmaskFragment(
|
||||
EventCategoryFragmentDefinition,
|
||||
event.categories
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.eventHeader}>
|
||||
<div className={styles.heading}>
|
||||
{/*<Breadcrumb link="/arrangementer" text="Arrangement" />*/}
|
||||
{event.categories.length > 0 && (
|
||||
{categories.length > 0 && (
|
||||
<div className={styles.categories}>
|
||||
{event.categories.map((category) => (
|
||||
{categories.map((category) => (
|
||||
<div key={category.name} className="tag">
|
||||
{category.name}
|
||||
</div>
|
||||
@@ -33,7 +44,7 @@ export const EventHeader = ({ event }: { event: EventFragment }) => {
|
||||
{featuredImage && (
|
||||
<ImageFigure
|
||||
src={featuredImage.url}
|
||||
alt={featuredImage.alt}
|
||||
alt={featuredImage.alt ?? ""}
|
||||
width={featuredImage.width}
|
||||
height={featuredImage.height}
|
||||
attribution={featuredImage.attribution}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { Suspense } from "react";
|
||||
import { VenueFragment } from "@/gql/graphql";
|
||||
import { getClient } from "@/app/client";
|
||||
import { EventContainer } from "@/components/events/EventContainer";
|
||||
import { PageHeader } from "@/components/general/PageHeader";
|
||||
import {
|
||||
EventCategory,
|
||||
EventFragment,
|
||||
EventOrganizer,
|
||||
eventsOverviewQuery,
|
||||
} from "@/lib/event";
|
||||
|
||||
export type EventIndexViewProps = {
|
||||
events: EventFragment[];
|
||||
eventCategories: EventCategory[];
|
||||
eventOrganizers: EventOrganizer[];
|
||||
venues: VenueFragment[];
|
||||
};
|
||||
|
||||
export async function loadEventIndexProps(): Promise<EventIndexViewProps> {
|
||||
const { data, error } = await getClient().query(eventsOverviewQuery, {});
|
||||
if (error) throw new Error(error.message);
|
||||
if (
|
||||
!data?.index ||
|
||||
!data?.events?.futureEvents ||
|
||||
!data?.eventCategories ||
|
||||
!data?.eventOrganizers ||
|
||||
!data?.venues
|
||||
) {
|
||||
throw new Error("Failed to load /arrangementer");
|
||||
}
|
||||
return {
|
||||
events: data.events.futureEvents as EventFragment[],
|
||||
eventCategories: data.eventCategories as EventCategory[],
|
||||
eventOrganizers: data.eventOrganizers as EventOrganizer[],
|
||||
venues: data.venues as VenueFragment[],
|
||||
};
|
||||
}
|
||||
|
||||
export function EventIndexView({
|
||||
events,
|
||||
eventCategories,
|
||||
eventOrganizers,
|
||||
venues,
|
||||
}: EventIndexViewProps) {
|
||||
return (
|
||||
<main className="site-main" id="main">
|
||||
<PageHeader heading="Dette skjer på Chateau Neuf" align="left" />
|
||||
<Suspense>
|
||||
<EventContainer
|
||||
events={events}
|
||||
eventCategories={eventCategories}
|
||||
eventOrganizers={eventOrganizers}
|
||||
venues={venues}
|
||||
/>
|
||||
</Suspense>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { graphql } from "@/gql";
|
||||
import { EventFragment } from "@/gql/graphql";
|
||||
import { getClient } from "@/app/client";
|
||||
import { EventDetails } from "@/components/events/EventDetails";
|
||||
import { EventHeader } from "@/components/events/EventHeader";
|
||||
import { BgPig } from "@/components/general/BgPig";
|
||||
import { PageContent } from "@/components/general/PageContent";
|
||||
import { getEventPig } from "@/lib/event";
|
||||
|
||||
const eventBySlugQuery = graphql(`
|
||||
query eventBySlug($slug: String!) {
|
||||
event: page(contentType: "events.EventPage", slug: $slug) {
|
||||
... on EventPage {
|
||||
...Event
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export type EventPageViewProps = { event: EventFragment };
|
||||
|
||||
export async function loadEventPageProps(args: {
|
||||
slug?: string;
|
||||
eventOverride?: EventFragment;
|
||||
}): Promise<EventPageViewProps | null> {
|
||||
if (args.eventOverride) {
|
||||
return { event: args.eventOverride };
|
||||
}
|
||||
if (!args.slug) throw new Error("loadEventPageProps needs slug or eventOverride");
|
||||
const { data, error } = await getClient().query(eventBySlugQuery, {
|
||||
slug: args.slug,
|
||||
});
|
||||
if (error) throw new Error(error.message);
|
||||
const event = data?.event as EventFragment | undefined;
|
||||
if (!event) return null;
|
||||
return { event };
|
||||
}
|
||||
|
||||
export function EventPageView({ event }: EventPageViewProps) {
|
||||
const eventPig = getEventPig(event);
|
||||
|
||||
return (
|
||||
<>
|
||||
<main className="site-main" id="main">
|
||||
<EventHeader event={event} />
|
||||
<EventDetails event={event} />
|
||||
{event.lead && (
|
||||
<div
|
||||
className="lead event-lead"
|
||||
dangerouslySetInnerHTML={{ __html: event.lead }}
|
||||
/>
|
||||
)}
|
||||
<PageContent blocks={event.body} />
|
||||
</main>
|
||||
{eventPig && <BgPig type={eventPig} color="white" />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,15 +1,23 @@
|
||||
"use client";
|
||||
|
||||
import { EventFragment, EventOrganizer } from "@/lib/event";
|
||||
import { unmaskFragment } from "@/gql";
|
||||
import {
|
||||
EventFragment,
|
||||
EventOrganizerFragmentDefinition,
|
||||
} from "@/lib/event";
|
||||
import styles from "./organizerList.module.scss";
|
||||
import Link from "next/link";
|
||||
import { Fragment } from "react";
|
||||
|
||||
export const OrganizerList = ({ event }: { event: EventFragment }) => {
|
||||
const total = event.organizers.length;
|
||||
const organizers = unmaskFragment(
|
||||
EventOrganizerFragmentDefinition,
|
||||
event.organizers
|
||||
);
|
||||
const total = organizers.length;
|
||||
return (
|
||||
<div className={styles.organizerList}>
|
||||
{event.organizers.map((organizer, index) => {
|
||||
{organizers.map((organizer, index) => {
|
||||
const url = organizer.association?.url ?? organizer.externalUrl ?? null;
|
||||
const hasValidUrl =
|
||||
typeof url === "string" &&
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { graphql } from "@/gql";
|
||||
import { GenericFragment } from "@/gql/graphql";
|
||||
import { getClient } from "@/app/client";
|
||||
import { PageHeader } from "@/components/general/PageHeader";
|
||||
import { PageContent } from "@/components/general/PageContent";
|
||||
import { BgPig } from "@/components/general/BgPig";
|
||||
|
||||
const GenericFragmentDefinition = graphql(`
|
||||
fragment Generic on GenericPage {
|
||||
__typename
|
||||
id
|
||||
urlPath
|
||||
seoTitle
|
||||
searchDescription
|
||||
title
|
||||
lead
|
||||
pig
|
||||
body {
|
||||
...Blocks
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const genericPageByUrlPathQuery = graphql(`
|
||||
query genericPageByUrl($urlPath: String!) {
|
||||
page: page(contentType: "generic.GenericPage", urlPath: $urlPath) {
|
||||
... on GenericPage {
|
||||
...Generic
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export type GenericPageViewProps = { page: GenericFragment };
|
||||
|
||||
export async function loadGenericPageProps(args: {
|
||||
urlPath?: string;
|
||||
pageOverride?: GenericFragment;
|
||||
}): Promise<GenericPageViewProps | null> {
|
||||
if (args.pageOverride) {
|
||||
return { page: args.pageOverride };
|
||||
}
|
||||
if (!args.urlPath) throw new Error("loadGenericPageProps needs urlPath or pageOverride");
|
||||
const { data, error } = await getClient().query(genericPageByUrlPathQuery, {
|
||||
urlPath: args.urlPath,
|
||||
});
|
||||
if (error) throw new Error(error.message);
|
||||
const page = data?.page as GenericFragment | undefined;
|
||||
if (!page) return null;
|
||||
return { page };
|
||||
}
|
||||
|
||||
export function GenericPageView({ page }: GenericPageViewProps) {
|
||||
return (
|
||||
<>
|
||||
<main className="site-main" id="main">
|
||||
<PageHeader heading={page.title} lead={page.lead} />
|
||||
<PageContent blocks={page.body} />
|
||||
</main>
|
||||
{page.pig && <BgPig type={page.pig} color="white" />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import NextImage, { ImageProps as NextImageProps } from "next/image";
|
||||
import NextImage, { type ImageProps as NextImageProps } from "next/image";
|
||||
import styles from "./image.module.scss";
|
||||
|
||||
type ImageProps = NextImageProps & {
|
||||
@@ -7,14 +7,35 @@ type ImageProps = NextImageProps & {
|
||||
imageFormat?: string | null; // "original" | "bleed" | "fullWidth"
|
||||
};
|
||||
|
||||
function isSvgSrc(src: NextImageProps["src"]): src is string {
|
||||
return typeof src === "string" && /\.svg(\?|#|$)/i.test(src);
|
||||
}
|
||||
|
||||
function MaybeNextImage(props: NextImageProps) {
|
||||
if (isSvgSrc(props.src)) {
|
||||
const { src, alt, width, height, className, style } = props;
|
||||
return (
|
||||
<img // eslint-disable-line @next/next/no-img-element
|
||||
src={src}
|
||||
alt={alt}
|
||||
width={width}
|
||||
height={height}
|
||||
className={className}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <NextImage {...props} />;
|
||||
}
|
||||
|
||||
export function ImageFigure(props: ImageProps) {
|
||||
const { attribution, caption, imageFormat, ...nextImageProps } = props;
|
||||
const { attribution, caption, imageFormat, ...imageProps } = props;
|
||||
return (
|
||||
<figure
|
||||
className={`${styles.image} ${imageFormat ? styles[imageFormat] : ""}`}
|
||||
>
|
||||
<div className={styles.imageWrapper}>
|
||||
<NextImage {...nextImageProps} />
|
||||
<MaybeNextImage {...imageProps} />
|
||||
{attribution && <div className={styles.attribution}>{attribution}</div>}
|
||||
</div>
|
||||
{caption && <figcaption>{caption}</figcaption>}
|
||||
@@ -23,5 +44,5 @@ export function ImageFigure(props: ImageProps) {
|
||||
}
|
||||
|
||||
export function Image(props: NextImageProps) {
|
||||
return <NextImage {...props} />;
|
||||
return <MaybeNextImage {...props} />;
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -11,7 +11,7 @@ export const Newsletter = ({ url }: { url?: string }) => {
|
||||
<DecorativeIcon type="email" />
|
||||
</div>
|
||||
<h2>Nyhetsbrev</h2>
|
||||
<p>Meld deg på vårt nyhetsbrev og hold deg oppdatert på program og nyheter!</p>
|
||||
<p>Meld deg på vårt nyhetsbrev og hold deg oppdatert på arrangementer og siste nytt!</p>
|
||||
</div>
|
||||
<a href={link} target="_blank" className="button primary">
|
||||
Meld deg på
|
||||
|
||||
@@ -6,7 +6,7 @@ export const PageHeader = ({
|
||||
align
|
||||
}: {
|
||||
heading: string;
|
||||
lead?: string;
|
||||
lead?: string | null;
|
||||
align?: "center" | "left"
|
||||
}) => {
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
"use client";
|
||||
|
||||
import styles from "./previewBanner.module.scss";
|
||||
|
||||
export function PreviewBanner() {
|
||||
return (
|
||||
<div className={styles.previewBanner} role="status">
|
||||
<span className={styles.label}>Forhåndsvisning</span>
|
||||
<button
|
||||
type="button"
|
||||
className="tertiary"
|
||||
onClick={async () => {
|
||||
await fetch("/api/preview/disable", { method: "POST" });
|
||||
window.location.reload();
|
||||
}}
|
||||
>
|
||||
Avslutt forhåndsvisning
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -25,6 +25,10 @@
|
||||
h2 {
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
|
||||
p {
|
||||
max-width: 48rem;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
.previewBanner {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--spacing-s);
|
||||
padding: var(--spacing-xs) var(--spacing-s);
|
||||
background: color-mix(in srgb, var(--color-deepBrick) 60%, transparent);
|
||||
backdrop-filter: blur(4px);
|
||||
color: var(--color-betongGray);
|
||||
font-family: var(--font-main-demi);
|
||||
font-size: var(--font-size-caption);
|
||||
}
|
||||
|
||||
.label {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
import Link from "next/link";
|
||||
import { graphql } from "@/gql";
|
||||
import { HomeFragment } from "@/gql/graphql";
|
||||
import { getClient } from "@/app/client";
|
||||
import { EventFragment } from "@/lib/event";
|
||||
import { NewsFragment } from "@/lib/news";
|
||||
import { FeaturedEvents } from "@/components/events/FeaturedEvents";
|
||||
import { UpcomingEvents } from "@/components/events/UpcomingEvents";
|
||||
import { Icon } from "@/components/general/Icon";
|
||||
import { Newsletter } from "@/components/general/Newsletter";
|
||||
import { Pig } from "@/components/general/Pig";
|
||||
import { SectionFooter } from "@/components/general/SectionFooter";
|
||||
import { SectionHeader } from "@/components/general/SectionHeader";
|
||||
import { NewsList } from "@/components/news/NewsList";
|
||||
|
||||
const HomeFragmentDefinition = graphql(`
|
||||
fragment Home on HomePage {
|
||||
__typename
|
||||
featuredEvents {
|
||||
id
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const homeQuery = graphql(`
|
||||
query home {
|
||||
events: eventIndex {
|
||||
... on EventIndex {
|
||||
futureEvents {
|
||||
... on EventPage {
|
||||
...Event
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
home: page(contentType: "home.HomePage", urlPath: "/home/") {
|
||||
... on HomePage {
|
||||
...Home
|
||||
}
|
||||
}
|
||||
news: pages(contentType: "news.newsPage", order: "-first_published_at", limit: 4) {
|
||||
... on NewsPage {
|
||||
...News
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export type HomePageViewProps = {
|
||||
home: HomeFragment;
|
||||
events: EventFragment[];
|
||||
news: NewsFragment[];
|
||||
};
|
||||
|
||||
export async function loadHomePageProps(overrides?: {
|
||||
homeOverride?: HomeFragment;
|
||||
}): Promise<HomePageViewProps> {
|
||||
const { data, error } = await getClient().query(homeQuery, {});
|
||||
if (error) throw new Error(error.message);
|
||||
const home = overrides?.homeOverride ?? (data?.home as HomeFragment | undefined);
|
||||
if (!home) throw new Error("Failed to load /");
|
||||
const events = (data?.events?.futureEvents ?? []) as EventFragment[];
|
||||
const news = (data?.news ?? []) as NewsFragment[];
|
||||
return { home, events, news };
|
||||
}
|
||||
|
||||
export function HomePageView({ home, events, news }: HomePageViewProps) {
|
||||
const featuredEventIds = home.featuredEvents.map((x) => x.id);
|
||||
const featuredEvents = [
|
||||
...events.filter((x) => featuredEventIds.includes(x.id)),
|
||||
...events.filter((x) => !featuredEventIds.includes(x.id)),
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<main className="site-main index" id="main">
|
||||
<FeaturedEvents events={featuredEvents} />
|
||||
<UpcomingEvents events={events} />
|
||||
<div className="infoBlock">
|
||||
<SectionHeader heading="Besøk oss" link="/praktisk" linkText="Praktisk info" />
|
||||
<div>
|
||||
<h2 className="title">Skal du besøke Chateau Neuf?</h2>
|
||||
<p>
|
||||
Vi hjelper deg med å finne frem, og sørger for at du har en fin
|
||||
opplevelse.
|
||||
</p>
|
||||
<Link href="/praktisk#adkomst" className="button">
|
||||
<span>Adresse og adkomst</span>
|
||||
<Icon type="arrowRight" />
|
||||
</Link>
|
||||
<Link href="/praktisk#billetter" className="button">
|
||||
<span>Billetter</span>
|
||||
<Icon type="arrowRight" />
|
||||
</Link>
|
||||
<Link href="/praktisk#apningstider" className="button">
|
||||
<span>Åpningstider</span>
|
||||
<Icon type="arrowRight" />
|
||||
</Link>
|
||||
</div>
|
||||
<div className="pig">
|
||||
<Pig type="point" />
|
||||
</div>
|
||||
<SectionFooter link="/praktisk" linkText="Praktisk info" />
|
||||
</div>
|
||||
<NewsList heading="Siste nytt" featured news={news} />
|
||||
</main>
|
||||
<Newsletter />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -49,6 +49,13 @@ export const Header = () => {
|
||||
// let's add a santa hat during December
|
||||
const isChristmas = new Date().getMonth() === 11;
|
||||
|
||||
// let's add the student hat during grisefestuka
|
||||
const pigWearsHat =
|
||||
new Date().getFullYear() === 2025 &&
|
||||
new Date().getMonth() === 9 &&
|
||||
new Date().getDate() >= 6 &&
|
||||
new Date().getDate() <= 12;
|
||||
|
||||
return (
|
||||
<>
|
||||
<header
|
||||
@@ -57,7 +64,7 @@ export const Header = () => {
|
||||
data-small={!isInView}
|
||||
>
|
||||
<Link href="/" aria-label="Hjem">
|
||||
<Logo christmas={isChristmas} />
|
||||
<Logo christmas={isChristmas} studentHat={pigWearsHat} />
|
||||
</Link>
|
||||
<nav className={styles.siteMenu}>
|
||||
<ul className={styles.mainMenu}>
|
||||
@@ -80,8 +87,13 @@ export const Header = () => {
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/medlem" data-active={pathname === "/medlem"}>
|
||||
Bli medlem
|
||||
<Link href="/frivillig" data-active={pathname === "/frivillig"}>
|
||||
Bli frivillig
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/foreninger" data-active={pathname === "/foreninger"}>
|
||||
Foreninger
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -113,8 +125,16 @@ export const Header = () => {
|
||||
</Link>
|
||||
</li>
|
||||
<li className={styles.menuItemLarge}>
|
||||
<Link href="/medlem" data-active={pathname === "/medlem"}>
|
||||
Bli medlem
|
||||
<Link href="/frivillig" data-active={pathname === "/frivillig"}>
|
||||
Bli frivillig
|
||||
</Link>
|
||||
</li>
|
||||
<li className={styles.menuItemLarge}>
|
||||
<Link
|
||||
href="/foreninger"
|
||||
data-active={pathname === "/foreninger"}
|
||||
>
|
||||
Foreninger
|
||||
</Link>
|
||||
</li>
|
||||
<li className={styles.divider}>
|
||||
@@ -133,23 +153,12 @@ export const Header = () => {
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/frivillig" data-active={pathname === "/frivillig"}>
|
||||
Bli frivillig
|
||||
<Link href="/medlem" data-active={pathname === "/medlem"}>
|
||||
Bli medlem
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href="/foreninger"
|
||||
data-active={pathname === "/foreninger"}
|
||||
>
|
||||
Foreninger
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href="/sponsorer"
|
||||
data-active={pathname === "/sponsorer"}
|
||||
>
|
||||
<Link href="/sponsorer" data-active={pathname === "/sponsorer"}>
|
||||
Sponsorer
|
||||
</Link>
|
||||
</li>
|
||||
@@ -181,7 +190,7 @@ export const Header = () => {
|
||||
{!isInView ? (
|
||||
<LogoIcon face="left" christmas={isChristmas} />
|
||||
) : (
|
||||
<Logo christmas={isChristmas} />
|
||||
<Logo christmas={isChristmas} studentHat={pigWearsHat} />
|
||||
)}
|
||||
</Link>
|
||||
<nav className={styles.siteMenu}>
|
||||
@@ -205,8 +214,16 @@ export const Header = () => {
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/medlem" data-active={pathname === "/medlem"}>
|
||||
Bli medlem
|
||||
<Link href="/frivillig" data-active={pathname === "/frivillig"}>
|
||||
Bli frivillig
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href="/foreninger"
|
||||
data-active={pathname === "/foreninger"}
|
||||
>
|
||||
Foreninger
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { getClient } from "@/app/client";
|
||||
import { PageHeader } from "@/components/general/PageHeader";
|
||||
import { NewsList } from "@/components/news/NewsList";
|
||||
import { NewsFragment, NewsIndexFragment, newsQuery } from "@/lib/news";
|
||||
|
||||
export type NewsIndexViewProps = {
|
||||
index: NewsIndexFragment;
|
||||
news: NewsFragment[];
|
||||
};
|
||||
|
||||
export async function loadNewsIndexProps(overrides?: {
|
||||
indexOverride?: NewsIndexFragment;
|
||||
}): Promise<NewsIndexViewProps> {
|
||||
const { data, error } = await getClient().query(newsQuery, {});
|
||||
if (error) throw new Error(error.message);
|
||||
const index = overrides?.indexOverride ?? (data?.index as NewsIndexFragment | undefined);
|
||||
if (!index) throw new Error("Failed to load /aktuelt");
|
||||
const news = (data?.news ?? []) as NewsFragment[];
|
||||
return { index, news };
|
||||
}
|
||||
|
||||
export function NewsIndexView({ index, news }: NewsIndexViewProps) {
|
||||
return (
|
||||
<main className="site-main" id="main">
|
||||
<PageHeader heading={index.title} lead={index.lead} align="left" />
|
||||
<NewsList news={news} />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -21,9 +21,11 @@ export const NewsItem = ({ news }: { news: NewsFragment }) => {
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.text}>
|
||||
<p className={styles.date}>
|
||||
{formatDate(news.firstPublishedAt, 'd. MMMM yyyy')}
|
||||
</p>
|
||||
{news.firstPublishedAt && (
|
||||
<p className={styles.date}>
|
||||
{formatDate(news.firstPublishedAt, "d. MMMM yyyy")}
|
||||
</p>
|
||||
)}
|
||||
<h2 className={styles.title}>{news.title}</h2>
|
||||
<p className={styles.lead}>{news.excerpt}</p>
|
||||
</div>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user