feat: Dyno-magic build navigation from application urls.py

Will use project urls.py to gather application urls.py to build navigation menu.

!1
This commit is contained in:
2024-05-09 06:37:54 +09:30
parent 7cdfdab1fc
commit 71bcd192b3
10 changed files with 264 additions and 42 deletions

8
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,8 @@
{
"recommendations": [
"ms-python.python",
"njpwerner.autodocstring",
"streetsidesoftware.code-spell-checker-australian-english",
"streetsidesoftware.code-spell-checker",
]
}

View File

@ -1,4 +1,7 @@
{
"gitlab.aiAssistedCodeSuggestions.enabled": false,
"gitlab.duoChat.enabled": false,
"cSpell.enableFiletypes": [
"!python"
],
}

View File

@ -5,3 +5,41 @@ date: 2024-04-06
template: project.html
about: https://gitlab.com/nofusscomputing/infrastructure/configuration-management/django_app
---
This Django Project is designed to be a base template for Django applications. It's intent is to contain only the minimal functionality that is/would be common to all Django applications. for instance: base templates, auth and the functions required to make the site navigable.
## Adding an Application
1. Install the django application with `pip <app-name>`
1. Update `itsm.settings.py`
``` python
INSTALLED_APPS = [
'<app name>.apps.<apps.py Class Name>', # Within project directory
'<app name>', # not in project directory
]
```
1. Update `itsm/urls.py`
``` python
urlpatterns = [
path("<url path>/", include("<app name>.urls")),
]
```
!!! tip
No url from the application will be visible without including the `name` parameter when calling the `path` function within the applications `url.py`. i.e. `urlpatterns[].path(name='<Navigation Name>')`. This is by design and provides the option to limit what URL's are displayed within the navigation menu.
Once you have completed the above list, your application will display collapsed within the navigation menu with the name of your application.

View File

@ -0,0 +1,91 @@
import re
from .urls import urlpatterns
from django.urls import URLPattern, URLResolver
def request(request):
return request.get_full_path()
def nav_items(context) -> list(dict()):
""" Fetch All Project URLs
Collect the project URLs for use in creating the site navigation.
The returned list contains a dictionary with the following items:
name: {str} Group Name
urls: {list} List of URLs for the group
is_active: {bool} if any of the links in this group are active
Each group url list item contains a dicionary with the following items:
name: {str} The display name for the link
url: {str} link URL
is_active: {bool} if this link is the active URL
Returns:
_type_: _description_
"""
dnav = []
re_pattern = re.compile('[a-z/0-9]+')
for nav_group in urlpatterns:
group_active = False
nav_items = []
if (
isinstance(nav_group, URLPattern)
):
group_name = str(nav_group.name)
elif (
isinstance(nav_group, URLResolver)
):
if nav_group.app_name is not None:
group_name = str(nav_group.app_name)
for pattern in nav_group.url_patterns:
is_active = False
url = '/' + str(nav_group.pattern) + str(pattern.pattern)
if str(context.path) == url:
is_active = True
if str(context.path).startswith('/' + str(nav_group.pattern)):
group_active = True
if pattern.pattern.name is not None:
name = str(pattern.name)
nav_items = nav_items + [ {
'name': name,
'url': url,
'is_active': is_active
} ]
if len(nav_items) > 0:
dnav = dnav + [{
'name': group_name,
'urls': nav_items,
'is_active': group_active
}]
return dnav
def navigation(context):
return {
'nav_items': nav_items(context)
}

View File

@ -65,6 +65,7 @@ TEMPLATES = [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'itsm.context_processors.navigation',
],
},
},

View File

@ -20,7 +20,6 @@ from django.urls import include, path
from .views import HomeView
urlpatterns = [
path('', HomeView.as_view(), name='home'),
path('admin/', admin.site.urls),
path('', HomeView.as_view()),
path("organization/", include("structure.urls")),
]

View File

