Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions docs/source/settings_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -392,3 +392,43 @@ Use this to specify the 'depth' value of a project's 'section root' pages. For m
Default value: ``False``

By default, menu items linking to custom URLs are attributed with the 'active' class only if their ``link_url`` value matches the path of the current request _exactly_. Setting this to `True` in your project's settings will enable a smarter approach to active class attribution for custom URLs, where only the 'path' part of the ``link_url`` value is used to determine what active class should be used. The new approach will also attribute the 'ancestor' class to menu items if the ``link_url`` looks like an ancestor of the current request URL.


.. _LOCALIZE_MENU_ITEMS:

``WAGTAILMENUS_LOCALIZE_MENU_ITEMS``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Default value: ``False`` (or ``True`` when ``WAGTAIL_I18N_ENABLED = True``)

When enabled, wagtailmenus will resolve each menu item's ``link_page`` to its
translation in the currently active locale at render time, so that menu item
titles and URLs automatically reflect the visitor's language without any
additional configuration.

If ``WAGTAIL_I18N_ENABLED`` is set to ``True`` in your project's settings,
this feature is **enabled automatically** — you do not need to set
``WAGTAILMENUS_LOCALIZE_MENU_ITEMS`` explicitly. You can still opt out by
setting it to ``False`` even when ``WAGTAIL_I18N_ENABLED`` is ``True``.

.. code-block:: python

# e.g. settings/base.py

# Opt in explicitly (not required if WAGTAIL_I18N_ENABLED = True):
WAGTAILMENUS_LOCALIZE_MENU_ITEMS = True

# Opt out even when WAGTAIL_I18N_ENABLED = True:
WAGTAILMENUS_LOCALIZE_MENU_ITEMS = False

When active, menu items stored against a default-locale page will be displayed
using the equivalent page in the active locale (if a translation exists).
If no translation is found for the active locale, the original page is used as
a fallback. The localization is applied **at render time only** — the stored
``link_page`` foreign key on the menu item is never modified.

.. NOTE::
Menu items are configured once, against the default-locale page. For this feature to do
anything, your pages need translated versions. `wagtail-localize
<https://github.com/wagtail/wagtail-localize>`_ is the usual way to produce them, but any
Wagtail translation workflow that creates linked ``Locale`` copies will work.
2 changes: 2 additions & 0 deletions wagtailmenus/conf/defaults.py
Comment thread
MrCordeiro marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@

ACTIVE_ANCESTOR_CLASS = 'ancestor'

LOCALIZE_MENU_ITEMS = False

PAGE_FIELD_FOR_MENU_ITEM_TEXT = 'title'

SECTION_ROOT_DEPTH = 3
Expand Down
9 changes: 9 additions & 0 deletions wagtailmenus/conf/settings.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import sys

from django.conf import settings as django_settings
from cogwheels import BaseAppSettingsHelper


class WagtailmenusSettingsHelper(BaseAppSettingsHelper):
deprecations = ()

def __getattr__(self, name):
value = super().__getattr__(name)
# Auto-detect locale-awareness from Wagtail's own i18n flag when the
# wagtailmenus-specific setting has not been explicitly overridden.
if name == 'LOCALIZE_MENU_ITEMS' and not self.is_overridden('LOCALIZE_MENU_ITEMS'):
return bool(getattr(django_settings, 'WAGTAIL_I18N_ENABLED', False))
return value


sys.modules[__name__] = WagtailmenusSettingsHelper()
49 changes: 41 additions & 8 deletions wagtailmenus/models/menus.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,21 @@ def pages_for_display(self):
# using OrderedDict to preserve ordering in Python < 3.6
return OrderedDict((p.id, p) for p in self.get_pages_for_display())

@cached_property
def localize_page_map(self):
"""Mapping of stored (default-locale) page IDs to their active-locale
page counterparts. Empty dict when LOCALIZE_MENU_ITEMS is disabled."""
if not settings.LOCALIZE_MENU_ITEMS:
return {}
menu_items = getattr(self, '_raw_menu_items', None) or self.get_base_menuitem_queryset()
mapping = {}
for item in menu_items:
if item.link_page:
localized = item.link_page.localized
if localized and localized.pk != item.link_page.pk:
mapping[item.link_page.pk] = localized
return mapping

def get_page_children_dict(self, page_qs=None):
"""
Returns a dictionary of lists, where the keys are 'path' values for
Expand Down Expand Up @@ -963,6 +978,9 @@ def get_top_level_items(self):
# allow this query result to be reused by get_pages_for_display()
self._raw_menu_items = menu_items

pages = self.pages_for_display
localize_map = self.localize_page_map

top_level_items = []
for item in menu_items:

Expand All @@ -971,10 +989,14 @@ def get_top_level_items(self):
top_level_items.append(item)
continue

# Translate the stored (default-locale) FK to the active-locale page
# when localization is active, without mutating item.link_page_id.
effective_page = localize_map.get(item.link_page_id, item.link_page)

# But, we only want to include links to pages if the page was
# in the get_pages_for_display() result
try:
item.link_page = self.pages_for_display[item.link_page_id]
item.link_page = pages[effective_page.pk]
top_level_items.append(item)
except KeyError:
continue
Expand All @@ -997,21 +1019,32 @@ def get_pages_for_display(self):
queryset = Page.objects.none()

for item in (item for item in menu_items if item.link_page):
effective_page = self.localize_page_map.get(item.link_page.pk, item.link_page)

if(
item.allow_subnav and
item.link_page.depth >= settings.SECTION_ROOT_DEPTH
effective_page.depth >= settings.SECTION_ROOT_DEPTH
):
# Add this branch to the overall `queryset`
queryset = queryset | Page.objects.filter(
path__startswith=item.link_page.path,
depth__lt=item.link_page.depth + self.max_levels,
path__startswith=effective_page.path,
depth__lt=effective_page.depth + self.max_levels,
)
else:
# Add this page only to the overall `queryset`
queryset = queryset | Page.objects.filter(id=item.link_page_id)

# Filter out pages unsutable display
queryset = self.get_base_page_queryset() & queryset
queryset = queryset | Page.objects.filter(id=effective_page.pk)

if settings.LOCALIZE_MENU_ITEMS:
# `queryset` already contains the localized pages.
# Apply the same live/show_in_menus/expired constraints as
# get_base_page_queryset() but only against the small set of pages
# in `queryset` (scales with menu size, not site size).
queryset = self.get_base_page_queryset().filter(
id__in=queryset.values('id')
)
else:
# Filter out pages unsuitable for display
queryset = self.get_base_page_queryset() & queryset

# Always return 'specific' page instances
return queryset.specific()
Expand Down
Loading
Loading