dynamicweb/menus/utils.py

321 lines
11 KiB
Python

from django.conf import settings
from django.urls import NoReverseMatch, Resolver404, resolve, reverse
from cms.toolbar.utils import get_object_edit_url, get_object_for_language, get_object_preview_url
from cms.utils import get_language_from_request
from cms.utils.i18n import (
force_language,
get_default_language_for_site,
get_fallback_languages,
hide_untranslated,
is_valid_site_language,
)
def mark_descendants(nodes):
"""
Mark the descendants of each node in a list.
Args:
nodes (list): A list of nodes.
Returns:
None
Modifies:
Each node in the input list will have its `descendant` attribute set to True.
Note:
This function is a recursive function that marks the descendants of each node in the input list.
Raises:
None
"""
for node in nodes:
node.descendant = True
mark_descendants(node.children)
def cut_levels(nodes, level):
"""
For cutting the nav_extender levels if you have a from_level in the navigation.
"""
if nodes:
if nodes[0].level == level:
return nodes
return sum((cut_levels(node.children, level) for node in nodes), [])
def find_selected(nodes):
"""
Finds a selected nav_extender node
"""
for node in nodes:
if hasattr(node, "selected"):
return node
elif hasattr(node, "ancestor"):
result = find_selected(node.children)
if result:
return result
def set_language_changer(request, func):
"""
Sets a language chooser function that accepts one parameter: language.
Parameters:
language (str): The language to be used.
Returns:
str: The URL in the supplied language.
Example:
def get_absolute_url(self, language=None):
reverse('product_view', args=[self.get_slug(language=language)])
Use this function in your nav extender views that have i18n slugs.
"""
request._language_changer = func
def language_changer_decorator(language_changer):
"""
A decorator wrapper for set_language_changer.
Example usage:
from menus.utils import language_changer_decorator
@language_changer_decorator(function_get_language_changer_url)
def my_view_function(request, somearg):
pass
"""
def _decorator(func):
"""
Decorator function that sets the language changer before calling the wrapped function.
Args:
func (function): The function to be wrapped.
Returns:
function: The wrapped function.
Raises:
None
"""
def _wrapped(request, *args, **kwargs):
"""
Wrapper function that sets the language changer and calls the original function.
Args:
request (object): The request object.
*args: Variable length argument list.
**kwargs: Arbitrary keyword arguments.
Returns:
The return value of the original function.
"""
set_language_changer(request, language_changer)
return func(request, *args, **kwargs)
_wrapped.__name__ = func.__name__
_wrapped.__doc__ = func.__doc__
return _wrapped
return _decorator
class DefaultLanguageChanger:
"""
A class for changing the default language of a web application.
Attributes:
request (object): The request object received by the view.
_app_path (str): The path of the application.
Methods:
app_path: Returns the path of the application based on the current language settings.
get_page_path: Returns the path of the page for a specific language.
__call__: Changes the language of the web application and returns the corresponding URL.
"""
def __init__(self, request):
"""
Initializer for the class.
Args:
request: The request object.
Attributes:
request: The request object.
_app_path: The application path.
Returns:
None.
"""
self.request = request
self.request_language = get_language_from_request(request)
self._app_path = None
@property
def request_path(self):
"""
The path of the page the user is *viewing*.
For regular page requests this equals ``request.path_info``. For
asynchronous toolbar/structure requests, ``request.path_info`` points at
an internal admin/AJAX endpoint, while the viewed page is reported via the
``cms_path`` GET query and stored on ``toolbar.request_path``. Prefer the
latter so that language URLs are built for the viewed page rather than the
AJAX endpoint.
"""
toolbar = getattr(self.request, 'toolbar', None)
request_path = getattr(toolbar, 'request_path', None)
return request_path or self.request.path_info
@property
def app_path(self):
"""
Returns the app path based on the current request.
Returns:
str: The app path.
Raises:
None.
Note:
This function is marked as a property and is invoked using the dot notation.
The app path is determined by first checking the _app_path attribute. If it is None,
the function calculates the app path based on the current request and other settings.
If the USE_I18N setting is True, the function tries to get the page path based on the language
extracted from the current request. Otherwise, it uses the default LANGUAGE_CODE setting.
If a valid page path is found, the app path is determined by removing the page path from the
viewed page path (see :attr:`request_path`). If no page path is found, the app path is the
same as the viewed page path.
"""
if self._app_path is None:
if settings.USE_I18N:
page_path = self.get_page_path(self.request_language)
else:
page_path = self.get_page_path(settings.LANGUAGE_CODE)
if page_path:
self._app_path = self.request_path[len(page_path):]
else: # pragma: no cover
# Defensive fallback: ``get_page_path`` always returns a
# non-empty path, so this branch is effectively unreachable.
self._app_path = self.request_path
return self._app_path
def get_page_path(self, lang):
"""
Get the path of a page for a specific language.
Args:
self (object): The object containing the function.
lang (str): The language code of the desired language.
Returns:
str: The path of the page corresponding to the input language.
Notes:
This function relies on the presence of a 'current_page' attribute in the 'request' object.
Raises:
None.
"""
page = getattr(self.request, 'current_page', None)
if not page:
return '/%s/' % lang if settings.USE_I18N else '/'
url = page.get_absolute_url(lang, fallback=False)
if url:
return url
site_id = page.site_id
if is_valid_site_language(lang, site_id=site_id):
_valid_language = True
_hide_untranslated = hide_untranslated(lang, site_id)
else:
_valid_language = False
_hide_untranslated = False
if _hide_untranslated and settings.USE_I18N:
return '/%s/' % lang
default_language = get_default_language_for_site(site_id)
if not _valid_language:
# The request language is not configured for the current site.
# Fallback to the default language configured for the current site.
url = page.get_absolute_url(default_language, fallback=False)
if url:
return url
if _valid_language:
fallbacks = get_fallback_languages(lang, site_id=site_id) or []
fallbacks = [_lang for _lang in fallbacks if _lang in page.get_languages()]
else:
fallbacks = []
if fallbacks:
return page.get_absolute_url(fallbacks[0], fallback=False)
return '/%s/' % lang if settings.USE_I18N else '/'
def __call__(self, lang):
"""
Call the function with the given language parameter.
Args:
lang (str): The language parameter to be passed to the function.
Returns:
str: The URL corresponding to the given language.
Notes:
This function is part of a class and is intended to be used as a callable.
Raises:
NoReverseMatch: If there is no matching URL for the given language.
TypeError: If there is a type error when trying to get the absolute URL.
"""
with force_language(self.request_language):
try:
view = resolve(self.request_path)
except (NoReverseMatch, Resolver404): # NOQA
view = None
if (
hasattr(self.request, 'toolbar') and
self.request.toolbar.obj
):
# Toolbar object
obj = self.request.toolbar.obj
if self.request.toolbar.edit_mode_active:
lang_obj = get_object_for_language(obj, lang, latest=True)
return '' if lang_obj is None else get_object_edit_url(lang_obj, language=lang)
if self.request.toolbar.preview_mode_active:
lang_obj = get_object_for_language(obj, lang, latest=True)
return '' if lang_obj is None else get_object_preview_url(lang_obj, language=lang)
if hasattr(obj, 'get_absolute_url'):
try:
# First see, if object can get language-specific absolute urls (like PageContent)
return obj.get_absolute_url(language=lang)
except (TypeError, NoReverseMatch):
# Object's get_absolute_url does not accept language parameter, set the language
with force_language(lang):
try:
return obj.get_absolute_url()
except NoReverseMatch:
# Fall back to view
pass
if view and view.url_name not in ('pages-details-by-slug', 'pages-root'):
view_name = view.url_name
if view.namespace:
view_name = f"{view.namespace}:{view_name}"
with force_language(lang):
try:
url = reverse(view_name, args=view.args, kwargs=view.kwargs, current_app=view.app_name)
except NoReverseMatch:
url = None
if url:
return url
return f"{self.get_page_path(lang)}{self.app_path}"