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}"