@ -41,36 +41,6 @@ header {
vertical-align: middle;
}
nav {
display: flex;
flex-direction: column;
background: #212427;
padding: 20px;
width: 275px;
height: 100%;
position: fixed;
top: 76px;
bottom: 4rem;
left: 0;
overflow-y: auto;
}
nav ul {
list-style-type: none;
padding: 0;
}
nav ul li {
padding: 10px;
}
nav ul li:hover {
border-left-color: #73bcf7;
border-left: 3px solid #73bcf7;
background-color: #666;
}
section {
display: flexbox;
@ -158,4 +128,91 @@ input {
border: none;
border-bottom: 1px solid #b1b1b1;
font-size: inherit;
}
nav {
display: flex;
flex-direction: column;
background: #212427;
padding: 20px;
width: 275px;
height: 100%;
position: fixed;
top: 76px;
bottom: 4rem;
left: 0;
overflow-y: auto;
}
nav button.collapsible {
background-color: inherit;
color: white;
cursor: pointer;
padding: 18px;
width: 100%;
border: none;
text-align: left;
outline: none;
font-size: 15px;
margin: 0px;
}
nav button.active, .collapsible:hover {
font-weight: bold;
}
nav button.collapsible:after {
content: '\002B';
color: white;
float: right;
margin-left: 5px;
}
nav button.active:after {
content: "\2212";
font-weight: bold;
}
nav div.content {
padding: 0px;
max-height: 0;
overflow: hidden;
transition: max-height 0.2s ease-out;
margin: 0px;
}
nav ul {
list-style-type: none;
padding: 0;
margin: 0px;
}
nav ul li {
padding: 10px;
}
nav ul li:hover {
border-left: 3px solid #73bcf7;
background-color: #666;
}
nav ul li.active {
border-left: 3px solid #73bcf7;
}
nav a {
color: #177ee6;
text-decoration: none;
}
nav a:visited {
color: #177ee6;
text-decoration: none;
}

View File

@ -2,8 +2,8 @@ from django.urls import path
from . import views
app_name = "structure"
app_name = "Structure"
urlpatterns = [
path("", views.IndexView.as_view(), name="index"),
path("<int:pk>/", views.OrganizationView.as_view(), name="organization"),
path("", views.IndexView.as_view(), name="Organizations"),
path("<int:pk>/", views.OrganizationView.as_view()),
]

View File

@ -15,7 +15,7 @@
<body>
<header>
<h1>Site Title</h1>
<h1><a href="/" style="text-decoration: none; color: inherit;">Site Title</a></h1>
<div class="dropdown">
<button class="dropbtn">{% block org_name %}Organization{% endblock %}</button>
@ -38,12 +38,8 @@
</header>
<main>
<nav>
<ul>
<li><a href="#">Link 1</a></li>
<li><a href="#">Link 2</a></li>
<li><a href="#">Link 3</a></li>
</ul>
<nav style="padding: 0px;">
{% include 'navigation.html.j2' %}
</nav>
<section>

View File

@ -0,0 +1,29 @@
{% block navigation %}
{% for group in nav_items %}
<button class="collapsible{% if group.is_active %} active{% endif %}">{{ group.name }}</button>
<div class="content"{% if group.is_active %} style="max-height:inherit" {% endif %}>
<ul>
{% for group_urls in group.urls %}
<li{% if group_urls.is_active %} class="active"{% endif %}><a href="{{ group_urls.url }}">{{ group_urls.name }}</a></li>
{% endfor %}
</ul>
</div>
{% endfor %}
<script>
var coll = document.getElementsByClassName("collapsible");
var i;
for (i = 0; i < coll.length; i++) {
coll[i].addEventListener("click", function () {
this.classList.toggle("active");
var content = this.nextElementSibling;
if (content.style.maxHeight) {
content.style.maxHeight = null;
} else {
content.style.maxHeight = content.scrollHeight + "px";
}
});
}
</script>
{% endblock %}