diff --git a/dnscms/associations/admin.py b/dnscms/associations/admin.py
index 82add05..01bcc0c 100644
--- a/dnscms/associations/admin.py
+++ b/dnscms/associations/admin.py
@@ -1,7 +1,7 @@
from django.utils.translation import gettext_lazy as _
from wagtail.admin.ui.tables import Column, DateColumn
from wagtail.admin.ui.tables.pages import PageStatusColumn, PageTitleColumn
-from wagtail.admin.viewsets.pages import PageListingViewSet
+from wagtail.admin.viewsets.pages import PageListingViewSet, PageViewSet
from associations.models import AssociationPage
from dnscms.admin import ListingRedirectChooseParentView
@@ -16,15 +16,11 @@ class AssociationChooseParentView(ListingRedirectChooseParentView):
listing_url_name = "associations:index"
-class AssociationPageListingViewSet(PageListingViewSet):
- model = AssociationPage
- choose_parent_view_class = AssociationChooseParentView
- icon = "group"
- menu_label = _("Associations")
- menu_order = 2
- add_to_admin_menu = True
- ordering = "title"
+class AssociationListingMixin:
+ """Shared model + columns for the standalone listing and the page explorer."""
+ model = AssociationPage
+ icon = "group"
columns = [
PageTitleColumn("title", label=_("Title"), sort_key="title", classname="title"),
AssociationTypeColumn(
@@ -43,4 +39,19 @@ class AssociationPageListingViewSet(PageListingViewSet):
]
-association_page_listing_viewset = AssociationPageListingViewSet("associations")
+class AssociationSidebarViewSet(AssociationListingMixin, PageListingViewSet):
+ """Standalone 'Associations' sidebar entry, reached independently of the page tree."""
+
+ choose_parent_view_class = AssociationChooseParentView
+ menu_label = _("Associations")
+ menu_order = 2
+ add_to_admin_menu = True
+ ordering = "title"
+
+
+class AssociationExplorerViewSet(AssociationListingMixin, PageViewSet):
+ """Applies the same columns when navigating into AssociationIndex via the page explorer."""
+
+
+association_sidebar_viewset = AssociationSidebarViewSet("associations")
+association_explorer_viewset = AssociationExplorerViewSet()
diff --git a/dnscms/associations/wagtail_hooks.py b/dnscms/associations/wagtail_hooks.py
index ce9cfab..c703adb 100644
--- a/dnscms/associations/wagtail_hooks.py
+++ b/dnscms/associations/wagtail_hooks.py
@@ -1,6 +1,6 @@
from wagtail import hooks
-from .admin import association_page_listing_viewset
+from .admin import association_sidebar_viewset, association_explorer_viewset
from .views import association_chooser_viewset
@@ -10,5 +10,10 @@ def register_viewset():
@hooks.register("register_admin_viewset")
-def register_association_page_listing_viewset():
- return association_page_listing_viewset
+def register_association_sidebar_viewset():
+ return association_sidebar_viewset
+
+
+@hooks.register("register_admin_viewset")
+def register_association_explorer_viewset():
+ return association_explorer_viewset
diff --git a/dnscms/events/admin.py b/dnscms/events/admin.py
index 2bb1e15..d36d1e3 100644
--- a/dnscms/events/admin.py
+++ b/dnscms/events/admin.py
@@ -3,8 +3,8 @@ from django.utils.translation import gettext, gettext_lazy as _
from django.utils.translation import ngettext
from wagtail.admin.ui.tables import Column, DateColumn
from wagtail.admin.ui.tables.pages import PageStatusColumn, PageTitleColumn
-from wagtail.admin.views.pages.listing import IndexView
-from wagtail.admin.viewsets.pages import PageListingViewSet
+from wagtail.admin.views.pages.listing import ExplorableIndexView, IndexView
+from wagtail.admin.viewsets.pages import PageListingViewSet, PageViewSet
from dnscms.admin import ListingRedirectChooseParentView
from events.models import EventPage
@@ -32,7 +32,9 @@ class OrganizersColumn(Column):
return f"{names[0]} (+{len(names) - 1})"
-class EventPageIndexView(IndexView):
+class EventPagePrefetchMixin:
+ """Prefetch the relations the event columns read, so the listing avoids N+1."""
+
def annotate_queryset(self, pages):
pages = super().annotate_queryset(pages)
return pages.prefetch_related(
@@ -41,20 +43,23 @@ class EventPageIndexView(IndexView):
)
+class EventPageIndexView(EventPagePrefetchMixin, IndexView):
+ pass
+
+
+class EventPageExplorableIndexView(EventPagePrefetchMixin, ExplorableIndexView):
+ pass
+
+
class EventChooseParentView(ListingRedirectChooseParentView):
listing_url_name = "events:index"
-class EventPageListingViewSet(PageListingViewSet):
- model = EventPage
- index_view_class = EventPageIndexView
- choose_parent_view_class = EventChooseParentView
- icon = "date"
- menu_label = _("Events")
- menu_order = 1
- add_to_admin_menu = True
- ordering = "-latest_revision_created_at"
+class EventListingMixin:
+ """Shared model + columns for the standalone listing and the page explorer."""
+ model = EventPage
+ icon = "date"
columns = [
PageTitleColumn("title", label=_("Title"), sort_key="title", classname="title"),
EventDateColumn("event_date", label=_("Date"), width="13%"),
@@ -69,4 +74,22 @@ class EventPageListingViewSet(PageListingViewSet):
]
-event_page_listing_viewset = EventPageListingViewSet("events")
+class EventSidebarViewSet(EventListingMixin, PageListingViewSet):
+ """Standalone 'Events' sidebar entry, reached independently of the page tree."""
+
+ index_view_class = EventPageIndexView
+ choose_parent_view_class = EventChooseParentView
+ menu_label = _("Events")
+ menu_order = 1
+ add_to_admin_menu = True
+ ordering = "-latest_revision_created_at"
+
+
+class EventExplorerViewSet(EventListingMixin, PageViewSet):
+ """Applies the same columns when navigating into EventIndex via the page explorer."""
+
+ index_view_class = EventPageExplorableIndexView
+
+
+event_sidebar_viewset = EventSidebarViewSet("events")
+event_explorer_viewset = EventExplorerViewSet()
diff --git a/dnscms/events/wagtail_hooks.py b/dnscms/events/wagtail_hooks.py
index 1f60608..a18b8fe 100644
--- a/dnscms/events/wagtail_hooks.py
+++ b/dnscms/events/wagtail_hooks.py
@@ -1,6 +1,6 @@
from wagtail import hooks
-from .admin import event_page_listing_viewset
+from .admin import event_sidebar_viewset, event_explorer_viewset
from .views import event_organizer_chooser_viewset
@@ -10,5 +10,10 @@ def register_viewset():
@hooks.register("register_admin_viewset")
-def register_event_page_listing_viewset():
- return event_page_listing_viewset
+def register_event_sidebar_viewset():
+ return event_sidebar_viewset
+
+
+@hooks.register("register_admin_viewset")
+def register_event_explorer_viewset():
+ return event_explorer_viewset
diff --git a/dnscms/locale/nb/LC_MESSAGES/django.mo b/dnscms/locale/nb/LC_MESSAGES/django.mo
index 335f88a..b4672f7 100644
Binary files a/dnscms/locale/nb/LC_MESSAGES/django.mo and b/dnscms/locale/nb/LC_MESSAGES/django.mo differ
diff --git a/dnscms/locale/nb/LC_MESSAGES/django.po b/dnscms/locale/nb/LC_MESSAGES/django.po
index 173340d..b40bad3 100644
--- a/dnscms/locale/nb/LC_MESSAGES/django.po
+++ b/dnscms/locale/nb/LC_MESSAGES/django.po
@@ -7,187 +7,143 @@ msgid ""
msgstr ""
"Project-Id-Version: dnscms\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2026-05-19 21:55+0200\n"
+"POT-Creation-Date: 2026-05-26 01:39+0200\n"
"Language: nb\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-#: associations/admin.py:23
-msgid "Associations"
-msgstr "Foreninger"
-
-#: associations/admin.py:29 events/admin.py:59 news/admin.py:24
msgid "Title"
msgstr "Tittel"
-#: associations/admin.py:32 associations/models.py:79
msgid "Type"
msgstr "Type"
-#: associations/admin.py:38 events/admin.py:64 news/admin.py:27
msgid "Updated"
msgstr "Oppdatert"
-#: associations/admin.py:42 events/admin.py:68 news/admin.py:31
msgid "Status"
msgstr "Status"
-#: associations/models.py:30 associations/models.py:76 events/models.py:327
-#: news/models.py:23 news/models.py:69
+msgid "Associations"
+msgstr "Foreninger"
+
msgid "Lead"
msgstr "Ingress"
-#: associations/models.py:31 associations/models.py:77
msgid "Content"
msgstr "Innhold"
-#: associations/models.py:42
msgid "association index"
msgstr "foreningsoversikt"
-#: associations/models.py:43
msgid "association indexes"
msgstr "foreningsoversikter"
-#: associations/models.py:52
msgid "Association"
msgstr "Forening"
-#: associations/models.py:53
msgid "Committee"
msgstr "Utvalg"
-#: associations/models.py:73 news/models.py:60
msgid "Excerpt"
msgstr "Utdrag"
-#: associations/models.py:74
msgid "A very short summary of the content below. Used in listing views."
msgstr ""
"En veldig kort oppsummering av innholdet nedenfor. Brukes i listevisninger."
-#: associations/models.py:80 events/models.py:189
msgid "Website"
msgstr "Nettsted"
-#: associations/models.py:98
msgid "association"
msgstr "forening"
-#: associations/models.py:99
msgid "associations"
msgstr "foreninger"
-#: associations/views.py:8
msgid "Choose an association"
msgstr "Velg en forening"
-#: associations/views.py:9
msgid "Choose another association"
msgstr "Velg en annen forening"
-#: associations/views.py:10
msgid "Edit this association"
msgstr "Rediger denne foreningen"
-#: events/admin.py:20
msgid "%Y-%m-%d at %H:%M"
msgstr "%Y-%m-%d kl %H:%M"
-#: events/admin.py:22
#, python-format
msgid "%(count)d occurrence"
msgid_plural "%(count)d occurrences"
msgstr[0] "%(count)d forekomst"
msgstr[1] "%(count)d forekomster"
-#: events/admin.py:53
-msgid "Events"
-msgstr "Arrangementer"
-
-#: events/admin.py:60
msgid "Date"
msgstr "Dato"
-#: events/admin.py:61 events/models.py:331
msgid "Organizers"
msgstr "Arrangører"
-#: events/models.py:73 events/models.py:156
+msgid "Events"
+msgstr "Arrangementer"
+
msgid "slug"
msgstr "permalenke"
-#: events/models.py:75
msgid "The name of the category as it will appear in URLs."
msgstr "Navnet på kategorien slik det vil vises i URL-er."
-#: events/models.py:79
msgid "Should this category be available as a filter in the event programme?"
msgstr "Skal denne kategorien være mulig å filtrere på i programmet?"
-#: events/models.py:83 events/models.py:266
msgid "None"
msgstr "Ingen"
-#: events/models.py:91
msgid "Default pig for events of this kind."
msgstr "Standardgris for arrangementer av denne typen."
-#: events/models.py:98 events/models.py:341
msgid "Pig"
msgstr "Gris"
-#: events/models.py:109
msgid "event category"
msgstr "arrangementskategori"
-#: events/models.py:110
msgid "event categories"
msgstr "arrangementskategorier"
-#: events/models.py:138
msgid "organizer"
msgstr "arrangør"
-#: events/models.py:139
msgid "organizers"
msgstr "arrangører"
-#: events/models.py:158
msgid "The name of the organizer as it will appear in URLs."
msgstr "Navnet på arrangøren slik det vil vises i URL-er."
-#: events/models.py:167
msgid "If a DNS association or committee is behind it, choose it here."
msgstr "Om en samfundsforening eller -utvalg står bak, velg det her."
-#: events/models.py:173
msgid "Link to the external organizer's website"
msgstr "Lenke til nettstedet til ekstern arrangør"
-#: events/models.py:182
msgid "Internal organizer"
msgstr "Intern arrangør"
-#: events/models.py:185
msgid "External organizer"
msgstr "Ekstern arrangør"
-#: events/models.py:190
msgid "Leave this empty if the organizer exists in the list above."
msgstr "La denne stå tom om arrangøren finnes i lista over."
-#: events/models.py:204
msgid "event organizer"
msgstr "arrangør"
-#: events/models.py:205
msgid "event organizers"
msgstr "arrangører"
-#: events/models.py:239
msgid ""
"Choose an image for use in the programme and other surfaces. Should be a "
"photo or an illustration without too much text – don't reuse a Facebook "
@@ -197,7 +153,6 @@ msgstr ""
"bilde eller en illustrasjon uten for mye tekst – ikke gjenbruk et Facebook-"
"cover ukritisk!"
-#: events/models.py:249
msgid ""
"A short text that appears right below the title. Feel free to leave it empty "
"if you fit most of it in the main title."
@@ -205,11 +160,9 @@ msgstr ""
"En kort tekst som kommer rett under tittelen. La denne gjerne stå tom om du "
"fikk plass til det meste i hovedtittelen."
-#: events/models.py:267
msgid "Automatic"
msgstr "Automatisk"
-#: events/models.py:276
msgid ""
"The pig that hangs out on the event page. Automatic causes one to be chosen "
"based on the event's category."
@@ -217,36 +170,28 @@ msgstr ""
"Grisen som henger på arrangementssiden. Automatisk fører til at en velges "
"basert på arrangementets kategori."
-#: events/models.py:284
msgid "Direct link to ticket purchase, e.g. TicketCo, Billetto or Ticketmaster"
msgstr ""
"Lenke direkte til billettkjøp, f.eks. TicketCo, Billetto eller Ticketmaster"
-#: events/models.py:289
msgid "Direct link to the event on Facebook"
msgstr "Lenke direkte til arrangementet på Facebook"
-#: events/models.py:298
msgid "Free"
msgstr "Gratis"
-#: events/models.py:298
msgid "Is this event free for everyone?"
msgstr "Er dette arrangementet gratis for alle?"
-#: events/models.py:303
msgid "Regular price"
msgstr "Ordinær pris"
-#: events/models.py:304
msgid "Price for students"
msgstr "Pris for studenter"
-#: events/models.py:305
msgid "Price for DNS members"
msgstr "Pris for medlemmer av DNS"
-#: events/models.py:312
msgid ""
"Write 0 for free. An empty field hides the price category. "
"If possible, write digits only."
@@ -254,57 +199,44 @@ msgstr ""
"Skriv 0 om gratis. Tomt felt skjuler priskategorien. Om "
"mulig, skriv kun tall."
-#: events/models.py:321
msgid "Ticket purchase link"
msgstr "Billettkjøpslenke"
-#: events/models.py:325
msgid "Subtitle"
msgstr "Undertittel"
-#: events/models.py:334
msgid "Who is behind the event?"
msgstr "Hvem står bak arrangementet?"
-#: events/models.py:337
msgid "Organizer"
msgstr "Arrangør"
-#: events/models.py:344
msgid "Facebook link"
msgstr "Facebook-lenke"
-#: events/models.py:345
msgid "Direct link to the event on Facebook."
msgstr "Lenke direkte til arrangementet på Facebook."
-#: events/models.py:347
msgid "Pricing and tickets"
msgstr "Priser og billettkjøp"
-#: events/models.py:349
msgid "Date, time and venue"
msgstr "Dato, tid og lokale"
-#: events/models.py:353
msgid "If the event spans several days, add each day as a separate occurrence."
msgstr ""
"Om arrangementet går over flere dager, legg inn hver dag som en egen "
"forekomst."
-#: events/models.py:356
msgid "Occurrence"
msgstr "Forekomst"
-#: events/models.py:399
msgid "event"
msgstr "arrangement"
-#: events/models.py:400
msgid "events"
msgstr "arrangementer"
-#: events/models.py:560
msgid ""
"Use this if none of the venues that can be selected on the left "
"fit. E.g. Frederikkeplassen or Sirkusteltet."
@@ -312,75 +244,60 @@ msgstr ""
"Bruk denne om ingen av lokalene som kan velges til venstre passer. "
"F.eks. Frederikkeplassen eller Sirkusteltet."
-#: events/models.py:569
msgid "Start"
msgstr "Start"
-#: events/models.py:570
msgid "End"
msgstr "Slutt"
-#: events/models.py:575
msgid "Venue"
msgstr "Lokale"
-#: events/models.py:576
msgid "Venue as free text"
msgstr "Lokale som fritekst"
-#: events/models.py:593
msgid "You can't both pick a venue and write something in this field."
msgstr "Du kan ikke både velge et lokale og skrive noe i dette feltet."
-#: events/models.py:598
msgid "Venue is required."
msgstr "Lokale er påkrevd."
-#: events/models.py:604
msgid "occurrence"
msgstr "forekomst"
-#: events/models.py:605
msgid "occurrences"
msgstr "forekomster"
-#: events/views.py:9
msgid "Choose organizers"
msgstr "Velg arrangører"
-#: events/views.py:10
msgid "Choose an organizer"
msgstr "Velg en arrangør"
-#: events/views.py:11
msgid "Choose another organizer"
msgstr "Velg en annen arrangør"
-#: events/views.py:12
msgid "Edit this organizer"
msgstr "Rediger denne arrangøren"
-#: images/models.py:40
msgid "image"
msgstr "bilde"
-#: images/models.py:41
msgid "images"
msgstr "bilder"
-#: news/admin.py:18
+msgid "First published"
+msgstr "Først publisert"
+
msgid "News"
msgstr "Nyheter"
-#: news/models.py:33
msgid "news index"
msgstr "nyhetsoversikt"
-#: news/models.py:34
msgid "news indexes"
msgstr "nyhetsoversikter"
-#: news/models.py:52
msgid ""
"Choose an image for use on the front page and other surfaces. Should be a "
"photo or an illustration without too much text."
@@ -388,15 +305,13 @@ msgstr ""
"Velg et bilde til bruk på forsiden og andre visningsflater. Bør være et "
"bilde eller en illustrasjon uten for mye tekst."
-#: news/models.py:62
msgid ""
"A very short summary of the article's content. Used on the front page and in "
"the article listing."
msgstr ""
-"En veldig kort oppsummering av innholdet i artikkelen. Brukes på forsiden "
-"og i artikkeloversikten."
+"En veldig kort oppsummering av innholdet i artikkelen. Brukes på forsiden og "
+"i artikkeloversikten."
-#: news/models.py:71
msgid ""
"A brief, introductory paragraph that summarizes the main content of the "
"article."
@@ -404,10 +319,41 @@ msgstr ""
"Et kortfattet, innledende avsnitt som oppsummerer hovedinnholdet i "
"artikkelen."
-#: news/models.py:92
msgid "news article"
msgstr "nyhetsartikkel"
-#: news/models.py:93
msgid "news articles"
msgstr "nyhetsartikler"
+
+msgid "Rentals page"
+msgstr "Utleieside"
+
+msgid "Venue overview"
+msgstr "Lokaleoversikt"
+
+msgid "Venues"
+msgstr "Lokaler"
+
+msgid "venue index"
+msgstr "lokaleoversikt"
+
+msgid "venue indexes"
+msgstr "lokaleoversikter"
+
+msgid "rentals page"
+msgstr "utleieside"
+
+msgid "rentals pages"
+msgstr "utleiesider"
+
+msgid "venue"
+msgstr "lokale"
+
+msgid "venues"
+msgstr "lokaler"
+
+#~ msgid "Bookable"
+#~ msgstr "Til utleie"
+
+#~ msgid "Listed"
+#~ msgstr "I oversikt"
diff --git a/dnscms/news/admin.py b/dnscms/news/admin.py
index d7ef908..b33ffc8 100644
--- a/dnscms/news/admin.py
+++ b/dnscms/news/admin.py
@@ -1,7 +1,7 @@
from django.utils.translation import gettext_lazy as _
from wagtail.admin.ui.tables import DateColumn
from wagtail.admin.ui.tables.pages import PageStatusColumn, PageTitleColumn
-from wagtail.admin.viewsets.pages import PageListingViewSet
+from wagtail.admin.viewsets.pages import PageListingViewSet, PageViewSet
from dnscms.admin import ListingRedirectChooseParentView
from news.models import NewsPage
@@ -11,17 +11,19 @@ class NewsChooseParentView(ListingRedirectChooseParentView):
listing_url_name = "news:index"
-class NewsPageListingViewSet(PageListingViewSet):
- model = NewsPage
- choose_parent_view_class = NewsChooseParentView
- icon = "info-circle"
- menu_label = _("News")
- menu_order = 3
- add_to_admin_menu = True
- ordering = "-latest_revision_created_at"
+class NewsListingMixin:
+ """Shared model + columns for the standalone listing and the page explorer."""
+ model = NewsPage
+ icon = "info-circle"
columns = [
PageTitleColumn("title", label=_("Title"), sort_key="title", classname="title"),
+ DateColumn(
+ "first_published_at",
+ label=_("First published"),
+ sort_key="first_published_at",
+ width="10%",
+ ),
DateColumn(
"latest_revision_created_at",
label=_("Updated"),
@@ -32,4 +34,19 @@ class NewsPageListingViewSet(PageListingViewSet):
]
-news_page_listing_viewset = NewsPageListingViewSet("news")
+class NewsSidebarViewSet(NewsListingMixin, PageListingViewSet):
+ """Standalone 'News' sidebar entry, reached independently of the page tree."""
+
+ choose_parent_view_class = NewsChooseParentView
+ menu_label = _("News")
+ menu_order = 3
+ add_to_admin_menu = True
+ ordering = "-latest_revision_created_at"
+
+
+class NewsExplorerViewSet(NewsListingMixin, PageViewSet):
+ """Applies the same columns when navigating into NewsIndex via the page explorer."""
+
+
+news_sidebar_viewset = NewsSidebarViewSet("news")
+news_explorer_viewset = NewsExplorerViewSet()
diff --git a/dnscms/news/wagtail_hooks.py b/dnscms/news/wagtail_hooks.py
index c9c7b76..3af6bbe 100644
--- a/dnscms/news/wagtail_hooks.py
+++ b/dnscms/news/wagtail_hooks.py
@@ -1,8 +1,13 @@
from wagtail import hooks
-from .admin import news_page_listing_viewset
+from .admin import news_sidebar_viewset, news_explorer_viewset
@hooks.register("register_admin_viewset")
-def register_news_page_listing_viewset():
- return news_page_listing_viewset
+def register_news_sidebar_viewset():
+ return news_sidebar_viewset
+
+
+@hooks.register("register_admin_viewset")
+def register_news_explorer_viewset():
+ return news_explorer_viewset
diff --git a/dnscms/tests/test_news.py b/dnscms/tests/test_news.py
index 4dda665..6c3d2d1 100644
--- a/dnscms/tests/test_news.py
+++ b/dnscms/tests/test_news.py
@@ -1,4 +1,4 @@
-from news.admin import NewsPageListingViewSet
+from news.admin import NewsSidebarViewSet
from news.models import NewsPage
from tests.conftest import NewsPageFactory
@@ -11,9 +11,9 @@ def test_news_page_persists_via_factory(news_index):
assert reloaded.excerpt == "Short summary"
-def test_news_listing_viewset_wired_to_newspage():
- assert NewsPageListingViewSet.model is NewsPage
- assert NewsPageListingViewSet.add_to_admin_menu is True
+def test_news_sidebar_viewset_wired_to_newspage():
+ assert NewsSidebarViewSet.model is NewsPage
+ assert NewsSidebarViewSet.add_to_admin_menu is True
def test_graphql_news_index_query(news_index, graphql_post):
diff --git a/dnscms/venues/admin.py b/dnscms/venues/admin.py
new file mode 100644
index 0000000..604fac9
--- /dev/null
+++ b/dnscms/venues/admin.py
@@ -0,0 +1,58 @@
+from django.utils.translation import gettext_lazy as _
+from wagtail.admin.ui.tables import BooleanColumn, DateColumn
+from wagtail.admin.ui.tables.pages import PageStatusColumn, PageTitleColumn
+from wagtail.admin.viewsets.pages import PageListingViewSet, PageViewSet
+
+from dnscms.admin import ListingRedirectChooseParentView
+from venues.models import VenuePage
+
+
+class VenueChooseParentView(ListingRedirectChooseParentView):
+ listing_url_name = "venues:index"
+
+
+class VenueListingMixin:
+ """Shared model + columns for the standalone listing and the page explorer."""
+
+ model = VenuePage
+ icon = "home"
+ columns = [
+ PageTitleColumn("title", label=_("Title"), sort_key="title", classname="title"),
+ BooleanColumn(
+ "show_as_bookable",
+ label=_("Rentals page"),
+ sort_key="show_as_bookable",
+ width="10%",
+ ),
+ BooleanColumn(
+ "show_in_overview",
+ label=_("Venue overview"),
+ sort_key="show_in_overview",
+ width="10%",
+ ),
+ DateColumn(
+ "latest_revision_created_at",
+ label=_("Updated"),
+ sort_key="latest_revision_created_at",
+ width="10%",
+ ),
+ PageStatusColumn("status", label=_("Status"), sort_key="live", width="10%"),
+ ]
+
+
+class VenueSidebarViewSet(VenueListingMixin, PageListingViewSet):
+ """Standalone 'Venues' sidebar entry, reached independently of the page tree."""
+
+ choose_parent_view_class = VenueChooseParentView
+ menu_label = _("Venues")
+ menu_order = 4
+ add_to_admin_menu = True
+ ordering = "title"
+
+
+class VenueExplorerViewSet(VenueListingMixin, PageViewSet):
+ """Applies the same columns when navigating into VenueIndex via the page explorer."""
+
+
+venue_sidebar_viewset = VenueSidebarViewSet("venues")
+venue_explorer_viewset = VenueExplorerViewSet()
diff --git a/dnscms/venues/migrations/0025_verbose_names.py b/dnscms/venues/migrations/0025_verbose_names.py
new file mode 100644
index 0000000..e88a932
--- /dev/null
+++ b/dnscms/venues/migrations/0025_verbose_names.py
@@ -0,0 +1,25 @@
+# Generated by Django 6.0.5 on 2026-05-25 23:40
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('venues', '0024_venuepage_show_in_overview_and_more'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='venueindex',
+ options={'verbose_name': 'venue index', 'verbose_name_plural': 'venue indexes'},
+ ),
+ migrations.AlterModelOptions(
+ name='venuepage',
+ options={'verbose_name': 'venue', 'verbose_name_plural': 'venues'},
+ ),
+ migrations.AlterModelOptions(
+ name='venuerentalindex',
+ options={'verbose_name': 'rentals page', 'verbose_name_plural': 'rentals pages'},
+ ),
+ ]
diff --git a/dnscms/venues/models.py b/dnscms/venues/models.py
index d09f1de..939819d 100644
--- a/dnscms/venues/models.py
+++ b/dnscms/venues/models.py
@@ -1,4 +1,5 @@
from django.db import models
+from django.utils.translation import gettext_lazy as _
from grapple.helpers import register_singular_query_field
from grapple.models import (
GraphQLBoolean,
@@ -38,6 +39,10 @@ class VenueIndex(HeadlessMixin, Page):
graphql_fields = [GraphQLRichText("lead"), GraphQLStreamfield("body")]
+ class Meta:
+ verbose_name = _("venue index")
+ verbose_name_plural = _("venue indexes")
+
@register_singular_query_field("venueRentalIndex")
class VenueRentalIndex(HeadlessMixin, Page):
@@ -55,6 +60,10 @@ class VenueRentalIndex(HeadlessMixin, Page):
graphql_fields = [GraphQLRichText("lead"), GraphQLStreamfield("body")]
+ class Meta:
+ verbose_name = _("rentals page")
+ verbose_name_plural = _("rentals pages")
+
class VenuePage(HeadlessMixin, WPImportedPageMixin, Page):
# no children
@@ -184,3 +193,7 @@ class VenuePage(HeadlessMixin, WPImportedPageMixin, Page):
search_fields = Page.search_fields + [
index.SearchField("body"),
]
+
+ class Meta:
+ verbose_name = _("venue")
+ verbose_name_plural = _("venues")
diff --git a/dnscms/venues/wagtail_hooks.py b/dnscms/venues/wagtail_hooks.py
new file mode 100644
index 0000000..0851775
--- /dev/null
+++ b/dnscms/venues/wagtail_hooks.py
@@ -0,0 +1,13 @@
+from wagtail import hooks
+
+from .admin import venue_explorer_viewset, venue_sidebar_viewset
+
+
+@hooks.register("register_admin_viewset")
+def register_venue_sidebar_viewset():
+ return venue_sidebar_viewset
+
+
+@hooks.register("register_admin_viewset")
+def register_venue_explorer_viewset():
+ return venue_explorer_viewset