From 29c61ffc768fb526738cde920a0798846945c924 Mon Sep 17 00:00:00 2001 From: Jonas Braathen Date: Tue, 19 May 2026 21:49:38 +0200 Subject: [PATCH] dnscms: improve associations app --- dnscms/associations/admin.py | 46 +++++ .../0026_alter_association_options.py | 26 +++ dnscms/associations/models.py | 29 ++-- dnscms/associations/views.py | 7 +- dnscms/associations/wagtail_hooks.py | 6 + dnscms/dnscms/admin.py | 27 +++ dnscms/dnscms/wagtail_hooks.py | 10 -- dnscms/events/admin.py | 23 +-- dnscms/locale/nb/LC_MESSAGES/django.mo | Bin 5731 -> 6505 bytes dnscms/locale/nb/LC_MESSAGES/django.po | 163 ++++++++++++------ dnscms/tests/test_associations.py | 53 ++++++ 11 files changed, 295 insertions(+), 95 deletions(-) create mode 100644 dnscms/associations/admin.py create mode 100644 dnscms/associations/migrations/0026_alter_association_options.py create mode 100644 dnscms/dnscms/admin.py create mode 100644 dnscms/tests/test_associations.py diff --git a/dnscms/associations/admin.py b/dnscms/associations/admin.py new file mode 100644 index 0000000..82add05 --- /dev/null +++ b/dnscms/associations/admin.py @@ -0,0 +1,46 @@ +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 associations.models import AssociationPage +from dnscms.admin import ListingRedirectChooseParentView + + +class AssociationTypeColumn(Column): + def get_value(self, instance): + return instance.get_association_type_display() + + +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" + + columns = [ + PageTitleColumn("title", label=_("Title"), sort_key="title", classname="title"), + AssociationTypeColumn( + "association_type", + label=_("Type"), + sort_key="association_type", + width="15%", + ), + DateColumn( + "latest_revision_created_at", + label=_("Updated"), + sort_key="latest_revision_created_at", + width="10%", + ), + PageStatusColumn("status", label=_("Status"), sort_key="live", width="10%"), + ] + + +association_page_listing_viewset = AssociationPageListingViewSet("associations") diff --git a/dnscms/associations/migrations/0026_alter_association_options.py b/dnscms/associations/migrations/0026_alter_association_options.py new file mode 100644 index 0000000..e28b607 --- /dev/null +++ b/dnscms/associations/migrations/0026_alter_association_options.py @@ -0,0 +1,26 @@ +# Generated by Django 6.0.5 on 2026-05-19 19:36 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('associations', '0025_associationpage_lead'), + ] + + operations = [ + migrations.AlterModelOptions( + name='associationindex', + options={'verbose_name': 'association index', 'verbose_name_plural': 'association indexes'}, + ), + migrations.AlterModelOptions( + name='associationpage', + options={'verbose_name': 'association', 'verbose_name_plural': 'associations'}, + ), + migrations.AlterField( + model_name='associationpage', + name='association_type', + field=models.CharField(choices=[('forening', 'Association'), ('utvalg', 'Committee')], default='forening', max_length=64), + ), + ] diff --git a/dnscms/associations/models.py b/dnscms/associations/models.py index 52b08db..5d526dc 100644 --- a/dnscms/associations/models.py +++ b/dnscms/associations/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 ( GraphQLImage, @@ -26,8 +27,8 @@ class AssociationIndex(HeadlessMixin, Page): body = CommonStreamField content_panels = Page.content_panels + [ - FieldPanel("lead", heading="Ingress"), - FieldPanel("body", heading="Innhold"), + FieldPanel("lead", heading=_("Lead")), + FieldPanel("body", heading=_("Content")), ] graphql_fields = [ @@ -37,6 +38,10 @@ class AssociationIndex(HeadlessMixin, Page): search_fields = Page.search_fields + class Meta: + verbose_name = _("association index") + verbose_name_plural = _("association indexes") + class AssociationPage(HeadlessMixin, WPImportedPageMixin, Page): subpage_types = [] @@ -44,8 +49,8 @@ class AssociationPage(HeadlessMixin, WPImportedPageMixin, Page): show_in_menus = False class AssociationType(models.TextChoices): - FORENING = "forening", "Forening" - UTVALG = "utvalg", "Utvalg" + FORENING = "forening", _("Association") + UTVALG = "utvalg", _("Committee") excerpt = models.TextField(max_length=512, blank=False) lead = RichTextField(features=["italic", "link"], blank=True) @@ -65,14 +70,14 @@ class AssociationPage(HeadlessMixin, WPImportedPageMixin, Page): content_panels = Page.content_panels + [ FieldPanel( "excerpt", - heading="Utdrag", - help_text="En veldig kort oppsummering av innholdet nedenfor. Brukes i listevisninger.", + heading=_("Excerpt"), + help_text=_("A very short summary of the content below. Used in listing views."), ), - FieldPanel("lead", heading="Ingress"), - FieldPanel("body", heading="Innhold"), + FieldPanel("lead", heading=_("Lead")), + FieldPanel("body", heading=_("Content")), FieldPanel("logo"), - FieldPanel("association_type", heading="Type"), - FieldPanel("website_url", heading="Nettside"), + FieldPanel("association_type", heading=_("Type")), + FieldPanel("website_url", heading=_("Website")), ] graphql_fields = [ @@ -89,6 +94,10 @@ class AssociationPage(HeadlessMixin, WPImportedPageMixin, Page): index.SearchField("body"), ] + class Meta: + verbose_name = _("association") + verbose_name_plural = _("associations") + def import_wordpress_data(self, data): import html diff --git a/dnscms/associations/views.py b/dnscms/associations/views.py index 85e17bf..c5e02e9 100644 --- a/dnscms/associations/views.py +++ b/dnscms/associations/views.py @@ -1,12 +1,13 @@ +from django.utils.translation import gettext_lazy as _ from wagtail.admin.viewsets.chooser import ChooserViewSet class AssociationChooserViewSet(ChooserViewSet): model = "associations.AssociationPage" icon = "group" - choose_one_text = "Choose an association" - choose_another_text = "Choose another association" - edit_item_text = "Edit this association" + choose_one_text = _("Choose an association") + choose_another_text = _("Choose another association") + edit_item_text = _("Edit this association") # form_fields = ["name"] diff --git a/dnscms/associations/wagtail_hooks.py b/dnscms/associations/wagtail_hooks.py index 1f43470..ce9cfab 100644 --- a/dnscms/associations/wagtail_hooks.py +++ b/dnscms/associations/wagtail_hooks.py @@ -1,8 +1,14 @@ from wagtail import hooks +from .admin import association_page_listing_viewset from .views import association_chooser_viewset @hooks.register("register_admin_viewset") def register_viewset(): return association_chooser_viewset + + +@hooks.register("register_admin_viewset") +def register_association_page_listing_viewset(): + return association_page_listing_viewset diff --git a/dnscms/dnscms/admin.py b/dnscms/dnscms/admin.py new file mode 100644 index 0000000..99f6048 --- /dev/null +++ b/dnscms/dnscms/admin.py @@ -0,0 +1,27 @@ +from urllib.parse import urlencode + +from django.urls import reverse +from wagtail.admin.views.pages.choose_parent import ChooseParentView + + +class ListingRedirectChooseParentView(ChooseParentView): + """ChooseParentView that redirects new pages back to a listing viewset. + + Subclasses set ``listing_url_name`` (e.g. ``"events:index"``). + """ + + listing_url_name: str + + def _with_next(self, response): + if response.status_code != 302: + return response + url = response["Location"] + sep = "&" if "?" in url else "?" + response["Location"] = f"{url}{sep}{urlencode({'next': reverse(self.listing_url_name)})}" + return response + + def get(self, request, *args, **kwargs): + return self._with_next(super().get(request, *args, **kwargs)) + + def form_valid(self, form): + return self._with_next(super().form_valid(form)) diff --git a/dnscms/dnscms/wagtail_hooks.py b/dnscms/dnscms/wagtail_hooks.py index d48957e..7ab829d 100644 --- a/dnscms/dnscms/wagtail_hooks.py +++ b/dnscms/dnscms/wagtail_hooks.py @@ -5,7 +5,6 @@ from django.utils.html import format_html from wagtail import hooks from wagtail.admin.menu import MenuItem -from associations.models import AssociationIndex from news.models import NewsIndex @@ -14,15 +13,6 @@ def enable_additional_rich_text_features(features): features.default_features.extend(["h5", "h6", "blockquote"]) -@hooks.register("register_admin_menu_item") -def register_associations_menu_item(): - page = AssociationIndex.objects.first() - associations_url = "#" - if page: - associations_url = reverse("wagtailadmin_explore", args=(quote(page.pk),)) - return MenuItem("Foreninger", associations_url, icon_name="group", order=2) - - @hooks.register("register_admin_menu_item") def register_news_menu_item(): page = NewsIndex.objects.first() diff --git a/dnscms/events/admin.py b/dnscms/events/admin.py index 18e72df..2bb1e15 100644 --- a/dnscms/events/admin.py +++ b/dnscms/events/admin.py @@ -1,15 +1,12 @@ -from urllib.parse import urlencode - -from django.urls import reverse from django.utils import timezone from django.utils.translation import gettext, gettext_lazy as _ from django.utils.translation import ngettext from wagtail.admin.ui.tables import Column, DateColumn from wagtail.admin.ui.tables.pages import PageStatusColumn, PageTitleColumn -from wagtail.admin.views.pages.choose_parent import ChooseParentView from wagtail.admin.views.pages.listing import IndexView from wagtail.admin.viewsets.pages import PageListingViewSet +from dnscms.admin import ListingRedirectChooseParentView from events.models import EventPage @@ -44,22 +41,8 @@ class EventPageIndexView(IndexView): ) -class EventChooseParentView(ChooseParentView): - """Redirect newly-created EventPages back to the events listing.""" - - def _with_next(self, response): - if response.status_code != 302: - return response - url = response["Location"] - sep = "&" if "?" in url else "?" - response["Location"] = f"{url}{sep}{urlencode({'next': reverse('events:index')})}" - return response - - def get(self, request, *args, **kwargs): - return self._with_next(super().get(request, *args, **kwargs)) - - def form_valid(self, form): - return self._with_next(super().form_valid(form)) +class EventChooseParentView(ListingRedirectChooseParentView): + listing_url_name = "events:index" class EventPageListingViewSet(PageListingViewSet): diff --git a/dnscms/locale/nb/LC_MESSAGES/django.mo b/dnscms/locale/nb/LC_MESSAGES/django.mo index 4aad5b3e983af1524bff8901841d770f4c39f4b9..b2952ed080647040aeb287fe6662c1637f7f0e2e 100644 GIT binary patch delta 2337 zcmZYATWl0n9LMp~d!e+Y+^q=1r3$5NrGTZ1l~Q^^Zql?yAA~I3LpydmQ+B3B5@ln2 zAZauTs{~Ap5|JjvC#yafbGbuVW1^E;D8nW7vjgu>ya_S@=87 zz$u)MGs=xQhf8rTUcy@Z78eyy}p z@1qtho;kfxEf#SdLvlAwsQb3y9Bji%);GO$w9o;300&SvoIp*yh7K08P0d?@_u(pB zg^6hVIb^Cif#hOdiLNJ5iM)kM@Lkk>KgGGMZ!XiR#6PeD3#g;2--XJ27g8kCi|g?* z+Y_coEL#dI|EDX+-`^Yc#$awW0l}L=RR|e+?uVQ0W5HLPt?0 z9FN9dMD6HRynttMHM*>(gfcjWZ{QAG%%CbZh`R5g$R|+=9>WPdRYU#hn=O=K0rucR zbWszZMkROxRiV?UM9!cpa1LYm9yZ`rT#E~Mb0=;?PPsXbx8Zrz&MzQEH=m^GXo0U$ ziCjjN?s_z?2YWx)W%v+wpb|TYci{vovCnWLeuec|$w|{fn^2W(jqFBMxCgb7^gcSu ztRG3h5#3GJb-djEM;F@DuR!hYSzy-Jwr9^NNqEr#dGa;I=hJFM15`$wX=JNo%uq6-@T-?EQ+tuE>^KbB0UoiUp_;sk?!Ds+O;;o&$}{y`@kv`)Y8h1Lt5 z12*X&jyZjSO*pRSB;6o%y#Z&;wTFY4X$u0s-;IZ^@8$Oc(>5CV!;JTv_CendtP}T~ zc;4jfXy*0OixqjJnMcYRYTEtbVK)q|HSN9lVrpS0 ho98`S(2|>Ea-G2EVF&I|c delta 1602 zcmYk+-AhzK7{~F)Ty5Qa-*UG!wbV>A%h$Bbbk)2OsvAo%LL?}qq|yX~HYv#RBI0NT zmDEk$b+ZuOMOcAEh|rx;Mj#s0A7D{^f9_El_Sw&z**P=MnVGXo<=;xepVK|#MrkIp zh`M!VUD)a2i!vW)R*YXU4HM(d1~3~Fa15W}1p2XMy;%@DQ2j5WhPr_p@fPOeMAWcN zQ7NEd5nD0Y8y%<}W4Z1@#dYncj;w8IOOvYr)!4xcY z+v_o`Opj2}4ehAwP9!<&Msl$8$iv%^+kOMJq<8QQj$sjYFpKv5I$p+c)HsRE`WyqO z-(O(>XHzV^lHn2!8gLo4w12S_{iI)lA>569*oKdBGk!-U?qO7NFdu4yAYaUF#i$3W z#e>*@NAW6htIedb{)JTLY0#2-+2ATn#bP{+n&1Lz1qNM*Q7bcwO8h=5p-0HaUhhS%>tr*7%K81OxM6RRe4Np-~rZY%iTR=_l%k2=yk9vYMROUITL<&&-N>Bq;yVoK2 zx(&7WCol%Jx+-}@C6P(+f5ZAy3Wz;Kl`bd^gqBqkt7t{^bXy2Lm0n9tzFKOiY$uus zcGCC{TPxLTwdfY7d^=J6fJG@%71uEKe{adXTcEdvMMHyxj+J+s(Zlak` z;l)`iMQb&){mzg0N$)^^-?^^-v)xX%_r2pu$a2aPJ~{cmK%~i+5F0s`G!hdDrg+x* m8r_bUdY$`e)6QUert>uYk2C4djePOv#zs7W(HLhs_~ajHM29T^ diff --git a/dnscms/locale/nb/LC_MESSAGES/django.po b/dnscms/locale/nb/LC_MESSAGES/django.po index 1052573..ad3c85e 100644 --- a/dnscms/locale/nb/LC_MESSAGES/django.po +++ b/dnscms/locale/nb/LC_MESSAGES/django.po @@ -7,13 +7,89 @@ msgid "" msgstr "" "Project-Id-Version: dnscms\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-05-19 21:17+0200\n" +"POT-Creation-Date: 2026-05-19 21:36+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:40 +msgid "Associations" +msgstr "Foreninger" + +#: associations/admin.py:46 events/admin.py:76 +msgid "Title" +msgstr "Tittel" + +#: associations/admin.py:49 associations/models.py:81 +msgid "Type" +msgstr "Type" + +#: associations/admin.py:55 events/admin.py:81 +msgid "Updated" +msgstr "Oppdatert" + +#: associations/admin.py:59 events/admin.py:85 +msgid "Status" +msgstr "Status" + +#: associations/models.py:30 associations/models.py:78 events/models.py:327 +msgid "Lead" +msgstr "Ingress" + +#: associations/models.py:31 associations/models.py:79 +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 +msgid "Excerpt" +msgstr "Utdrag" + +#: associations/models.py:75 +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:82 events/models.py:189 +msgid "Website" +msgstr "Nettsted" + +#: associations/models.py:100 +msgid "association" +msgstr "forening" + +#: associations/models.py:101 +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:23 msgid "%Y-%m-%d at %H:%M" msgstr "%Y-%m-%d kl %H:%M" @@ -29,26 +105,14 @@ msgstr[1] "%(count)d forekomster" msgid "Events" msgstr "Arrangementer" -#: events/admin.py:76 -msgid "Title" -msgstr "Tittel" - #: events/admin.py:77 msgid "Date" msgstr "Dato" -#: events/admin.py:78 events/models.py:333 +#: events/admin.py:78 events/models.py:331 msgid "Organizers" msgstr "Arrangører" -#: events/admin.py:81 -msgid "Updated" -msgstr "Oppdatert" - -#: events/admin.py:85 -msgid "Status" -msgstr "Status" - #: events/models.py:73 events/models.py:156 msgid "slug" msgstr "permalenke" @@ -69,7 +133,7 @@ msgstr "Ingen" msgid "Default pig for events of this kind." msgstr "Standardgris for arrangementer av denne typen." -#: events/models.py:98 events/models.py:343 +#: events/models.py:98 events/models.py:341 msgid "Pig" msgstr "Gris" @@ -109,10 +173,6 @@ msgstr "Intern arrangør" msgid "External organizer" msgstr "Ekstern arrangør" -#: events/models.py:189 -msgid "Website" -msgstr "Nettsted" - #: events/models.py:190 msgid "Leave this empty if the organizer exists in the list above." msgstr "La denne stå tom om arrangøren finnes i lista over." @@ -157,33 +217,34 @@ msgstr "" #: 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" +msgstr "" +"Lenke direkte til billettkjøp, f.eks. TicketCo, Billetto eller Ticketmaster" #: events/models.py:289 msgid "Direct link to the event on Facebook" msgstr "Lenke direkte til arrangementet på Facebook" -#: events/models.py:299 +#: events/models.py:298 msgid "Free" msgstr "Gratis" -#: events/models.py:299 +#: events/models.py:298 msgid "Is this event free for everyone?" msgstr "Er dette arrangementet gratis for alle?" -#: events/models.py:305 +#: events/models.py:303 msgid "Regular price" msgstr "Ordinær pris" -#: events/models.py:306 +#: events/models.py:304 msgid "Price for students" msgstr "Pris for studenter" -#: events/models.py:307 +#: events/models.py:305 msgid "Price for DNS members" msgstr "Pris for medlemmer av DNS" -#: events/models.py:314 +#: events/models.py:312 msgid "" "Write 0 for free. An empty field hides the price category. " "If possible, write digits only." @@ -191,59 +252,57 @@ msgstr "" "Skriv 0 om gratis. Tomt felt skjuler priskategorien. Om " "mulig, skriv kun tall." -#: events/models.py:323 +#: events/models.py:321 msgid "Ticket purchase link" msgstr "Billettkjøpslenke" -#: events/models.py:327 +#: events/models.py:325 msgid "Subtitle" msgstr "Undertittel" -#: events/models.py:329 -msgid "Lead" -msgstr "Ingress" - -#: events/models.py:336 +#: events/models.py:334 msgid "Who is behind the event?" msgstr "Hvem står bak arrangementet?" -#: events/models.py:339 +#: events/models.py:337 msgid "Organizer" msgstr "Arrangør" -#: events/models.py:346 +#: events/models.py:344 msgid "Facebook link" msgstr "Facebook-lenke" -#: events/models.py:347 +#: events/models.py:345 msgid "Direct link to the event on Facebook." msgstr "Lenke direkte til arrangementet på Facebook." -#: events/models.py:349 +#: events/models.py:347 msgid "Pricing and tickets" msgstr "Priser og billettkjøp" -#: events/models.py:351 +#: events/models.py:349 msgid "Date, time and venue" msgstr "Dato, tid og lokale" -#: events/models.py:355 +#: 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." +msgstr "" +"Om arrangementet går over flere dager, legg inn hver dag som en egen " +"forekomst." -#: events/models.py:359 +#: events/models.py:356 msgid "Occurrence" msgstr "Forekomst" -#: events/models.py:402 +#: events/models.py:399 msgid "event" msgstr "arrangement" -#: events/models.py:403 +#: events/models.py:400 msgid "events" msgstr "arrangementer" -#: events/models.py:563 +#: 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." @@ -251,35 +310,35 @@ msgstr "" "Bruk denne om ingen av lokalene som kan velges til venstre passer. " "F.eks. Frederikkeplassen eller Sirkusteltet." -#: events/models.py:572 +#: events/models.py:569 msgid "Start" msgstr "Start" -#: events/models.py:573 +#: events/models.py:570 msgid "End" msgstr "Slutt" -#: events/models.py:578 +#: events/models.py:575 msgid "Venue" msgstr "Lokale" -#: events/models.py:579 +#: events/models.py:576 msgid "Venue as free text" msgstr "Lokale som fritekst" -#: events/models.py:596 +#: 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:601 +#: events/models.py:598 msgid "Venue is required." msgstr "Lokale er påkrevd." -#: events/models.py:607 +#: events/models.py:604 msgid "occurrence" msgstr "forekomst" -#: events/models.py:608 +#: events/models.py:605 msgid "occurrences" msgstr "forekomster" diff --git a/dnscms/tests/test_associations.py b/dnscms/tests/test_associations.py new file mode 100644 index 0000000..e48e633 --- /dev/null +++ b/dnscms/tests/test_associations.py @@ -0,0 +1,53 @@ +from associations.admin import AssociationTypeColumn +from associations.models import AssociationPage +from tests.conftest import AssociationPageFactory + + +def test_associationpage_persists_via_factory(association_index): + page = AssociationPageFactory( + parent=association_index, + title="EDB-gjengen", + excerpt="WOW FLINKE", + association_type=AssociationPage.AssociationType.UTVALG, + ) + + reloaded = AssociationPage.objects.get(pk=page.pk) + assert reloaded.title == "EDB-gjengen" + assert reloaded.excerpt == "WOW FLINKE" + assert reloaded.association_type == "utvalg" + + +def test_association_type_column_renders_forening_display(association_index): + page = AssociationPageFactory( + parent=association_index, + association_type=AssociationPage.AssociationType.FORENING, + ) + column = AssociationTypeColumn("association_type") + + assert column.get_value(page) == "Forening" + + +def test_association_type_column_renders_utvalg_display(association_index): + page = AssociationPageFactory( + parent=association_index, + association_type=AssociationPage.AssociationType.UTVALG, + ) + column = AssociationTypeColumn("association_type") + + assert column.get_value(page) == "Utvalg" + + +def test_graphql_association_index_query(association_index, graphql_post): + response, body = graphql_post( + """ + query { + associationIndex { + title + } + } + """ + ) + + assert response.status_code == 200 + assert "errors" not in body, body + assert body["data"]["associationIndex"]["title"] == association_index.title