diff --git a/AUTHORS.md b/AUTHORS.md index f317f07571b1081e5088f2ce7e97b729cb7754a8..c3bac5369eb20699f6ec14be7a99bf66bae34137 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -7,4 +7,4 @@ Val Kneeman under the name [django-menuware](https://github.com/un33k/django-men * Milton Lenis - miltonln04@gmail.com ## Contributors: -None yet. Why not be the first? +* Jonathan Weth - dev@jonathanweth.de \ No newline at end of file diff --git a/docs/authors.rst b/docs/authors.rst index 825ff2551d3c7ee11d769e26423a08575a61d319..c2c84fd3ee82beed38b885e56abbabc37be2786c 100644 --- a/docs/authors.rst +++ b/docs/authors.rst @@ -4,3 +4,8 @@ Authors `Milton Lenis <https://github.com/MiltonLn>`__ - miltonln04@gmail.com `Juan Diego GarcÃa <https://github.com/yamijuan>`__ - juandgoc@gmail.com + +Contributors +============ + +`Jonathan Weth <https://github.com/hansegucker>`__ - dev@jonathanweth.de diff --git a/docs/menugeneration.rst b/docs/menugeneration.rst index a8d035113fb697b59d52b7327d0c17ebba6525dd..32c8ee9ec49a44f4e57b2090030054b3ae4967a9 100644 --- a/docs/menugeneration.rst +++ b/docs/menugeneration.rst @@ -10,6 +10,7 @@ Django Menu Generator uses python dictionaries to represent the menu items, usua "icon_class": 'some icon class', "url": URL spec, "root": True | False, + "related_urls": [ list of related URLs ], "validators": [ list of validators ], "submenu": Dictionary like this } @@ -22,6 +23,8 @@ Where each key is as follows: - ``url``: See :doc:`urls` +- ``related_urls``: If one of this URLs is part of the path on the currently opened page, the menu item will be marked as selected (format of URLs like described at :doc:`urls`) + - ``root``: A flag to indicate this item is the root of a path, with this you can correctly mark nested menus as selected. - ``validators``: See :doc:`validators` diff --git a/menu_generator/menu.py b/menu_generator/menu.py index 38111bdb4838c17bb0c50723ebe003c3839b7c8b..276d76e671efc97fb0d429af02821e7534c7511d 100755 --- a/menu_generator/menu.py +++ b/menu_generator/menu.py @@ -2,7 +2,7 @@ import copy import django from django.core.exceptions import ImproperlyConfigured -from .utils import get_callable +from .utils import get_callable, parse_url if django.VERSION >= (1, 10): # pragma: no cover from django.urls import reverse, NoReverseMatch @@ -74,22 +74,33 @@ class MenuBase(object): Given a menu item dictionary, it returns the URL or an empty string. """ url = item_dict.get('url', '') - try: - final_url = reverse(**url) if type(url) is dict else reverse(url) - except NoReverseMatch: - final_url = url - return final_url + return parse_url(url) + + def _get_related_urls(self, item_dict): + """ + Given a menu item dictionary, it returns the relateds URLs or an empty list. + """ + related_urls = item_dict.get('related_urls', []) + return [parse_url(url) for url in related_urls] def _is_selected(self, item_dict): """ Given a menu item dictionary, it returns true if `url` is on path, unless the item is marked as a root, in which case returns true if `url` is part of path. + + If related URLS are given, it also returns true if one of the related URLS is part of path. """ url = self._get_url(item_dict) - if self._is_root(item_dict): - return url in self.path + if self._is_root(item_dict) and url in self.path: + return True + elif url == self.path: + return True else: - return url == self.path + # Go through all related URLs and test + for related_url in self._get_related_urls(item_dict): + if related_url in self.path: + return True + return False def _is_root(self, item_dict): """ diff --git a/menu_generator/tests/test_menu.py b/menu_generator/tests/test_menu.py index b310b2c63fe66f45af78be37b2eb20fc0b787931..7128d1306cddf2b0eda483a1f923b7ce6ff09936 100755 --- a/menu_generator/tests/test_menu.py +++ b/menu_generator/tests/test_menu.py @@ -261,3 +261,48 @@ class MenuTestCase(TestCase): nav = self.menu.generate_menu(list_dict) self.assertEqual(len(nav), 1) self.assertIsNone(nav[0]["submenu"]) + + def test_generate_menu_selected_related_urls_simple(self): + self.request.user = TestUser(authenticated=True) + self.request.path = "/persons/1/" + self.menu.save_user_state(self.request) + list_dict = [ + { + "name": "parent1", + "url": "/user/account/", + "related_urls": ["/persons/", "/groups/"], + } + ] + nav = self.menu.generate_menu(list_dict) + + self.assertEqual(len(nav), 1) + self.assertEqual(nav[0]["selected"], True) + + def test_generate_menu_selected_related_urls_submenu(self): + self.request.user = TestUser(authenticated=True) + self.request.path = "/persons/1/" + self.menu.save_user_state(self.request) + list_dict = [ + { + "name": "parent1", + "url": "/user/account/", + "submenu": [ + { + "name": "child1", + "url": '/user/account/profile/', + "related_urls": ["/persons/"] + }, + { + "name": "child2", + "url": 'named_url', + "related_urls": ["/groups/"] + }, + ], + } + ] + nav = self.menu.generate_menu(list_dict) + + self.assertEqual(len(nav), 1) + self.assertEqual(nav[0]["selected"], True) + self.assertEqual(nav[0]["submenu"][0]["selected"], True) + self.assertEqual(nav[0]["submenu"][1]["selected"], False) \ No newline at end of file diff --git a/menu_generator/utils.py b/menu_generator/utils.py index d761bdc5f5f64ef76e7efc969a191a7f92811f8c..c238b9d4276a4ffd361d131b1405ce52b5a3103a 100755 --- a/menu_generator/utils.py +++ b/menu_generator/utils.py @@ -1,8 +1,13 @@ from importlib import import_module +import django from django.apps import apps from django.core.exceptions import ImproperlyConfigured +if django.VERSION >= (1, 10): # pragma: no cover + from django.urls import reverse, NoReverseMatch +else: + from django.core.urlresolvers import reverse, NoReverseMatch def get_callable(func_or_path): """ @@ -35,3 +40,14 @@ def clean_app_config(app_path): "The application {0} is not in the configured apps or does" + "not have the pattern app.apps.AppConfig".format(app_path) ) + + +def parse_url(url): + """ + Returns concrete URL for a menu dict URL attribute. + """ + try: + final_url = reverse(**url) if type(url) is dict else reverse(url) + except NoReverseMatch: + final_url = url + return final_url \ No newline at end of file