From 089970a5cd75b047cfcc8cfd94122e13d58d49c3 Mon Sep 17 00:00:00 2001 From: Jonas Braathen Date: Tue, 26 May 2026 01:41:39 +0200 Subject: [PATCH] dnscms: use wagtail pageviewsets everywhere --- dnscms/associations/admin.py | 31 ++-- dnscms/associations/wagtail_hooks.py | 11 +- dnscms/events/admin.py | 49 ++++-- dnscms/events/wagtail_hooks.py | 11 +- dnscms/locale/nb/LC_MESSAGES/django.mo | Bin 7434 -> 7872 bytes dnscms/locale/nb/LC_MESSAGES/django.po | 144 ++++++------------ dnscms/news/admin.py | 37 +++-- dnscms/news/wagtail_hooks.py | 11 +- dnscms/tests/test_news.py | 8 +- dnscms/venues/admin.py | 58 +++++++ .../venues/migrations/0025_verbose_names.py | 25 +++ dnscms/venues/models.py | 13 ++ dnscms/venues/wagtail_hooks.py | 13 ++ 13 files changed, 266 insertions(+), 145 deletions(-) create mode 100644 dnscms/venues/admin.py create mode 100644 dnscms/venues/migrations/0025_verbose_names.py create mode 100644 dnscms/venues/wagtail_hooks.py 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 335f88a2245783457d35427e96b2373401360296..b4672f78a39a50dbf3504032d3affcaa68cf81e0 100644 GIT binary patch delta 2480 zcmYk-drXye9LMqBL688siV|6Q5->qS5hyW`gv3fTL?K0#4xA@Fae~vipt!Z8Q!8z= zxz@7T+SuH?ReVFoM}>#&~caF2@nfz|wSM zreQVGWEyY=Hsf*Z#zAAECXKtN)366`!WZyHJcSzY3seU`Vk(YcGLB+C{)NSu#%Qt} zOK=^o#6i@+AECzi23e!|4W}}`8Kt6*|G_-GhMAblAsc6-23&~sScU31jJp2}y74R2 zeShNJ_&3hOimXI?hqVh?yzychGjUet`AMh);XYUU@AKFw#y9L;%Lfj?mi z&SkdB+5#jyrV=Z$7OOCTkK!@Z&gJB=|N3DG70rA;Y9|)qQ@8|meHb;7v)1pBG0hd! zfd8R>mqfmlx^(QtB3y>gB3UwDB7f$44qDKaT=ripQSTZk6Y0xLM-5PnOx9G|_D0lz z>rwY_vgaODKig0{6hUqA9@~D&7$o#?Mh5TtMx}FQ|b=P(S?L zULQkEB$Z`+idk5S=TR%Xifb^NeICFq*n<~w71oenrD$K2iY9Q-`XXuvj^YV?7kA?3 znTd|SMgGiX4h87qp!*9^6DmWcq5`#m8e}eJ8J1%!F2LP*2S(qb(oE$CRPIVS6k-Ev z<(;S<=|&9@K&@~)DnOKAtHK7ak`X$s3Tt=<X4{n82Z<_Tk!=%8iTJm}wrRj}Tdze;q?^!FPKM%TleOJi zt?`TOiB>R=(A%KWk|6#C>!8jHV(zkSji^sG4|lxG;kb~{?kR=iWdoImh$iA*VlAPR z%qGf+Vy&O_$Nyp6gDSl8@%*hu520MIA=VMfH)%8*3BAX9aJz^SVsg1}(#d-2bBTwE zQbNy^N)i#(gZ2QSytWd0!rO@PvV}?w;U)AbR#{2(5cR~Jgh~UUXGZx}Sw(CnDv3@) zMX#&M5~3nri$6BT+#h>0_4VW|UoaGQ2O`~mUuc`-jkQlZ=E`Vw`obQ6$Q|%(jg4hy zCCzAe`XY|If4~V2_?+z~ej18R%{l983QpYXn8_z06Y@v4ngRVZadi9oyv~lvbtjZK z3&p<9eI_NR>66nFvvGpqSVzH-E4?l3cYIFB=XGKQh51P{oBMk`en*oD`Fg`9aT<)3 o&ARAX8@Yb3nWzR$zzIIaZ~A&2-eb?wAX{?f9QZ3JOBUy delta 2042 zcmZwIdrZw?9LMqJoH`0!RN}Z+l5}w`B-dOjO68hl2+0s)8U1rMvoV=}a+_?J#w<4c zW*BW{_{(N4e=zsWKTLnPjF~pxpWk`RW}fx^y`JCi{Lb_HKHum0o!Y1D&keka4LxjV z%ZP!*N3SsfyyfFSyB=mtI^Mt-e1~QD4ePNW+?XnC!#qsrZcIBJL*0^Uz&u2a5z?=uJ5+) zMdoA25RHa6l0 zY)4fpBRcrK>8Ql=F`$_j(bYh^9E)5sek3Q87DN3R+vL~}3Q;qvKz(7AJzs|!s1{X;22?5c+3QXA{1hsY zR#b_v+3Ovs8Q;e{_z=_ZNG$cIZ`zq|2|mIdm_+((@E8_h7plaC)KL$fZ(WX>Q4OBM zo!EehtfR(l#y)rv`{Etc{Vz}x?F`USX}_W}>_U~!!?#9aJZ9o79D#LMj3-fV$w%ym zK1Nr?5>ORNK|LS~l}IkCA2orZ$dq= z8CB}j)>hOKTt!W&1C`)C)Sh~So5yja0@;~ z4OqyJhVHLKJ#aNDp&Hzeb@uZvRAoa31nrCAdjB)%4B~@qT!{-%30-k-Fy=F=gg;RO zMDjDKJ&=S-FcUSS9Ax=TDJp@r_Vdl?Zd%j?_n{I#h*3P>999QgtPW}^ZleZzjC#;Z zd;Z>@e?cYk3qAO!CDK_$%(oYmJd<~mvzJUR9ag{uQaETsscEC75qhU*5i^Megw|aZ z`nRzH?xvi8>|1vqDWSQYFwaCfl1LI=m%>tq0L9-%_w3Rp`}vG3G&|~|6JX**M=@4e45n^Vmwhn6cP!9+H_(Lp|w{l zCz6PvL^2WP)Q7i*I-?`cc$_y;5njg|bJr8PeN%mn6B*a&bI$wQJWgHzIIq(@@QJ7A Q*6MAwRhz0eZ>%=I0pBpIFaQ7m 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