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:
8
.vscode/extensions.json
vendored
Normal file
8
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"ms-python.python",
|
||||
"njpwerner.autodocstring",
|
||||
"streetsidesoftware.code-spell-checker-australian-english",
|
||||
"streetsidesoftware.code-spell-checker",
|
||||
]
|
||||
}
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -1,4 +1,7 @@
|
||||
{
|
||||
"gitlab.aiAssistedCodeSuggestions.enabled": false,
|
||||
"gitlab.duoChat.enabled": false,
|
||||
"cSpell.enableFiletypes": [
|
||||
"!python"
|
||||
],
|
||||
}
|
@ -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.
|
||||
|
91
itsm/itsm/context_processors.py
Normal file
91
itsm/itsm/context_processors.py
Normal 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)
|
||||
}
|
@ -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',
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -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")),
|
||||
]
|
||||
|
@ -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;
|
||||
|
||||
}
|
@ -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()),
|
||||
]
|
||||
|
@ -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>
|
||||
|
29
itsm/templates/navigation.html.j2
Normal file
29
itsm/templates/navigation.html.j2
Normal 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 %}
|
Reference in New Issue
Block a user