-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a05d40c
commit 0ba5128
Showing
7 changed files
with
187 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -43,6 +43,7 @@ dev = [ | |
"django-stubs-ext", | ||
"hatch", | ||
"mypy", | ||
"model_bakery", | ||
"nox", | ||
"pytest", | ||
"pytest-cov", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
from __future__ import annotations | ||
|
||
from dataclasses import dataclass | ||
from dataclasses import field | ||
|
||
from django.http import HttpRequest | ||
from django.template.loader import render_to_string | ||
from django.urls import reverse | ||
from django.urls.exceptions import NoReverseMatch | ||
|
||
|
||
@dataclass | ||
class Nav: | ||
items: list[NavGroup | NavItem] | ||
template_name: str | ||
|
||
def _get_url(self, url: str) -> str: | ||
try: | ||
return reverse(url) | ||
except NoReverseMatch: | ||
return url | ||
|
||
@classmethod | ||
def render(cls, request: HttpRequest) -> str: | ||
items = [ | ||
item for item in cls.items if cls._check_item_visibility(request, item) | ||
] | ||
return render_to_string( | ||
template_name=cls.template_name, | ||
context={"items": items}, | ||
) | ||
|
||
@classmethod | ||
def _check_item_visibility( | ||
cls, request: HttpRequest, item: NavGroup | NavItem | ||
) -> bool: | ||
if isinstance(item, NavItem): | ||
for idx, perm in enumerate(item.permissions): | ||
user_perm = getattr(request.user, perm, False) | ||
if not user_perm: | ||
return False | ||
if not idx == len(item.permissions) - 1: | ||
continue | ||
elif isinstance(item, NavGroup): | ||
sub_items = [ | ||
sub_item | ||
for sub_item in item.items | ||
if cls._check_item_visibility(request, sub_item) | ||
] | ||
if not sub_items: | ||
return False | ||
|
||
return True | ||
|
||
@staticmethod | ||
def _check_item_active(request: HttpRequest, url: str) -> bool: | ||
return request.path.startswith(url) and url != "/" or request.path == url | ||
|
||
|
||
@dataclass | ||
class NavGroup: | ||
title: str | ||
items: list[NavGroup | NavItem] | ||
url: str | None = None | ||
permissions: list[str] = field(default_factory=list) | ||
active: bool = False | ||
|
||
|
||
@dataclass | ||
class NavItem: | ||
title: str | ||
url: str | ||
permissions: list[str] = field(default_factory=list) | ||
active: bool = False |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
from __future__ import annotations | ||
|
||
from django import template | ||
from django.template import Context | ||
from django.utils.module_loading import import_string | ||
|
||
register = template.Library() | ||
|
||
|
||
@register.simple_tag(takes_context=True) | ||
def simple_nav(context: Context, nav_path: str) -> str: | ||
nav = import_string(nav_path) | ||
return nav.render(request=context["request"]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{% for item in items %} | ||
{% if item.items %} | ||
{% include 'tests/test_nav.html' with items=item.items %} | ||
{% endif %} | ||
<a href="{{ item.url }}">{{ item.title }}</a> | ||
{% endfor %} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
from __future__ import annotations | ||
|
||
import pytest | ||
from django.contrib.auth import get_user_model | ||
from django.http import HttpRequest | ||
from django.utils.module_loading import import_string | ||
from model_bakery import baker | ||
|
||
from simple_nav.nav import Nav | ||
from simple_nav.nav import NavGroup | ||
from simple_nav.nav import NavItem | ||
|
||
pytestmark = pytest.mark.django_db | ||
|
||
|
||
class TestNav(Nav): | ||
items = [ | ||
NavItem(title="Relative URL", url="/relative-url"), | ||
NavItem(title="Absolute URL", url="https://example.com/absolute-url"), | ||
NavGroup( | ||
title="Group", | ||
url="/group", | ||
items=[ | ||
NavItem(title="Relative URL", url="/relative-url"), | ||
NavItem(title="Absolute URL", url="https://example.com/absolute-url"), | ||
], | ||
), | ||
NavItem( | ||
title="is_authenticated Item", url="#", permissions=["is_authenticated"] | ||
), | ||
NavItem(title="is_staff Item", url="#", permissions=["is_staff"]), | ||
NavItem(title="is_superuser Item", url="#", permissions=["is_superuser"]), | ||
NavGroup( | ||
title="is_authenticated Group", | ||
permissions=["is_authenticated"], | ||
items=[NavItem(title="Test Item", url="#")], | ||
), | ||
NavGroup( | ||
title="is_staff Group", | ||
permissions=["is_staff"], | ||
items=[NavItem(title="Test Item", url="#")], | ||
), | ||
NavGroup( | ||
title="is_superuser Group", | ||
permissions=["is_superuser"], | ||
items=[NavItem(title="Test Item", url="#")], | ||
), | ||
] | ||
template_name = "tests/test_nav.html" | ||
|
||
|
||
@pytest.fixture | ||
def req(): | ||
return HttpRequest() | ||
|
||
|
||
@pytest.fixture | ||
def user(): | ||
return baker.make(get_user_model()) | ||
|
||
|
||
def test_dotted_path_loading(): | ||
nav = import_string("tests.test_nav.TestNav") | ||
|
||
assert len(nav.items) == 9 | ||
assert nav.template_name == "tests/test_nav.html" | ||
|
||
|
||
def test_nav_render(req, user): | ||
req.user = user | ||
rendered_template = TestNav.render(req) | ||
|
||
assert "Relative URL" in rendered_template | ||
assert "/relative-url" in rendered_template | ||
assert "Absolute URL" in rendered_template | ||
assert "https://example.com/absolute-url" in rendered_template | ||
assert "Group" in rendered_template | ||
|
||
|
||
def test_dotted_path_rendering(req, user): | ||
req.user = user | ||
nav = import_string("tests.test_nav.TestNav") | ||
|
||
assert nav.render(req) |