Skip to content

Commit 5a4149f

Browse files
authored
Merge pull request #20 from hansegucker/activ-menu-item-related-url
Mark menu items as selected if a related URL is in path
2 parents 7e1c772 + c4b6906 commit 5a4149f

File tree

6 files changed

+90
-10
lines changed

6 files changed

+90
-10
lines changed

AUTHORS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ Val Kneeman under the name [django-menuware](https://github.com/un33k/django-men
77
* Milton Lenis - [email protected]
88

99
## Contributors:
10-
None yet. Why not be the first?
10+
* Jonathan Weth - [email protected]

docs/authors.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,8 @@ Authors
44
`Milton Lenis <https://github.com/MiltonLn>`__ - [email protected]
55

66
`Juan Diego García <https://github.com/yamijuan>`__ - [email protected]
7+
8+
Contributors
9+
============
10+
11+
`Jonathan Weth <https://github.com/hansegucker>`__ - [email protected]

docs/menugeneration.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Django Menu Generator uses python dictionaries to represent the menu items, usua
1010
"icon_class": 'some icon class',
1111
"url": URL spec,
1212
"root": True | False,
13+
"related_urls": [ list of related URLs ],
1314
"validators": [ list of validators ],
1415
"submenu": Dictionary like this
1516
}
@@ -22,6 +23,8 @@ Where each key is as follows:
2223

2324
- ``url``: See :doc:`urls`
2425

26+
- ``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`)
27+
2528
- ``root``: A flag to indicate this item is the root of a path, with this you can correctly mark nested menus as selected.
2629

2730
- ``validators``: See :doc:`validators`

menu_generator/menu.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import django
44
from django.core.exceptions import ImproperlyConfigured
5-
from .utils import get_callable
5+
from .utils import get_callable, parse_url
66

77
if django.VERSION >= (1, 10): # pragma: no cover
88
from django.urls import reverse, NoReverseMatch
@@ -74,22 +74,33 @@ def _get_url(self, item_dict):
7474
Given a menu item dictionary, it returns the URL or an empty string.
7575
"""
7676
url = item_dict.get('url', '')
77-
try:
78-
final_url = reverse(**url) if type(url) is dict else reverse(url)
79-
except NoReverseMatch:
80-
final_url = url
81-
return final_url
77+
return parse_url(url)
78+
79+
def _get_related_urls(self, item_dict):
80+
"""
81+
Given a menu item dictionary, it returns the relateds URLs or an empty list.
82+
"""
83+
related_urls = item_dict.get('related_urls', [])
84+
return [parse_url(url) for url in related_urls]
8285

8386
def _is_selected(self, item_dict):
8487
"""
8588
Given a menu item dictionary, it returns true if `url` is on path,
8689
unless the item is marked as a root, in which case returns true if `url` is part of path.
90+
91+
If related URLS are given, it also returns true if one of the related URLS is part of path.
8792
"""
8893
url = self._get_url(item_dict)
89-
if self._is_root(item_dict):
90-
return url in self.path
94+
if self._is_root(item_dict) and url in self.path:
95+
return True
96+
elif url == self.path:
97+
return True
9198
else:
92-
return url == self.path
99+
# Go through all related URLs and test
100+
for related_url in self._get_related_urls(item_dict):
101+
if related_url in self.path:
102+
return True
103+
return False
93104

94105
def _is_root(self, item_dict):
95106
"""

menu_generator/tests/test_menu.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,3 +261,48 @@ def test_generate_menu_no_visible_submenu(self):
261261
nav = self.menu.generate_menu(list_dict)
262262
self.assertEqual(len(nav), 1)
263263
self.assertIsNone(nav[0]["submenu"])
264+
265+
def test_generate_menu_selected_related_urls_simple(self):
266+
self.request.user = TestUser(authenticated=True)
267+
self.request.path = "/persons/1/"
268+
self.menu.save_user_state(self.request)
269+
list_dict = [
270+
{
271+
"name": "parent1",
272+
"url": "/user/account/",
273+
"related_urls": ["/persons/", "/groups/"],
274+
}
275+
]
276+
nav = self.menu.generate_menu(list_dict)
277+
278+
self.assertEqual(len(nav), 1)
279+
self.assertEqual(nav[0]["selected"], True)
280+
281+
def test_generate_menu_selected_related_urls_submenu(self):
282+
self.request.user = TestUser(authenticated=True)
283+
self.request.path = "/persons/1/"
284+
self.menu.save_user_state(self.request)
285+
list_dict = [
286+
{
287+
"name": "parent1",
288+
"url": "/user/account/",
289+
"submenu": [
290+
{
291+
"name": "child1",
292+
"url": '/user/account/profile/',
293+
"related_urls": ["/persons/"]
294+
},
295+
{
296+
"name": "child2",
297+
"url": 'named_url',
298+
"related_urls": ["/groups/"]
299+
},
300+
],
301+
}
302+
]
303+
nav = self.menu.generate_menu(list_dict)
304+
305+
self.assertEqual(len(nav), 1)
306+
self.assertEqual(nav[0]["selected"], True)
307+
self.assertEqual(nav[0]["submenu"][0]["selected"], True)
308+
self.assertEqual(nav[0]["submenu"][1]["selected"], False)

menu_generator/utils.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
from importlib import import_module
22

3+
import django
34
from django.apps import apps
45
from django.core.exceptions import ImproperlyConfigured
56

7+
if django.VERSION >= (1, 10): # pragma: no cover
8+
from django.urls import reverse, NoReverseMatch
9+
else:
10+
from django.core.urlresolvers import reverse, NoReverseMatch
611

712
def get_callable(func_or_path):
813
"""
@@ -35,3 +40,14 @@ def clean_app_config(app_path):
3540
"The application {0} is not in the configured apps or does" +
3641
"not have the pattern app.apps.AppConfig".format(app_path)
3742
)
43+
44+
45+
def parse_url(url):
46+
"""
47+
Returns concrete URL for a menu dict URL attribute.
48+
"""
49+
try:
50+
final_url = reverse(**url) if type(url) is dict else reverse(url)
51+
except NoReverseMatch:
52+
final_url = url
53+
return final_url

0 commit comments

Comments
 (0)