feat(settings): New model to allow adding templated links to devices and software
!43 #6
This commit is contained in:
@ -1,9 +1,12 @@
|
|||||||
|
from django.template import Template, Context
|
||||||
|
from django.utils.html import escape
|
||||||
from django.views import generic
|
from django.views import generic
|
||||||
|
|
||||||
from access.mixin import OrganizationPermission
|
from access.mixin import OrganizationPermission
|
||||||
|
|
||||||
from core.exceptions import MissingAttribute
|
from core.exceptions import MissingAttribute
|
||||||
|
|
||||||
|
from settings.models.external_link import ExternalLink
|
||||||
from settings.models.user_settings import UserSettings
|
from settings.models.user_settings import UserSettings
|
||||||
|
|
||||||
|
|
||||||
@ -50,6 +53,61 @@ class ChangeView(View, generic.UpdateView):
|
|||||||
|
|
||||||
template_name:str = 'form.html.j2'
|
template_name:str = 'form.html.j2'
|
||||||
|
|
||||||
|
# ToDo: on migrating all views to seperate display and change views, external_links will not be required in `ChangView`
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
""" Get template context
|
||||||
|
|
||||||
|
For items that have the ability to have external links, this function
|
||||||
|
adds the external link details to the context.
|
||||||
|
|
||||||
|
!!! Danger "Requirement"
|
||||||
|
This function may be overridden with the caveat that this function is still called.
|
||||||
|
by the overriding function. i.e. `super().get_context_data(skwargs)`
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
The adding of `external_links` within this view is scheduled to be removed.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(dict): Context for the template to use inclusive of 'external_links'
|
||||||
|
"""
|
||||||
|
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
external_links_query = None
|
||||||
|
|
||||||
|
|
||||||
|
if self.model._meta.model_name == 'device':
|
||||||
|
|
||||||
|
external_links_query = ExternalLink.objects.filter(devices=True)
|
||||||
|
|
||||||
|
elif self.model._meta.model_name == 'software':
|
||||||
|
|
||||||
|
external_links_query = ExternalLink.objects.filter(software=True)
|
||||||
|
|
||||||
|
|
||||||
|
if external_links_query:
|
||||||
|
|
||||||
|
external_links: list = []
|
||||||
|
|
||||||
|
user_context = Context(context)
|
||||||
|
|
||||||
|
for external_link in external_links_query:
|
||||||
|
|
||||||
|
user_string = Template(external_link)
|
||||||
|
external_link_context: dict = {
|
||||||
|
'name': escape(external_link.name),
|
||||||
|
'link': escape(user_string.render(user_context)),
|
||||||
|
}
|
||||||
|
|
||||||
|
if external_link.colour:
|
||||||
|
|
||||||
|
external_link_context.update({'colour': external_link.colour })
|
||||||
|
external_links += [ external_link_context ]
|
||||||
|
|
||||||
|
context['external_links'] = external_links
|
||||||
|
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
class DeleteView(OrganizationPermission, generic.DeleteView):
|
class DeleteView(OrganizationPermission, generic.DeleteView):
|
||||||
@ -64,6 +122,60 @@ class DisplayView(OrganizationPermission, generic.DetailView):
|
|||||||
template_name:str = 'form.html.j2'
|
template_name:str = 'form.html.j2'
|
||||||
|
|
||||||
|
|
||||||
|
# ToDo: on migrating all views to seperate display and change views, external_links will not be required in `ChangView`
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
""" Get template context
|
||||||
|
|
||||||
|
For items that have the ability to have external links, this function
|
||||||
|
adds the external link details to the context.
|
||||||
|
|
||||||
|
!!! Danger "Requirement"
|
||||||
|
This function may be overridden with the caveat that this function is still called.
|
||||||
|
by the overriding function. i.e. `super().get_context_data(skwargs)`
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(dict): Context for the template to use inclusive of 'external_links'
|
||||||
|
"""
|
||||||
|
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
external_links_query = None
|
||||||
|
|
||||||
|
|
||||||
|
if self.model._meta.model_name == 'device':
|
||||||
|
|
||||||
|
external_links_query = ExternalLink.objects.filter(devices=True)
|
||||||
|
|
||||||
|
elif self.model._meta.model_name == 'software':
|
||||||
|
|
||||||
|
external_links_query = ExternalLink.objects.filter(software=True)
|
||||||
|
|
||||||
|
|
||||||
|
if external_links_query:
|
||||||
|
|
||||||
|
external_links: list = []
|
||||||
|
|
||||||
|
user_context = Context(context)
|
||||||
|
|
||||||
|
for external_link in external_links_query:
|
||||||
|
|
||||||
|
user_string = Template(external_link)
|
||||||
|
external_link_context: dict = {
|
||||||
|
'name': escape(external_link.name),
|
||||||
|
'link': escape(user_string.render(user_context)),
|
||||||
|
}
|
||||||
|
|
||||||
|
if external_link.colour:
|
||||||
|
|
||||||
|
external_link_context.update({'colour': external_link.colour })
|
||||||
|
external_links += [ external_link_context ]
|
||||||
|
|
||||||
|
context['external_links'] = external_links
|
||||||
|
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class IndexView(View, generic.ListView):
|
class IndexView(View, generic.ListView):
|
||||||
|
|
||||||
|
@ -41,6 +41,8 @@ class View(OrganizationPermission, generic.View):
|
|||||||
|
|
||||||
from config_management.models.groups import ConfigGroups
|
from config_management.models.groups import ConfigGroups
|
||||||
|
|
||||||
|
from settings.models.external_link import ExternalLink
|
||||||
|
|
||||||
if not hasattr(self, 'model'):
|
if not hasattr(self, 'model'):
|
||||||
|
|
||||||
match self.kwargs['model_name']:
|
match self.kwargs['model_name']:
|
||||||
@ -61,6 +63,10 @@ class View(OrganizationPermission, generic.View):
|
|||||||
|
|
||||||
self.model = DeviceType
|
self.model = DeviceType
|
||||||
|
|
||||||
|
case 'externallink':
|
||||||
|
|
||||||
|
self.model = ExternalLink
|
||||||
|
|
||||||
case 'manufacturer':
|
case 'manufacturer':
|
||||||
|
|
||||||
self.model = Manufacturer
|
self.model = Manufacturer
|
||||||
|
@ -83,7 +83,9 @@
|
|||||||
<div id="Details" class="tabcontent">
|
<div id="Details" class="tabcontent">
|
||||||
<h3>
|
<h3>
|
||||||
Details
|
Details
|
||||||
<span style="font-weight: normal; float: right;">{% include 'icons/issue_link.html.j2' with issue=6 %}</span>
|
{% for external_link in external_links %}
|
||||||
|
<span style="font-weight: normal; float: right;">{% include 'icons/external_link.html.j2' with external_link=external_link %}</span>
|
||||||
|
{% endfor %}
|
||||||
</h3>
|
</h3>
|
||||||
<div style="align-items:flex-start; align-content: center; display: flexbox; width: 100%">
|
<div style="align-items:flex-start; align-content: center; display: flexbox; width: 100%">
|
||||||
|
|
||||||
|
@ -43,8 +43,12 @@
|
|||||||
|
|
||||||
<form method="post">
|
<form method="post">
|
||||||
<div id="Details" class="tabcontent">
|
<div id="Details" class="tabcontent">
|
||||||
<h3>Details</h3>
|
<h3>
|
||||||
|
Details
|
||||||
|
{% for external_link in external_links %}
|
||||||
|
<span style="font-weight: normal; float: right;">{% include 'icons/external_link.html.j2' with external_link=external_link %}</span>
|
||||||
|
{% endfor %}
|
||||||
|
</h3>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form }}
|
{{ form }}
|
||||||
<br>
|
<br>
|
||||||
|
@ -52,6 +52,7 @@ span.icon-text {
|
|||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
span.icon-text a {
|
span.icon-text a {
|
||||||
@ -142,6 +143,16 @@ span.icon-issue {
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.icon-external-link {
|
||||||
|
height: 30px;
|
||||||
|
line-height: 30px;
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
vertical-align: middle;
|
||||||
|
display: inline-block;
|
||||||
|
width: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
/* .icon {
|
/* .icon {
|
||||||
display: block;
|
display: block;
|
||||||
content: none;
|
content: none;
|
||||||
|
21
app/settings/forms/external_links.py
Normal file
21
app/settings/forms/external_links.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
from django import forms
|
||||||
|
from django.db.models import Q
|
||||||
|
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
from access.models import Organization, TeamUsers
|
||||||
|
|
||||||
|
from core.forms.common import CommonModelForm
|
||||||
|
|
||||||
|
from settings.models.external_link import ExternalLink
|
||||||
|
|
||||||
|
|
||||||
|
class ExternalLinksForm(CommonModelForm):
|
||||||
|
|
||||||
|
prefix = 'external_links'
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
model = ExternalLink
|
37
app/settings/migrations/0002_externallink.py
Normal file
37
app/settings/migrations/0002_externallink.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# Generated by Django 5.0.7 on 2024-07-17 05:02
|
||||||
|
|
||||||
|
import access.fields
|
||||||
|
import access.models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('access', '0001_initial'),
|
||||||
|
('settings', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ExternalLink',
|
||||||
|
fields=[
|
||||||
|
('is_global', models.BooleanField(default=False)),
|
||||||
|
('model_notes', models.TextField(blank=True, default=None, null=True, verbose_name='Notes')),
|
||||||
|
('id', models.AutoField(primary_key=True, serialize=False, unique=True)),
|
||||||
|
('name', models.CharField(help_text='Name to display on link button', max_length=30, unique=True, verbose_name='Button Name')),
|
||||||
|
('template', models.CharField(help_text='External Link template', max_length=180, verbose_name='Link Template')),
|
||||||
|
('colour', models.CharField(blank=True, default=None, help_text='Colour to render the link button. Use HTML colour code', max_length=80, null=True, verbose_name='Button Colour')),
|
||||||
|
('devices', models.BooleanField(default=False, help_text='Render link for devices', verbose_name='Devices')),
|
||||||
|
('software', models.BooleanField(default=False, help_text='Render link for software', verbose_name='Software')),
|
||||||
|
('created', access.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False)),
|
||||||
|
('modified', access.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False)),
|
||||||
|
('organization', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists])),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
0
app/settings/models/__init__.py
Normal file
0
app/settings/models/__init__.py
Normal file
66
app/settings/models/external_link.py
Normal file
66
app/settings/models/external_link.py
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
from django.template import Template
|
||||||
|
|
||||||
|
from access.fields import *
|
||||||
|
from access.models import TenancyObject
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ExternalLink(TenancyObject):
|
||||||
|
|
||||||
|
id = models.AutoField(
|
||||||
|
primary_key=True,
|
||||||
|
unique=True,
|
||||||
|
blank=False
|
||||||
|
)
|
||||||
|
|
||||||
|
name = models.CharField(
|
||||||
|
blank = False,
|
||||||
|
max_length = 30,
|
||||||
|
unique = True,
|
||||||
|
help_text = 'Name to display on link button',
|
||||||
|
verbose_name = 'Button Name',
|
||||||
|
)
|
||||||
|
|
||||||
|
slug = None
|
||||||
|
|
||||||
|
template = models.CharField(
|
||||||
|
blank = False,
|
||||||
|
max_length = 180,
|
||||||
|
unique = False,
|
||||||
|
help_text = 'External Link template',
|
||||||
|
verbose_name = 'Link Template',
|
||||||
|
)
|
||||||
|
|
||||||
|
colour = models.CharField(
|
||||||
|
blank = True,
|
||||||
|
null = True,
|
||||||
|
default = None,
|
||||||
|
max_length = 80,
|
||||||
|
unique = False,
|
||||||
|
help_text = 'Colour to render the link button. Use HTML colour code',
|
||||||
|
verbose_name = 'Button Colour',
|
||||||
|
)
|
||||||
|
|
||||||
|
devices = models.BooleanField(
|
||||||
|
default = False,
|
||||||
|
blank = False,
|
||||||
|
help_text = 'Render link for devices',
|
||||||
|
verbose_name = 'Devices',
|
||||||
|
)
|
||||||
|
|
||||||
|
software = models.BooleanField(
|
||||||
|
default = False,
|
||||||
|
blank = False,
|
||||||
|
help_text = 'Render link for software',
|
||||||
|
verbose_name = 'Software',
|
||||||
|
)
|
||||||
|
|
||||||
|
created = AutoCreatedField()
|
||||||
|
|
||||||
|
modified = AutoLastModifiedField()
|
||||||
|
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
""" Return the Template to render """
|
||||||
|
|
||||||
|
return str(self.template)
|
18
app/settings/templates/icons/external_link.html.j2
Normal file
18
app/settings/templates/icons/external_link.html.j2
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<style>
|
||||||
|
.inner-text {
|
||||||
|
background-color: #fff;
|
||||||
|
border-top-right-radius: 15px;
|
||||||
|
border-bottom-right-radius: 15px;
|
||||||
|
border-right: 15px;
|
||||||
|
margin-right: -5px;
|
||||||
|
padding: 1px 5px 1px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<span class="icon-text external-link" style="background-color: {% if external_link.colour %}{{ external_link.colour }}{% else %}#177ee6{% endif %};">
|
||||||
|
<span class="icon-external-link" style="margin-left: 5px;">
|
||||||
|
{% include 'icons/link.svg' %}
|
||||||
|
</span>
|
||||||
|
<a class="inner-text" href="{{ external_link.link }}" target="_blank"> {{ external_link.name }}</a>
|
||||||
|
</span>
|
1
app/settings/templates/icons/link.svg
Normal file
1
app/settings/templates/icons/link.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M10.59,13.41C11,13.8 11,14.44 10.59,14.83C10.2,15.22 9.56,15.22 9.17,14.83C7.22,12.88 7.22,9.71 9.17,7.76V7.76L12.71,4.22C14.66,2.27 17.83,2.27 19.78,4.22C21.73,6.17 21.73,9.34 19.78,11.29L18.29,12.78C18.3,11.96 18.17,11.14 17.89,10.36L18.36,9.88C19.54,8.71 19.54,6.81 18.36,5.64C17.19,4.46 15.29,4.46 14.12,5.64L10.59,9.17C9.41,10.34 9.41,12.24 10.59,13.41M13.41,9.17C13.8,8.78 14.44,8.78 14.83,9.17C16.78,11.12 16.78,14.29 14.83,16.24V16.24L11.29,19.78C9.34,21.73 6.17,21.73 4.22,19.78C2.27,17.83 2.27,14.66 4.22,12.71L5.71,11.22C5.7,12.04 5.83,12.86 6.11,13.65L5.64,14.12C4.46,15.29 4.46,17.19 5.64,18.36C6.81,19.54 8.71,19.54 9.88,18.36L13.41,14.83C14.59,13.66 14.59,11.76 13.41,10.59C13,10.2 13,9.56 13.41,9.17Z" /></svg>
|
After Width: | Height: | Size: 795 B |
194
app/settings/templates/settings/external_link.html.j2
Normal file
194
app/settings/templates/settings/external_link.html.j2
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
{% extends 'base.html.j2' %}
|
||||||
|
|
||||||
|
{% load markdown %}
|
||||||
|
|
||||||
|
{% block title %}{{ externallink.name }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
function openCity(evt, cityName) {
|
||||||
|
// Declare all variables
|
||||||
|
var i, tabcontent, tablinks;
|
||||||
|
|
||||||
|
// Get all elements with class="tabcontent" and hide them
|
||||||
|
tabcontent = document.getElementsByClassName("tabcontent");
|
||||||
|
for (i = 0; i < tabcontent.length; i++) {
|
||||||
|
tabcontent[i].style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all elements with class="tablinks" and remove the class "active"
|
||||||
|
tablinks = document.getElementsByClassName("tablinks");
|
||||||
|
for (i = 0; i < tablinks.length; i++) {
|
||||||
|
tablinks[i].className = tablinks[i].className.replace(" active", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show the current tab, and add an "active" class to the button that opened the tab
|
||||||
|
document.getElementById(cityName).style.display = "block";
|
||||||
|
evt.currentTarget.className += " active";
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="tab">
|
||||||
|
<button onclick="window.location='{% url 'Settings:External Links' %}';"
|
||||||
|
style="vertical-align: middle; padding: auto; margin: 0px">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="25px" viewBox="0 -960 960 960" width="25px"
|
||||||
|
style="vertical-align: middle; margin: 0px; padding: 0px border: none; " fill="#6a6e73">
|
||||||
|
<path d="m313-480 155 156q11 11 11.5 27.5T468-268q-11 11-28 11t-28-11L228-452q-6-6-8.5-13t-2.5-15q0-8 2.5-15t8.5-13l184-184q11-11 27.5-11.5T468-692q11 11 11 28t-11 28L313-480Zm264 0 155 156q11 11 11.5 27.5T732-268q-11 11-28 11t-28-11L492-452q-6-6-8.5-13t-2.5-15q0-8 2.5-15t8.5-13l184-184q11-11 27.5-11.5T732-692q11 11 11 28t-11 28L577-480Z" />
|
||||||
|
</svg> Back to External Links</button>
|
||||||
|
<button id="defaultOpen" class="tablinks" onclick="openCity(event, 'Details')">Details</button>
|
||||||
|
<button id="NotesOpen" class="tablinks" onclick="openCity(event, 'Notes')">Notes</button>
|
||||||
|
</div>
|
||||||
|
<style>
|
||||||
|
|
||||||
|
.detail-view-field {
|
||||||
|
display:unset;
|
||||||
|
height: 30px;
|
||||||
|
line-height: 30px;
|
||||||
|
padding: 0px 20px 40px 20px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-view-field label {
|
||||||
|
display: inline-block;
|
||||||
|
font-weight: bold;
|
||||||
|
width: 200px;
|
||||||
|
margin: 10px;
|
||||||
|
/*padding: 10px;*/
|
||||||
|
height: 30px;
|
||||||
|
line-height: 30px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-view-field span {
|
||||||
|
display: inline-block;
|
||||||
|
width: 340px;
|
||||||
|
margin: 10px;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
height: 30px;
|
||||||
|
line-height: 30px;
|
||||||
|
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<form action="" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
|
||||||
|
<div id="Details" class="tabcontent">
|
||||||
|
<h3>
|
||||||
|
Details
|
||||||
|
{% for external_link in external_links %}
|
||||||
|
<span style="font-weight: normal; float: right;">{% include 'icons/external_link.html.j2' with external_link=external_link %}</span>
|
||||||
|
{% endfor %}
|
||||||
|
</h3>
|
||||||
|
<div style="align-items:flex-start; align-content: center; display: flexbox; width: 100%">
|
||||||
|
|
||||||
|
<div style="display: inline; width: 40%; margin: 30px;">
|
||||||
|
|
||||||
|
<div class="detail-view-field">
|
||||||
|
<label>{{ form.organization.label }}</label>
|
||||||
|
<span>{{ externallink.organization }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-view-field">
|
||||||
|
<label>{{ form.name.label }}</label>
|
||||||
|
<span>{{ form.name.value }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-view-field">
|
||||||
|
<label>{{ form.template.label }}</label>
|
||||||
|
<span>{{ externallink.template }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-view-field">
|
||||||
|
<label>{{ form.colour.label }}</label>
|
||||||
|
<span>
|
||||||
|
{% if form.colour.value %}
|
||||||
|
{{ form.colour.value }}
|
||||||
|
{% else %}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-view-field">
|
||||||
|
<label>{{ form.devices.label }}</label>
|
||||||
|
<span> {{ form.devices.value }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-view-field">
|
||||||
|
<label>{{ form.software.label }}</label>
|
||||||
|
<span>{{ externallink.software }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-view-field">
|
||||||
|
<label>Created</label>
|
||||||
|
<span>{{ externallink.created }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-view-field">
|
||||||
|
<label>Modified</label>
|
||||||
|
<span>{{ externallink.modified }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display: inline; width: 40%; margin: 30px; text-align: left;">
|
||||||
|
<div>
|
||||||
|
<label style="font-weight: bold; width: 100%; border-bottom: 1px solid #ccc; display: block; text-align: inherit;">{{ form.model_notes.label }}</label>
|
||||||
|
|
||||||
|
<div style="display: inline-block; text-align: left;">
|
||||||
|
{% if form.model_notes.value %}
|
||||||
|
{{ form.model_notes.value | markdown | safe }}
|
||||||
|
{% else %}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<input type="button" value="Edit" onclick="window.location='{% url 'Settings:_external_link_change' externallink.id %}';">
|
||||||
|
|
||||||
|
|
||||||
|
{% if not tab %}
|
||||||
|
<script>
|
||||||
|
// Get the element with id="defaultOpen" and click on it
|
||||||
|
document.getElementById("defaultOpen").click();
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div id="Notes" class="tabcontent">
|
||||||
|
<h3>
|
||||||
|
Notes
|
||||||
|
</h3>
|
||||||
|
{{ notes_form }}
|
||||||
|
<input type="submit" name="{{notes_form.prefix}}" value="Submit" />
|
||||||
|
<div class="comments">
|
||||||
|
{% if notes %}
|
||||||
|
{% for note in notes%}
|
||||||
|
{% include 'note.html.j2' %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if tab == 'notes' %}
|
||||||
|
<script>
|
||||||
|
// Get the element with id="defaultOpen" and click on it
|
||||||
|
document.getElementById("NotesOpen").click();
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
42
app/settings/templates/settings/external_links.html.j2
Normal file
42
app/settings/templates/settings/external_links.html.j2
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
{% extends 'base.html.j2' %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block content_header_icon %}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<input type="button" value="New External Link" onclick="window.location='{% url 'Settings:_external_link_add' %}';">
|
||||||
|
<table class="data">
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Organization</th>
|
||||||
|
<th> </th>
|
||||||
|
</tr>
|
||||||
|
{% for item in list %}
|
||||||
|
<tr>
|
||||||
|
<td><a href="{% url 'Settings:_external_link_view' pk=item.id %}">{{ item.name }}</a></td>
|
||||||
|
<td>{% if item.is_global %}Global{% else %}{{ item.organization }}{% endif %}</td>
|
||||||
|
<td> </td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="pagination">
|
||||||
|
<span class="step-links">
|
||||||
|
{% if page_obj.has_previous %}
|
||||||
|
<a href="?page=1">« first</a>
|
||||||
|
<a href="?page={{ page_obj.previous_page_number }}">previous</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<span class="current">
|
||||||
|
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{% if page_obj.has_next %}
|
||||||
|
<a href="?page={{ page_obj.next_page_number }}">next</a>
|
||||||
|
<a href="?page={{ page_obj.paginator.num_pages }}">last »</a>
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
@ -2,7 +2,7 @@ from django.urls import path
|
|||||||
|
|
||||||
from core.views import celery_log
|
from core.views import celery_log
|
||||||
|
|
||||||
from .views import app_settings, home, device_models, device_types, manufacturer, software_categories
|
from settings.views import app_settings, home, device_models, device_types, external_link, manufacturer, software_categories
|
||||||
|
|
||||||
from itam.views import device_type, device_model, software_category
|
from itam.views import device_type, device_model, software_category
|
||||||
|
|
||||||
@ -11,6 +11,13 @@ urlpatterns = [
|
|||||||
|
|
||||||
path("", home.View.as_view(), name="Settings"),
|
path("", home.View.as_view(), name="Settings"),
|
||||||
|
|
||||||
|
path("external_links", external_link.Index.as_view(), name="External Links"),
|
||||||
|
path("external_links/add", external_link.Add.as_view(), name="_external_link_add"),
|
||||||
|
path("external_links/<int:pk>", external_link.View.as_view(), name="_external_link_view"),
|
||||||
|
path("external_links/<int:pk>/edit", external_link.Change.as_view(), name="_external_link_change"),
|
||||||
|
path("external_links/<int:pk>/delete", external_link.Delete.as_view(), name="_external_link_delete"),
|
||||||
|
|
||||||
|
|
||||||
path('application', app_settings.View.as_view(), name="_settings_application"),
|
path('application', app_settings.View.as_view(), name="_settings_application"),
|
||||||
|
|
||||||
path("task_results", celery_log.Index.as_view(), name="_task_results"),
|
path("task_results", celery_log.Index.as_view(), name="_task_results"),
|
||||||
|
164
app/settings/views/external_link.py
Normal file
164
app/settings/views/external_link.py
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
from django.contrib.auth import decorators as auth_decorator
|
||||||
|
from django.db.models import Q
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
|
from django.views import generic
|
||||||
|
|
||||||
|
|
||||||
|
from access.mixin import OrganizationPermission
|
||||||
|
|
||||||
|
from core.views.common import AddView, ChangeView, DeleteView, DisplayView, IndexView
|
||||||
|
|
||||||
|
from settings.forms.external_links import ExternalLinksForm
|
||||||
|
from settings.models.external_link import ExternalLink
|
||||||
|
|
||||||
|
|
||||||
|
class Index(IndexView):
|
||||||
|
|
||||||
|
context_object_name = "list"
|
||||||
|
|
||||||
|
model = ExternalLink
|
||||||
|
|
||||||
|
paginate_by = 10
|
||||||
|
|
||||||
|
permission_required = [
|
||||||
|
'settings.view_externallink'
|
||||||
|
]
|
||||||
|
|
||||||
|
template_name = 'settings/external_links.html.j2'
|
||||||
|
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
context['model_docs_path'] = self.model._meta.app_label + '/external_links/'
|
||||||
|
|
||||||
|
context['content_title'] = 'External Links'
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class View(ChangeView):
|
||||||
|
|
||||||
|
context_object_name = "externallink"
|
||||||
|
|
||||||
|
form_class = ExternalLinksForm
|
||||||
|
|
||||||
|
model = ExternalLink
|
||||||
|
|
||||||
|
permission_required = [
|
||||||
|
'settings.view_externallink',
|
||||||
|
]
|
||||||
|
|
||||||
|
template_name = 'settings/external_link.html.j2'
|
||||||
|
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
context['model_pk'] = self.kwargs['pk']
|
||||||
|
context['model_name'] = self.model._meta.verbose_name.replace(' ', '')
|
||||||
|
|
||||||
|
context['model_delete_url'] = reverse('Settings:_external_link_delete', args=(self.kwargs['pk'],))
|
||||||
|
|
||||||
|
context['content_title'] = self.object.name
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
def get_success_url(self, **kwargs):
|
||||||
|
|
||||||
|
return reverse('Settings:_external_link_view', args={self.kwargs['pk']})
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(auth_decorator.permission_required("settings.change_externallink", raise_exception=True))
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
|
||||||
|
return super().post(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class Change(ChangeView):
|
||||||
|
|
||||||
|
context_object_name = "externallink"
|
||||||
|
|
||||||
|
form_class = ExternalLinksForm
|
||||||
|
|
||||||
|
model = ExternalLink
|
||||||
|
|
||||||
|
permission_required = [
|
||||||
|
'settings.change_externallink',
|
||||||
|
]
|
||||||
|
|
||||||
|
template_name = 'form.html.j2'
|
||||||
|
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
context['content_title'] = self.object.name
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
def get_success_url(self, **kwargs):
|
||||||
|
|
||||||
|
return reverse('Settings:_external_link_view', args={self.kwargs['pk']})
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(auth_decorator.permission_required("settings.change_externallink", raise_exception=True))
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
|
||||||
|
return super().post(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Add(AddView):
|
||||||
|
|
||||||
|
|
||||||
|
form_class = ExternalLinksForm
|
||||||
|
|
||||||
|
model = ExternalLink
|
||||||
|
|
||||||
|
permission_required = [
|
||||||
|
'settings.add_externallink',
|
||||||
|
]
|
||||||
|
|
||||||
|
template_name = 'form.html.j2'
|
||||||
|
|
||||||
|
|
||||||
|
def get_success_url(self, **kwargs):
|
||||||
|
|
||||||
|
return reverse(viewname = 'Settings:External Links')
|
||||||
|
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
context['content_title'] = 'Add External Link'
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class Delete(DeleteView):
|
||||||
|
|
||||||
|
model = ExternalLink
|
||||||
|
|
||||||
|
permission_required = [
|
||||||
|
'settings.delete_externallink',
|
||||||
|
]
|
||||||
|
|
||||||
|
template_name = 'form.html.j2'
|
||||||
|
|
||||||
|
def get_success_url(self, **kwargs):
|
||||||
|
|
||||||
|
return reverse('Settings:External Links')
|
||||||
|
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
context['content_title'] = 'Delete ' + self.object.name
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
title: External Links
|
||||||
|
description: No Fuss Computings Centurion ERP External Links model documentation.
|
||||||
|
date: 2024-07-15
|
||||||
|
template: project.html
|
||||||
|
about: https://gitlab.com/nofusscomputing/infrastructure/configuration-management/centurion_erp
|
||||||
|
---
|
||||||
|
|
||||||
|
This model enables the end user to define external links to be rendered alongside other models display pages. The values are added to the page context in the [Change View](../common_views.md#display-view).
|
||||||
|
|
||||||
|
|
||||||
|
## External Links
|
||||||
|
|
||||||
|
::: app.settings.models.external_link.ExternalLink
|
||||||
|
options:
|
||||||
|
inherited_members: true
|
||||||
|
heading_level: 3
|
@ -34,6 +34,11 @@ All models must meet the following requirements:
|
|||||||
- No `queryset` is to return data that the user has not got access to. _see [queryset()](./api/models/tenancy_object.md#tenancy-object-manager)_
|
- No `queryset` is to return data that the user has not got access to. _see [queryset()](./api/models/tenancy_object.md#tenancy-object-manager)_
|
||||||
|
|
||||||
|
|
||||||
|
## History
|
||||||
|
|
||||||
|
Currently the adding of history to a model is a manual process. edit the file located at `core.views.history` and within `View.get_object` add the model to the `switch` statement.
|
||||||
|
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
The following Unit test cases exists for models:
|
The following Unit test cases exists for models:
|
||||||
|
18
docs/projects/centurion_erp/user/settings/external_links.md
Normal file
18
docs/projects/centurion_erp/user/settings/external_links.md
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
title: External Links
|
||||||
|
description: External Links user documentation for Centurion ERP by No Fuss Computing
|
||||||
|
date: 2024-07-17
|
||||||
|
template: project.html
|
||||||
|
about: https://gitlab.com/nofusscomputing/infrastructure/configuration-management/centurion_erp
|
||||||
|
---
|
||||||
|
|
||||||
|
External Links allow an end user to specify by means of a jinja template a link that when displayed upon an items display page will add a button with a hyperlink to the url provided. External links can be assigned to: devices and software. This includes both at the same time.
|
||||||
|
|
||||||
|
|
||||||
|
## Create a link
|
||||||
|
|
||||||
|
- Software context is under key `software`
|
||||||
|
|
||||||
|
- Device context is under key `device`
|
||||||
|
|
||||||
|
To add a templated link within the `Link Template` field enter your url, with the variable within jinja braces. for example to add a link that will expand with the devices id, specify `{{ device.id }}`. i.e. `https://domainname.tld/{{ device.id }}`. If the link is for software use key `software`. Available fields under context key all of those that are available at the time the page is rendered.
|
@ -82,6 +82,8 @@ nav:
|
|||||||
|
|
||||||
- projects/centurion_erp/development/api/models/core_history_save.md
|
- projects/centurion_erp/development/api/models/core_history_save.md
|
||||||
|
|
||||||
|
- projects/centurion_erp/development/api/models/external_links.md
|
||||||
|
|
||||||
- projects/centurion_erp/development/api/models/itam_device.md
|
- projects/centurion_erp/development/api/models/itam_device.md
|
||||||
|
|
||||||
- projects/centurion_erp/development/api/models/access_organization_permission_checking.md
|
- projects/centurion_erp/development/api/models/access_organization_permission_checking.md
|
||||||
@ -194,6 +196,8 @@ nav:
|
|||||||
|
|
||||||
- projects/centurion_erp/user/settings/app_settings.md
|
- projects/centurion_erp/user/settings/app_settings.md
|
||||||
|
|
||||||
|
- projects/centurion_erp/user/settings/external_links.md
|
||||||
|
|
||||||
|
|
||||||
- Operations:
|
- Operations:
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user