diff --git a/network-api/networkapi/donate_banner/tests.py b/network-api/networkapi/donate_banner/tests.py index aa20d27a884..56a73578132 100644 --- a/network-api/networkapi/donate_banner/tests.py +++ b/network-api/networkapi/donate_banner/tests.py @@ -1,7 +1,7 @@ from django.contrib.auth import get_user_model from django.test import TestCase from django.urls import reverse -from wagtail.admin.viewsets import viewsets +from wagtail.admin.viewsets import viewsets as wagtail_admin_viewsets from wagtail.admin.viewsets.chooser import ChooserViewSet from wagtail.models import Locale from wagtail.test.utils import WagtailTestUtils @@ -17,7 +17,8 @@ def is_donate_banner_chooser_viewset(self, viewset): def get_chooser_viewset(self): # Get the last registered ChooserViewSet for the DonateBanner model. # Note: There can be multiple ChooserViewSets registered for a model. - model_viewsets = [x for x in viewsets.viewsets if self.is_donate_banner_chooser_viewset(x)] + wagtail_admin_viewsets.populate() + model_viewsets = [x for x in wagtail_admin_viewsets.viewsets if self.is_donate_banner_chooser_viewset(x)] return model_viewsets[-1] def setUp(self): diff --git a/network-api/networkapi/nav/factories.py b/network-api/networkapi/nav/factories.py index 41f4fd94a27..5c286c3115e 100644 --- a/network-api/networkapi/nav/factories.py +++ b/network-api/networkapi/nav/factories.py @@ -219,6 +219,7 @@ class Params: ) no_button = factory.Trait(button=[]) + title = factory.Faker("sentence", nb_words=3) overview = wagtail_factories.ListBlockFactory(NavOverviewFactory) columns = wagtail_factories.ListBlockFactory( NavColumnFactory, @@ -247,3 +248,4 @@ class Meta: "3": "dropdown", }, ) + locale = factory.LazyFunction(lambda: wagtail_models.Locale.get_default()) diff --git a/network-api/networkapi/nav/migrations/0002_sitenavmenu.py b/network-api/networkapi/nav/migrations/0002_sitenavmenu.py new file mode 100644 index 00000000000..3f35e37b12a --- /dev/null +++ b/network-api/networkapi/nav/migrations/0002_sitenavmenu.py @@ -0,0 +1,42 @@ +# Generated by Django 4.2.11 on 2024-04-02 12:26 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("wagtailcore", "0089_log_entry_data_json_null_to_object"), + ("nav", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="SiteNavMenu", + fields=[ + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ( + "active_nav_menu", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="site_nav_menu", + verbose_name="Active Navigation Menu", + to="nav.navmenu", + ), + ), + ( + "site", + models.OneToOneField( + editable=False, on_delete=django.db.models.deletion.CASCADE, to="wagtailcore.site" + ), + ), + ], + options={ + "verbose_name": "Navigation Menu", + "verbose_name_plural": "Navigation Menus", + }, + ), + ] diff --git a/network-api/networkapi/nav/models.py b/network-api/networkapi/nav/models.py index f6efd1a8917..6e0bb10ef11 100644 --- a/network-api/networkapi/nav/models.py +++ b/network-api/networkapi/nav/models.py @@ -1,7 +1,10 @@ from django.db import models from wagtail import models as wagtail_models from wagtail.admin import panels +from wagtail.contrib.settings.models import BaseSiteSetting, register_setting from wagtail.fields import StreamField +from wagtail.search import index +from wagtail_localize.fields import TranslatableField from networkapi.nav import blocks as nav_blocks @@ -26,13 +29,43 @@ class NavMenu( ) panels = [ + panels.HelpPanel(content="To enable a navigation menu on a site, go to Settings > Navigation Menus."), panels.FieldPanel("title"), panels.FieldPanel("dropdowns"), ] + translatable_fields = [ + TranslatableField("title"), + TranslatableField("dropdowns"), + ] + + search_fields = [ + index.SearchField("title"), + ] + class Meta(wagtail_models.TranslatableMixin.Meta): verbose_name = "Navigation Menu" verbose_name_plural = "Navigation Menus" def __str__(self) -> str: return self.title + + +@register_setting +class SiteNavMenu(BaseSiteSetting): + active_nav_menu = models.ForeignKey( + "nav.NavMenu", + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name="site_nav_menu", + verbose_name="Active Navigation Menu", + ) + + content_panels = [ + panels.FieldPanel("active_nav_menu"), + ] + + class Meta: + verbose_name = "Navigation Menu" + verbose_name_plural = "Navigation Menus" diff --git a/network-api/networkapi/nav/tests/test_views.py b/network-api/networkapi/nav/tests/test_views.py new file mode 100644 index 00000000000..5ce457f264b --- /dev/null +++ b/network-api/networkapi/nav/tests/test_views.py @@ -0,0 +1,58 @@ +from django.contrib.auth import get_user_model +from django.test import TestCase +from django.urls import reverse +from wagtail.admin.viewsets import viewsets as wagtail_admin_viewsets +from wagtail.admin.viewsets.chooser import ChooserViewSet +from wagtail.models import Locale +from wagtail.test.utils import WagtailTestUtils + +from networkapi.nav import factories as nav_factories +from networkapi.nav import models as nav_models + + +class TestNavMenuSnippetChooser(WagtailTestUtils, TestCase): + def is_nav_menu_chooser_viewset(self, viewset): + return viewset.model == nav_models.NavMenu and issubclass(viewset.__class__, ChooserViewSet) + + def get_chooser_viewset(self): + # Get the last registered ChooserViewSet for the NavMenu model. + # Note: There can be multiple ChooserViewSets registered for a model. + wagtail_admin_viewsets.populate() + model_viewsets = [x for x in wagtail_admin_viewsets.viewsets if self.is_nav_menu_chooser_viewset(x)] + return model_viewsets[-1] + + def setUp(self): + # Get the chooser url from the viewsets registry. + # Would have been easier to get from model.snippet_viewset, but + # that does not work somehow. + chooser_viewset = self.get_chooser_viewset() + self.chooser_url = reverse(f"{chooser_viewset.name}:choose") + + User = get_user_model() + self.user = User.objects.create_superuser("admin-user", "admin@example.com", "password") + self.client.force_login(self.user) + + self.default_locale = Locale.get_default() + self.fr_locale = Locale.objects.create(language_code="fr") + self.menus = [ + nav_factories.NavMenuFactory.create(title="Generic"), + nav_factories.NavMenuFactory.create(title="Foundation"), + nav_factories.NavMenuFactory.create(title="Mozfest"), + ] + + def test_menu_chooser_exclude_locale(self): + translated_menu = self.menus[0].copy_for_translation(self.fr_locale) + translated_menu.save() + all_menus = nav_models.NavMenu.objects.all() + default_menus = nav_models.NavMenu.objects.filter(locale=self.default_locale) + response = self.client.get(self.chooser_url) + results = response.context["results"] + + # Chooser does not include every menu, but only the default language ones + self.assertNotEqual(len(results), all_menus.count()) + self.assertEqual(len(results), default_menus.count()) + self.assertNotIn(translated_menu, results) + self.assertIn(self.menus[0], results) + + # Menu chooser should not contain locale filter + self.assertNotContains(response, "id_locale") diff --git a/network-api/networkapi/nav/wagtail_hooks.py b/network-api/networkapi/nav/wagtail_hooks.py index eba9b1212f2..efe0ce37397 100644 --- a/network-api/networkapi/nav/wagtail_hooks.py +++ b/network-api/networkapi/nav/wagtail_hooks.py @@ -1,7 +1,23 @@ +from wagtail import hooks from wagtail.snippets.models import register_snippet from wagtail.snippets.views.snippets import SnippetViewSet, SnippetViewSetGroup from networkapi.nav.models import NavMenu +from networkapi.wagtailcustomization.views.snippet_chooser import ( + DefaultLocaleSnippetChooserViewSet, +) + + +# Customise chooser to only show the default language nav menus as options. +# We do not want editors to select the translations as +# localisation will be handled on the template instead. +@hooks.register("register_admin_viewset") +def register_nav_menu_chooser_viewset(): + return DefaultLocaleSnippetChooserViewSet( + "wagtailsnippetchoosers_custom_navmenu", + model=NavMenu, + url_prefix="nav/chooser", + ) class NavMenuViewSet(SnippetViewSet):