views: Implement retrieving breadcrumbs of a page

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: Veiko Aasa <veiko17@disroot.org>
This commit is contained in:
Sunil Mohan Adapa 2024-12-22 19:48:25 -08:00 committed by Veiko Aasa
parent 86031d25f1
commit a29fb97dd9
No known key found for this signature in database
GPG Key ID: 478539CAE680674E
3 changed files with 119 additions and 3 deletions

View File

@ -3,13 +3,20 @@
Django URL patterns for running tests.
"""
from django.urls import re_path
from django.urls import include, re_path
from django.views.generic import TemplateView
_test_view = TemplateView.as_view(template_name='index.html')
app_urls = [
re_path(r'^apps/testapp/$', _test_view, name='index'),
re_path(r'^apps/testapp/create/$', _test_view, name='create'),
]
urlpatterns = [
re_path(r'^$', _test_view, name='index'),
re_path(r'^apps/$', _test_view, name='apps'),
re_path(r'', include((app_urls, 'testapp'))),
re_path(r'^sys/$', _test_view, name='system'),
re_path(r'^test/(?P<a>\d+)/(?P<b>\d+)/(?P<c>\d+)/$', _test_view,
name='test'),

View File

@ -4,8 +4,69 @@ Tests for common FreedomBox views.
"""
import pytest
from django.urls import resolve
from plinth.views import is_safe_url
from plinth import menu as menu_module
from plinth.views import get_breadcrumbs, is_safe_url
@pytest.fixture(name='menu')
def fixture_menu():
"""Initialized menu module."""
menu_module.Menu._all_menus = set()
menu_module.init()
menu_module.Menu('home-id', name='Home', url_name='index')
menu_module.Menu('apps-id', name='Apps', url_name='apps',
parent_url_name='index')
menu_module.Menu('testapp-id', name='Test App', url_name='testapp:index',
parent_url_name='apps')
def test_get_breadcrumbs(rf, menu):
"""Test that computing breadcrumbs works."""
def _crumb(name: str, is_active: bool = False, url_name: str | None = None,
is_active_section: bool = False):
crumb = {'name': name, 'is_active': is_active, 'url_name': url_name}
if is_active_section:
crumb['is_active_section'] = True
return crumb
def _get(path: str):
request = rf.get(path)
request.resolver_match = resolve(path)
return get_breadcrumbs(request)
def _compare(dict1: dict[str, dict[str, str | bool]],
dict2: dict[str, dict[str, str | bool]]):
"""Compare dictionaries with order."""
assert list(dict1.items()) == list(dict2.items())
_compare(_get('/'), {'/': _crumb('Home', True, 'index', True)})
_compare(
_get('/apps/'), {
'/apps/': _crumb('Apps', True, 'apps', True),
'/': _crumb('Home', False, 'index'),
})
_compare(
_get('/apps/testapp/'), {
'/apps/testapp/': _crumb('Test App', True, 'testapp:index'),
'/apps/': _crumb('Apps', False, 'apps', True),
'/': _crumb('Home', False, 'index'),
})
_compare(
_get('/apps/testapp/create/'), {
'/apps/testapp/create/': _crumb('Here', True, 'testapp:create'),
'/apps/testapp/': _crumb('Test App', False, 'testapp:index'),
'/apps/': _crumb('Apps', False, 'apps', True),
'/': _crumb('Home', False, 'index'),
})
_compare(
_get('/test/1/2/3/'), {
'/test/1/2/3/': _crumb('Here', True, 'test', True),
'/': _crumb('Home', False, 'index'),
})
@pytest.mark.parametrize('url', [

View File

@ -11,7 +11,8 @@ import urllib.parse
from django.contrib import messages
from django.core.exceptions import ImproperlyConfigured
from django.forms import Form
from django.http import Http404, HttpResponseBadRequest, HttpResponseRedirect
from django.http import (Http404, HttpRequest, HttpResponseBadRequest,
HttpResponseRedirect)
from django.shortcuts import redirect
from django.template.response import TemplateResponse
from django.urls import reverse
@ -62,6 +63,53 @@ def is_safe_url(url):
return True
def get_breadcrumbs(request: HttpRequest) -> dict[str, dict[str, str | bool]]:
"""Return all the URL ancestors that can be show as breadcrumbs."""
breadcrumbs = {}
def _add(url: str, name: str, url_name: str | None = None):
"""Add item into the breadcrumb dictionary."""
breadcrumbs[url] = {
'name': name,
'is_active': request.path == url,
'url_name': url_name
}
url_name = request.resolver_match.url_name
full_url_name = ':'.join(request.resolver_match.app_names + [url_name])
try:
menu_item = menu.Menu.get_with_url_name(full_url_name)
except LookupError:
# There is no menu entry for this page, find it's app.
_add(request.path, _('Here'), full_url_name)
app_url_name = ':'.join(request.resolver_match.app_names + ['index'])
try:
menu_item = menu.Menu.get_with_url_name(app_url_name)
except LookupError:
# Don't know which app this page belongs to, assume parent is Home.
menu_item = menu.Menu.get_with_url_name('index')
for _number in range(10):
_add(menu_item.url, menu_item.name, menu_item.url_name)
if not menu_item.parent_url_name:
# We have reached the top
break
menu_item = menu.Menu.get_with_url_name(menu_item.parent_url_name)
else:
# Too much hierarchy, we must be in a recursive loop.
breadcrumbs = {}
menu_item = menu.Menu.get_with_url_name('index')
_add(menu_item.url, menu_item.name, menu_item.url_name)
# Find the active section: 'index', 'apps', 'system' or 'help'.
active_section_index = -2 if len(breadcrumbs) >= 2 else -1
active_section_key = list(breadcrumbs.keys())[active_section_index]
breadcrumbs[active_section_key]['is_active_section'] = True
return breadcrumbs
def messages_error(request, message, exception):
"""Show an error message using Django messages framework.