Merge branch 'development' into 'master'
chore: release See merge request nofusscomputing/projects/django_template!7
This commit is contained in:
@ -8,7 +8,7 @@ Unit:
|
||||
- pip install -r requirements.txt
|
||||
- pip install -r requirements_test.txt
|
||||
- cd app
|
||||
- pytest --cov --cov-report term --cov-report xml:../artifacts/coverage.xml --cov-report html:../artifacts/coverage/ --junit-xml=../artifacts/test.junit.xml
|
||||
- pytest --cov --cov-report term --cov-report xml:../artifacts/coverage.xml --cov-report html:../artifacts/coverage/ --junit-xml=../artifacts/unit.JUnit.xml
|
||||
coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/'
|
||||
artifacts:
|
||||
expire_in: "30 days"
|
||||
@ -18,7 +18,7 @@ Unit:
|
||||
coverage_format: cobertura
|
||||
path: artifacts/coverage.xml
|
||||
junit:
|
||||
- artifacts/unit.JUnit.xml
|
||||
- artifacts/*.JUnit.xml
|
||||
paths:
|
||||
- artifacts/
|
||||
rules:
|
||||
|
@ -168,11 +168,13 @@ if API_ENABLED:
|
||||
],
|
||||
'DEFAULT_PAGINATION_CLASS':
|
||||
'rest_framework_json_api.pagination.JsonApiPageNumberPagination',
|
||||
'DEFAULT_PARSER_CLASSES': (
|
||||
'rest_framework_json_api.parsers.JSONParser',
|
||||
'rest_framework.parsers.FormParser',
|
||||
'rest_framework.parsers.MultiPartParser'
|
||||
),
|
||||
# leaving these uncommented, even though are the default renderers
|
||||
# causes the api to require inputs the fields under an 'attributes' key
|
||||
# 'DEFAULT_PARSER_CLASSES': (
|
||||
# 'rest_framework_json_api.parsers.JSONParser',
|
||||
# 'rest_framework.parsers.FormParser',
|
||||
# 'rest_framework.parsers.MultiPartParser'
|
||||
# ),
|
||||
# leaving these uncommented, even though are the default renderers
|
||||
# causes the api to output the fields under a 'attributes' key
|
||||
# 'DEFAULT_RENDERER_CLASSES': (
|
||||
@ -190,10 +192,11 @@ if API_ENABLED:
|
||||
'TEST_REQUEST_RENDERER_CLASSES': (
|
||||
'rest_framework_json_api.renderers.JSONRenderer',
|
||||
),
|
||||
'TEST_REQUEST_DEFAULT_FORMAT': 'vnd.api+json'
|
||||
# 'TEST_REQUEST_DEFAULT_FORMAT': 'vnd.api+json'
|
||||
'TEST_REQUEST_DEFAULT_FORMAT': 'json'
|
||||
}
|
||||
|
||||
|
||||
DATETIME_FORMAT = 'j N Y H:i:s'
|
||||
|
||||
if os.path.isdir(SETTINGS_DIR):
|
||||
|
||||
|
19
app/itam/forms/device/device.py
Normal file
19
app/itam/forms/device/device.py
Normal file
@ -0,0 +1,19 @@
|
||||
from django import forms
|
||||
from django.db.models import Q
|
||||
|
||||
from itam.models.device import Device
|
||||
|
||||
|
||||
class DeviceForm(forms.ModelForm):
|
||||
|
||||
prefix = 'device'
|
||||
|
||||
class Meta:
|
||||
model = Device
|
||||
fields = [
|
||||
'id',
|
||||
'name',
|
||||
'serial_number',
|
||||
'uuid',
|
||||
'device_type',
|
||||
]
|
18
app/itam/forms/device/operating_system.py
Normal file
18
app/itam/forms/device/operating_system.py
Normal file
@ -0,0 +1,18 @@
|
||||
from django import forms
|
||||
from django.db.models import Q
|
||||
|
||||
from itam.models.device import DeviceOperatingSystem
|
||||
|
||||
|
||||
class Update(forms.ModelForm):
|
||||
|
||||
prefix = 'operating_system'
|
||||
|
||||
class Meta:
|
||||
model = DeviceOperatingSystem
|
||||
fields = [
|
||||
"id",
|
||||
"version",
|
||||
'operating_system_version',
|
||||
]
|
||||
|
22
app/itam/forms/device_softwareupdate.py
Normal file
22
app/itam/forms/device_softwareupdate.py
Normal file
@ -0,0 +1,22 @@
|
||||
from django import forms
|
||||
from django.db.models import Q
|
||||
|
||||
from itam.models.device import DeviceSoftware
|
||||
from itam.models.software import Software, SoftwareVersion
|
||||
|
||||
|
||||
class SoftwareUpdate(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = DeviceSoftware
|
||||
fields = [
|
||||
'action',
|
||||
'version',
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.fields['version'].queryset = SoftwareVersion.objects.filter(software_id=self.instance.software.id)
|
||||
|
0
app/itam/forms/operating_system/__init__.py
Normal file
0
app/itam/forms/operating_system/__init__.py
Normal file
41
app/itam/forms/operating_system/update.py
Normal file
41
app/itam/forms/operating_system/update.py
Normal file
@ -0,0 +1,41 @@
|
||||
from app import settings
|
||||
from django import forms
|
||||
from django.db.models import Q
|
||||
|
||||
from itam.models.operating_system import OperatingSystem
|
||||
|
||||
|
||||
class Update(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = OperatingSystem
|
||||
fields = [
|
||||
"name",
|
||||
'slug',
|
||||
'id',
|
||||
'organization',
|
||||
'is_global',
|
||||
]
|
||||
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.fields['_created'] = forms.DateTimeField(
|
||||
label="Created",
|
||||
input_formats=settings.DATETIME_FORMAT,
|
||||
initial=kwargs['instance'].created,
|
||||
disabled=True
|
||||
)
|
||||
|
||||
self.fields['_modified'] = forms.DateTimeField(
|
||||
label="Modified",
|
||||
input_formats=settings.DATETIME_FORMAT,
|
||||
initial=kwargs['instance'].modified,
|
||||
disabled=True
|
||||
)
|
||||
|
||||
|
||||
if kwargs['instance'].is_global:
|
||||
|
||||
self.fields['is_global'].widget.attrs['disabled'] = True
|
18
app/itam/migrations/0002_alter_softwareversion_name.py
Normal file
18
app/itam/migrations/0002_alter_softwareversion_name.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.0.6 on 2024-05-17 10:10
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('itam', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='softwareversion',
|
||||
name='name',
|
||||
field=models.CharField(max_length=50),
|
||||
),
|
||||
]
|
19
app/itam/migrations/0003_devicesoftware_version.py
Normal file
19
app/itam/migrations/0003_devicesoftware_version.py
Normal file
@ -0,0 +1,19 @@
|
||||
# Generated by Django 5.0.6 on 2024-05-17 10:18
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('itam', '0002_alter_softwareversion_name'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='devicesoftware',
|
||||
name='version',
|
||||
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='itam.softwareversion'),
|
||||
),
|
||||
]
|
@ -0,0 +1,47 @@
|
||||
# Generated by Django 5.0.6 on 2024-05-18 08:51
|
||||
|
||||
import access.fields
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('access', '0001_initial'),
|
||||
('itam', '0003_devicesoftware_version'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='OperatingSystem',
|
||||
fields=[
|
||||
('is_global', models.BooleanField(default=False)),
|
||||
('id', models.AutoField(primary_key=True, serialize=False, unique=True)),
|
||||
('created', access.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False)),
|
||||
('modified', access.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False)),
|
||||
('name', models.CharField(max_length=50, unique=True)),
|
||||
('slug', access.fields.AutoSlugField()),
|
||||
('organization', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='access.organization')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='OperatingSystemVersion',
|
||||
fields=[
|
||||
('is_global', models.BooleanField(default=False)),
|
||||
('id', models.AutoField(primary_key=True, serialize=False, unique=True)),
|
||||
('created', access.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False)),
|
||||
('modified', access.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False)),
|
||||
('name', models.CharField(max_length=50)),
|
||||
('operating_system', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='itam.operatingsystem')),
|
||||
('organization', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='access.organization')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
@ -0,0 +1,38 @@
|
||||
# Generated by Django 5.0.6 on 2024-05-18 15:20
|
||||
|
||||
import access.fields
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('access', '0001_initial'),
|
||||
('itam', '0004_operatingsystem_operatingsystemversion'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='operatingsystemversion',
|
||||
name='name',
|
||||
field=models.CharField(max_length=50, verbose_name='Major Version'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DeviceOperatingSystem',
|
||||
fields=[
|
||||
('is_global', models.BooleanField(default=False)),
|
||||
('id', models.AutoField(primary_key=True, serialize=False, unique=True)),
|
||||
('created', access.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False)),
|
||||
('modified', access.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False)),
|
||||
('version', models.CharField(max_length=15, verbose_name='Installed Version')),
|
||||
('device', models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to='itam.device')),
|
||||
('operating_system_version', models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to='itam.operatingsystemversion', verbose_name='Operating System/Version')),
|
||||
('organization', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='access.organization')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
@ -2,8 +2,8 @@ from django.db import models
|
||||
|
||||
from access.fields import *
|
||||
from access.models import TenancyObject
|
||||
from itam.models.software import Software
|
||||
|
||||
from itam.models.software import Software, SoftwareVersion
|
||||
from itam.models.operating_system import OperatingSystemVersion
|
||||
|
||||
|
||||
class DeviceCommonFields(TenancyObject, models.Model):
|
||||
@ -98,11 +98,16 @@ class Device(DeviceCommonFieldsName):
|
||||
|
||||
state = 'absent'
|
||||
|
||||
software = {
|
||||
software_action = {
|
||||
"name": software.software.slug,
|
||||
"state": state
|
||||
}
|
||||
config['software'] = config['software'] + [ software ]
|
||||
|
||||
|
||||
if software.version:
|
||||
software_action['version'] = software.version.name
|
||||
|
||||
config['software'] = config['software'] + [ software_action ]
|
||||
|
||||
return config
|
||||
|
||||
@ -140,3 +145,42 @@ class DeviceSoftware(DeviceCommonFields):
|
||||
choices=Actions,
|
||||
default=None,
|
||||
)
|
||||
|
||||
version = models.ForeignKey(
|
||||
SoftwareVersion,
|
||||
on_delete=models.CASCADE,
|
||||
default = None,
|
||||
null = True,
|
||||
blank= True
|
||||
|
||||
)
|
||||
|
||||
|
||||
|
||||
class DeviceOperatingSystem(DeviceCommonFields):
|
||||
|
||||
device = models.ForeignKey(
|
||||
Device,
|
||||
on_delete = models.CASCADE,
|
||||
default = None,
|
||||
null = False,
|
||||
blank = False,
|
||||
|
||||
)
|
||||
|
||||
operating_system_version = models.ForeignKey(
|
||||
OperatingSystemVersion,
|
||||
verbose_name = 'Operating System/Version',
|
||||
on_delete = models.CASCADE,
|
||||
default = None,
|
||||
null = False,
|
||||
blank = False
|
||||
|
||||
)
|
||||
|
||||
version = models.CharField(
|
||||
verbose_name = 'Installed Version',
|
||||
max_length = 15,
|
||||
null = False,
|
||||
blank = False,
|
||||
)
|
||||
|
64
app/itam/models/operating_system.py
Normal file
64
app/itam/models/operating_system.py
Normal file
@ -0,0 +1,64 @@
|
||||
from django.db import models
|
||||
|
||||
from access.fields import *
|
||||
from access.models import TenancyObject
|
||||
|
||||
|
||||
|
||||
class OperatingSystemCommonFields(TenancyObject, models.Model):
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
id = models.AutoField(
|
||||
primary_key=True,
|
||||
unique=True,
|
||||
blank=False
|
||||
)
|
||||
|
||||
created = AutoCreatedField()
|
||||
|
||||
modified = AutoLastModifiedField()
|
||||
|
||||
|
||||
|
||||
class OperatingSystemFieldsName(OperatingSystemCommonFields):
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
name = models.CharField(
|
||||
blank = False,
|
||||
max_length = 50,
|
||||
unique = True,
|
||||
)
|
||||
|
||||
slug = AutoSlugField()
|
||||
|
||||
|
||||
|
||||
class OperatingSystem(OperatingSystemFieldsName):
|
||||
|
||||
def __str__(self):
|
||||
|
||||
return self.name
|
||||
|
||||
|
||||
class OperatingSystemVersion(OperatingSystemCommonFields):
|
||||
|
||||
operating_system = models.ForeignKey(
|
||||
OperatingSystem,
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
|
||||
name = models.CharField(
|
||||
verbose_name = 'Major Version',
|
||||
blank = False,
|
||||
max_length = 50,
|
||||
unique = False,
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
|
||||
return self.operating_system.name + ' ' + self.name
|
||||
|
@ -62,5 +62,15 @@ class SoftwareVersion(SoftwareCommonFields):
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
|
||||
name = models.CharField(
|
||||
blank = False,
|
||||
max_length = 50,
|
||||
unique = False,
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
|
||||
return self.name
|
||||
|
||||
|
||||
|
||||
|
@ -30,62 +30,83 @@
|
||||
</script>
|
||||
|
||||
<div class="tab">
|
||||
<button onclick="window.location='{% url 'ITAM:Devices' %}';">
|
||||
<< Back to Devices</button>
|
||||
<button id="defaultOpen" class="tablinks" onclick="openCity(event, 'Details')">Details</button>
|
||||
<button id="defaultOpen" class="tablinks" onclick="openCity(event, 'OperatingSystem')">Operating System</button>
|
||||
<button class="tablinks" onclick="openCity(event, 'Software')">Software</button>
|
||||
<button class="tablinks" onclick="openCity(event, 'ConfigManagement')">Config Management</button>
|
||||
<!-- <button class="tablinks" onclick="openCity(event, 'Installations')">Installations</button> -->
|
||||
<button onclick="window.location='{% url 'ITAM:Devices' %}';"
|
||||
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 Devices</button>
|
||||
<button id="defaultOpen" class="tablinks" onclick="openCity(event, 'Details')">Details</button>
|
||||
<button id="defaultOpen" class="tablinks" onclick="openCity(event, 'OperatingSystem')">Operating System</button>
|
||||
<button class="tablinks" onclick="openCity(event, 'Software')">Software</button>
|
||||
<button class="tablinks" onclick="openCity(event, 'ConfigManagement')">Config Management</button>
|
||||
<!-- <button class="tablinks" onclick="openCity(event, 'Installations')">Installations</button> -->
|
||||
</div>
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
<!-- Tab content -->
|
||||
<div id="Details" class="tabcontent">
|
||||
<h3>Details</h3>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form }}
|
||||
<input type="submit" value="Submit">
|
||||
</form>
|
||||
|
||||
<script>
|
||||
// Get the element with id="defaultOpen" and click on it
|
||||
document.getElementById("defaultOpen").click();
|
||||
</script>
|
||||
<div id="Details" class="tabcontent">
|
||||
<h3>
|
||||
Details
|
||||
<span style="font-weight: normal; float: right;">{% include 'icons/issue_link.html.j2' with issue=6 %}</span>
|
||||
</h3>
|
||||
{{ form.as_p }}
|
||||
<input name="{{form.prefix}}" type="submit" value="Submit">
|
||||
<script>
|
||||
// Get the element with id="defaultOpen" and click on it
|
||||
document.getElementById("defaultOpen").click();
|
||||
</script>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="OperatingSystem" class="tabcontent">
|
||||
<h3>Operating System</h3>
|
||||
</div>
|
||||
|
||||
<div id="Software" class="tabcontent">
|
||||
<h3>Software</h3>
|
||||
|
||||
<input type="button" value="Add Software Action" onclick="window.location='{% url 'ITAM:_device_software_add' device.id %}';">
|
||||
<table>
|
||||
<thead>
|
||||
<th>Name</th>
|
||||
<th>Action</th>
|
||||
<th>Installed</th>
|
||||
<th> </th>
|
||||
</thead>
|
||||
{% for software in softwares %}
|
||||
<tr>
|
||||
<td><a href="{% url 'ITAM:_software_view' pk=software.id %}">{{ software.software }}</a></td>
|
||||
<td><a href="{% url 'ITAM:_device_software_view' device_id=device.id pk=software.id %}">{{ software.get_action_display }}</a></td>
|
||||
<td> </td>
|
||||
<th> </th>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id="ConfigManagement" class="tabcontent">
|
||||
<h3>Configuration Management</h3>
|
||||
<div>
|
||||
<textarea cols="90" rows="30" readonly>{{ config }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="OperatingSystem" class="tabcontent">
|
||||
<h3>Operating System</h3>
|
||||
{{ operating_system.as_p }}
|
||||
<input type="submit" name="{{operating_system.prefix}}" value="Submit" />
|
||||
</div>
|
||||
|
||||
<div id="Software" class="tabcontent">
|
||||
<h3>Software</h3>
|
||||
|
||||
<input type="button" value="Add Software Action" onclick="window.location='{% url 'ITAM:_device_software_add' device.id %}';">
|
||||
<table>
|
||||
<thead>
|
||||
<th>Name</th>
|
||||
<th>Action</th>
|
||||
<th>Version</th>
|
||||
<th>Installed</th>
|
||||
<th> </th>
|
||||
</thead>
|
||||
{% if softwares %}
|
||||
{% for software in softwares %}
|
||||
<tr>
|
||||
<td><a href="{% url 'ITAM:_software_view' pk=software.software_id %}">{{ software.software }}</a></td>
|
||||
<td><a href="{% url 'ITAM:_device_software_view' device_id=device.id pk=software.id %}">{{ software.get_action_display }}</a></td>
|
||||
<td>
|
||||
{% if software.version %}
|
||||
{{ software.version }}
|
||||
{% else %}
|
||||
Any
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% include 'icons/issue_link.html.j2' with issue=2 %}
|
||||
</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<td colspan="5">Nothing Found {% include 'icons/issue_link.html.j2' with issue=2 %}</td>
|
||||
{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id="ConfigManagement" class="tabcontent">
|
||||
<h3>Configuration Management</h3>
|
||||
<div>
|
||||
<textarea cols="90" rows="30" readonly>{{ config }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
@ -28,4 +28,22 @@
|
||||
{% 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 %}
|
131
app/itam/templates/itam/operating_system.html.j2
Normal file
131
app/itam/templates/itam/operating_system.html.j2
Normal file
@ -0,0 +1,131 @@
|
||||
{% extends 'base.html.j2' %}
|
||||
|
||||
{% block title %}{{ operating_system.name }}{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
<script>
|
||||
|
||||
function openCity(evt, cityName) {
|
||||
|
||||
var i, tabcontent, tablinks;
|
||||
|
||||
tabcontent = document.getElementsByClassName("tabcontent");
|
||||
for (i = 0; i < tabcontent.length; i++) {
|
||||
tabcontent[i].style.display = "none";
|
||||
}
|
||||
|
||||
tablinks = document.getElementsByClassName("tablinks");
|
||||
for (i = 0; i < tablinks.length; i++) {
|
||||
tablinks[i].className = tablinks[i].className.replace(" active", "");
|
||||
}
|
||||
|
||||
document.getElementById(cityName).style.display = "block";
|
||||
evt.currentTarget.className += " active";
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="tab">
|
||||
<button onclick="window.location='{% url 'ITAM:Operating Systems' %}';" 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 Operating Systems</button>
|
||||
<button id="defaultOpen" class="tablinks" onclick="openCity(event, 'Details')">Details</button>
|
||||
<button class="tablinks" onclick="openCity(event, 'Versions')">Versions</button>
|
||||
<button class="tablinks" onclick="openCity(event, 'Licences')">Licences</button>
|
||||
<button class="tablinks" onclick="openCity(event, 'Installations')">Installations</button>
|
||||
</div>
|
||||
|
||||
<div id="Details" class="tabcontent">
|
||||
<h3>Details</h3>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form }}
|
||||
<input type="submit" value="Submit">
|
||||
</form>
|
||||
|
||||
<script>
|
||||
document.getElementById("defaultOpen").click();
|
||||
</script>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="Versions" class="tabcontent">
|
||||
<h3>Versions</h3>
|
||||
<input type="button" value="New Operating System Version" onclick="window.location='{% url 'ITAM:_operating_system_version_add' pk=operating_system.id %}';">
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<th>Version</th>
|
||||
<th>Installations</th>
|
||||
<th>Vulnerable</th>
|
||||
<th> </th>
|
||||
</thead>
|
||||
{% for version in operating_system_versions %}
|
||||
<tr>
|
||||
<td><a href="{% url 'ITAM:_operating_system_version_view' operating_system_id=operating_system.id pk=version.id %}">{{ version.name }}</a></td>
|
||||
<td>{% if version.installs == 0%}-{% else %}{{ version.installs }}{% endif %}</td>
|
||||
<td> </td>
|
||||
<td><a href="{% url 'ITAM:_operating_system_version_delete' operating_system_id=operating_system.id pk=version.id %}">DELETE</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="Licences" class="tabcontent">
|
||||
<h3>Licences</h3>
|
||||
{% include 'icons/issue_link.html.j2' with issue=4 %}
|
||||
<table>
|
||||
<thead>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Available</th>
|
||||
<th> </th>
|
||||
</thead>
|
||||
<tr>
|
||||
<td>GPL-3</td>
|
||||
<td>Open Source</td>
|
||||
<td>1 / 5</td>
|
||||
<th> </th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MIT</td>
|
||||
<td>Open Source</td>
|
||||
<td>Unlimited</td>
|
||||
<th> </th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Windows Device</td>
|
||||
<td>CAL</td>
|
||||
<td>11 / 15</td>
|
||||
<th> </th>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="Installations" class="tabcontent">
|
||||
<h3>Installations</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<th>Device</th>
|
||||
<th>Organization</th>
|
||||
<th>Version</th>
|
||||
<th title="Date Software Installed">Installed</th>
|
||||
<th> </th>
|
||||
</thead>
|
||||
{% for install in installs %}
|
||||
<tr>
|
||||
<td><a href="{% url 'ITAM:_device_view' pk=install.device_id %}">{{ install.device }}</a></td>
|
||||
<td>{{ install.organization }}</td>
|
||||
<td>{{ install.version }}</td>
|
||||
<td>{% include 'icons/issue_link.html.j2' with issue=2 %}</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
47
app/itam/templates/itam/operating_system_index.html.j2
Normal file
47
app/itam/templates/itam/operating_system_index.html.j2
Normal file
@ -0,0 +1,47 @@
|
||||
{% extends 'base.html.j2' %}
|
||||
|
||||
{% block title %}Operating Systems{% endblock %}
|
||||
{% block content_header_icon %}{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
<input type="button" value="New Operating System" onclick="window.location='{% url 'ITAM:_operating_system_add' %}';">
|
||||
<table class="data">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Created</th>
|
||||
<th>Modified</th>
|
||||
<th>Organization</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
{% for operating_system in operating_systems %}
|
||||
<tr>
|
||||
<td><a href="{% url 'ITAM:_operating_system_view' pk=operating_system.id %}">{{ operating_system.name }}</a></td>
|
||||
<td>{{ operating_system.created }}</td>
|
||||
<td>{{ operating_system.modified }}</td>
|
||||
<td>{% if operating_system.is_global %}Global{% else %}{{ operating_system.organization }}{% endif %}</td>
|
||||
<td><a href="{% url 'ITAM:_operating_system_delete' pk=operating_system.id %}">Delete</a></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 %}
|
@ -30,8 +30,10 @@
|
||||
</script>
|
||||
|
||||
<div class="tab">
|
||||
<button onclick="window.location='{% url 'ITAM:Software' %}';">
|
||||
<< Back to Software</button>
|
||||
<button onclick="window.location='{% url 'ITAM:Software' %}';" 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 Software</button>
|
||||
<button id="defaultOpen" class="tablinks" onclick="openCity(event, 'Details')">Details</button>
|
||||
<button id="defaultOpen" class="tablinks" onclick="openCity(event, 'Versions')">Versions</button>
|
||||
<button class="tablinks" onclick="openCity(event, 'Licences')">Licences</button>
|
||||
@ -56,7 +58,7 @@
|
||||
|
||||
<div id="Versions" class="tabcontent">
|
||||
<h3>Versions</h3>
|
||||
Not Yet Implemented
|
||||
<input type="button" value="New Software Version" onclick="window.location='{% url 'ITAM:_software_version_add' pk=software.id %}';">
|
||||
<table>
|
||||
<thead>
|
||||
<th>Version</th>
|
||||
@ -64,17 +66,20 @@
|
||||
<th>Vulnerable</th>
|
||||
<th> </th>
|
||||
</thead>
|
||||
{% for version in software_versions %}
|
||||
<tr>
|
||||
<td>1.0.0</td>
|
||||
<td>5</td>
|
||||
<td><input type="checkbox" checked disabled></td>
|
||||
<td><a href="{% url 'ITAM:_software_version_view' software_id=software.id pk=version.id %}">{{ version.name }}</a></td>
|
||||
<td>{% include 'icons/issue_link.html.j2' with issue=2 %}</td>
|
||||
<td>{% include 'icons/issue_link.html.j2' with issue=3 %}</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id="Licences" class="tabcontent">
|
||||
<h3>Licences</h3>
|
||||
Not Yet Implemented
|
||||
{% include 'icons/issue_link.html.j2' with issue=4 %}
|
||||
<table>
|
||||
<thead>
|
||||
<th>Name</th>
|
||||
@ -105,24 +110,45 @@
|
||||
|
||||
<div id="Installations" class="tabcontent">
|
||||
<h3>Installations</h3>
|
||||
Dev Notes: This table will show joined tables installed software and device software action as a single row.
|
||||
<table>
|
||||
<table>
|
||||
<thead>
|
||||
<th>Device</th>
|
||||
<th>Organization</th>
|
||||
<th title="Not Set/Install/Remove">Action</th>
|
||||
<th>Version</th>
|
||||
<th title="Date Software Installed">Installed</th>
|
||||
<th> </th>
|
||||
</thead>
|
||||
{% if device_software %}
|
||||
{% for device in device_software %}
|
||||
<tr>
|
||||
<td><a href="{% url 'ITAM:_device_view' pk=device.device.id %}">{{ device.device }}</a></td>
|
||||
<td>{{ device.organization }}</td>
|
||||
<td>{{ device.get_action_display }}</td>
|
||||
<td>Not Implemented</td>
|
||||
<td>
|
||||
{% if device.get_action_display == 'Install' %}
|
||||
{% include 'icons/success_text.html.j2' with icon_text=device.get_action_display %}
|
||||
{% elif device.get_action_display == 'Remove'%}
|
||||
{% include 'icons/cross_text.html.j2' with icon_text=device.get_action_display %}
|
||||
{% else %}
|
||||
{{ device.get_action_display }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if device.version %}
|
||||
{{ device.version }}
|
||||
{% else %}
|
||||
Any
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{% include 'icons/issue_link.html.j2' with issue=2 %}</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="6">Nothing Found</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
@ -26,4 +26,23 @@
|
||||
{% 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 %}
|
@ -20,3 +20,19 @@ def test_device_software_action(user):
|
||||
"""Ensure only software that is from the same organization or is global can be added to the device
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_device_not_global(user):
|
||||
"""Devices are not global items.
|
||||
|
||||
Ensure that a device can't be set to be global.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_device_operating_system_version_only_one(user):
|
||||
"""model deviceoperatingsystem must only contain one value per device
|
||||
"""
|
||||
pass
|
||||
|
41
app/itam/tests/test_operating_system.py
Normal file
41
app/itam/tests/test_operating_system.py
Normal file
@ -0,0 +1,41 @@
|
||||
# from django.conf import settings
|
||||
# from django.shortcuts import reverse
|
||||
from django.test import TestCase, Client
|
||||
|
||||
import pytest
|
||||
import unittest
|
||||
import requests
|
||||
|
||||
# from django.contrib.auth import get_user_model
|
||||
# from django.core.exceptions import ValidationError
|
||||
# from access.models import Organization
|
||||
|
||||
# class Test_app_structure_auth(unittest.TestCase):
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_operating_system_update_is_global_no_change(user):
|
||||
"""Once operating_system is set to global it can't be changed.
|
||||
|
||||
global status can't be changed as non-global items may reference the item.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_operating_system_prevent_delete_if_used(user):
|
||||
"""Any operating_system in use by a device must not be deleted.
|
||||
|
||||
i.e. A global os can't be deleted
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_operating_system_version_installs_by_os_count(user):
|
||||
"""Operating System Versions has a count field that must be accurate
|
||||
|
||||
The count is of model OperatingSystemVersion linked to model DeviceOperatingSystem
|
||||
"""
|
||||
|
||||
pass
|
@ -20,3 +20,12 @@ def test_software_update_is_global_no_change(user):
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_software_prevent_delete_if_used(user):
|
||||
"""Any software in use by a device must not be deleted.
|
||||
|
||||
i.e. A device has an action set for the software.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
@ -1,7 +1,7 @@
|
||||
from django.urls import path
|
||||
|
||||
from . import views
|
||||
from .views import device, device_type, software, software_category
|
||||
from .views import device, device_type, software, software_category, software_version, operating_system, operating_system_version
|
||||
|
||||
app_name = "ITAM"
|
||||
urlpatterns = [
|
||||
@ -11,15 +11,30 @@ urlpatterns = [
|
||||
path("device/<int:pk>/", device.View.as_view(), name="_device_view"),
|
||||
path("device/<int:pk>/software/add", device.SoftwareAdd.as_view(), name="_device_software_add"),
|
||||
path("device/<int:device_id>/software/<int:pk>", device.SoftwareView.as_view(), name="_device_software_view"),
|
||||
|
||||
path("device/<int:pk>/delete", device.Delete.as_view(), name="_device_delete"),
|
||||
path("device/add/", device.Add.as_view(), name="_device_add"),
|
||||
|
||||
|
||||
path("device_type/add/", device_type.Add.as_view(), name="_device_type_add"),
|
||||
|
||||
|
||||
path("operating_system", operating_system.IndexView.as_view(), name="Operating Systems"),
|
||||
path("operating_system/<int:pk>", operating_system.View.as_view(), name="_operating_system_view"),
|
||||
path("operating_system/add", operating_system.Add.as_view(), name="_operating_system_add"),
|
||||
path("operating_system/delete/<int:pk>", operating_system.Delete.as_view(), name="_operating_system_delete"),
|
||||
|
||||
|
||||
path("operating_system/<int:operating_system_id>/version/<int:pk>", operating_system_version.View.as_view(), name="_operating_system_version_view"),
|
||||
path("operating_system/<int:pk>/version/add", operating_system_version.Add.as_view(), name="_operating_system_version_add"),
|
||||
path("operating_system/<int:operating_system_id>/version/<int:pk>/delete", operating_system_version.Delete.as_view(), name="_operating_system_version_delete"),
|
||||
|
||||
|
||||
|
||||
path("software/", software.IndexView.as_view(), name="Software"),
|
||||
path("software/<int:pk>/", software.View.as_view(), name="_software_view"),
|
||||
path("software/<int:pk>/delete", software.Delete.as_view(), name="_software_delete"),
|
||||
path("software/<int:pk>/version/add", software_version.Add.as_view(), name="_software_version_add"),
|
||||
path("software/<int:software_id>/version/<int:pk>", software_version.View.as_view(), name="_software_version_view"),
|
||||
path("software/add/", software.Add.as_view(), name="_software_add"),
|
||||
|
||||
path("software_category/add/", software_category.Add.as_view(), name="_software_category_add"),
|
||||
|
@ -7,8 +7,12 @@ from django.views import generic
|
||||
from access.mixin import OrganizationPermission
|
||||
from access.models import Organization
|
||||
|
||||
from ..models.device import Device, DeviceSoftware
|
||||
from ..models.device import Device, DeviceSoftware, DeviceOperatingSystem
|
||||
from itam.forms.device_softwareadd import SoftwareAdd
|
||||
from itam.forms.device_softwareupdate import SoftwareUpdate
|
||||
|
||||
from itam.forms.device.device import DeviceForm
|
||||
from itam.forms.device.operating_system import Update as OperatingSystemForm
|
||||
|
||||
|
||||
class IndexView(PermissionRequiredMixin, OrganizationPermission, generic.ListView):
|
||||
@ -17,6 +21,8 @@ class IndexView(PermissionRequiredMixin, OrganizationPermission, generic.ListVie
|
||||
template_name = 'itam/device_index.html.j2'
|
||||
context_object_name = "devices"
|
||||
|
||||
paginate_by = 10
|
||||
|
||||
def get_queryset(self):
|
||||
|
||||
if self.request.user.is_superuser:
|
||||
@ -29,38 +35,80 @@ class IndexView(PermissionRequiredMixin, OrganizationPermission, generic.ListVie
|
||||
|
||||
|
||||
|
||||
def _get_form(request, formcls, prefix, **kwargs):
|
||||
data = request.POST if prefix in request.POST else None
|
||||
return formcls(data, prefix=prefix, **kwargs)
|
||||
|
||||
class View(OrganizationPermission, generic.UpdateView):
|
||||
|
||||
model = Device
|
||||
|
||||
permission_required = [
|
||||
'itam.view_device'
|
||||
]
|
||||
|
||||
template_name = 'itam/device.html.j2'
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'name',
|
||||
'serial_number',
|
||||
'uuid',
|
||||
'device_type',
|
||||
'is_global'
|
||||
]
|
||||
form_class = DeviceForm
|
||||
|
||||
context_object_name = "device"
|
||||
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
softwares = DeviceSoftware.objects.filter(device=self.kwargs['pk'])
|
||||
try:
|
||||
operating_system_version = DeviceOperatingSystem.objects.get(device=self.kwargs['pk'])
|
||||
|
||||
except DeviceOperatingSystem.DoesNotExist:
|
||||
|
||||
context['content_title'] = self.object.name
|
||||
operating_system_version = None
|
||||
|
||||
if operating_system_version:
|
||||
|
||||
context['operating_system'] = OperatingSystemForm(prefix='operating_system', instance=operating_system_version)
|
||||
|
||||
else:
|
||||
|
||||
context['operating_system'] = OperatingSystemForm(prefix='operating_system')
|
||||
|
||||
|
||||
softwares = DeviceSoftware.objects.filter(device=self.kwargs['pk'])
|
||||
context['softwares'] = softwares
|
||||
|
||||
config = self.object.get_configuration(self.kwargs['pk'])
|
||||
context['config'] = json.dumps(config, indent=4, sort_keys=True)
|
||||
|
||||
context['content_title'] = self.object.name
|
||||
|
||||
return context
|
||||
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
|
||||
device = Device.objects.get(pk=self.kwargs['pk'])
|
||||
|
||||
try:
|
||||
|
||||
existing_os = DeviceOperatingSystem.objects.get(device=self.kwargs['pk'])
|
||||
|
||||
except DeviceOperatingSystem.DoesNotExist:
|
||||
|
||||
existing_os = None
|
||||
|
||||
operating_system = OperatingSystemForm(request.POST, prefix='operating_system', instance=existing_os)
|
||||
|
||||
if operating_system.is_bound and operating_system.is_valid():
|
||||
|
||||
operating_system.instance.organization = device.organization
|
||||
operating_system.instance.device = device
|
||||
|
||||
operating_system.save()
|
||||
|
||||
return super().post(request, *args, **kwargs)
|
||||
|
||||
|
||||
def get_success_url(self, **kwargs):
|
||||
|
||||
return f"/itam/device/{self.kwargs['pk']}/"
|
||||
@ -74,13 +122,12 @@ class SoftwareView(OrganizationPermission, generic.UpdateView):
|
||||
]
|
||||
template_name = 'form.html.j2'
|
||||
|
||||
fields = [
|
||||
'action',
|
||||
]
|
||||
|
||||
|
||||
context_object_name = "devicesoftware"
|
||||
|
||||
form_class = SoftwareUpdate
|
||||
|
||||
|
||||
def form_valid(self, form):
|
||||
device = Device.objects.get(pk=self.kwargs['device_id'])
|
||||
@ -108,9 +155,12 @@ class Add(PermissionRequiredMixin, OrganizationPermission, generic.CreateView):
|
||||
'uuid',
|
||||
'device_type',
|
||||
'organization',
|
||||
'is_global'
|
||||
]
|
||||
|
||||
def form_valid(self, form):
|
||||
form.instance.is_global = False
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
def get_success_url(self, **kwargs):
|
||||
|
||||
|
112
app/itam/views/operating_system.py
Normal file
112
app/itam/views/operating_system.py
Normal file
@ -0,0 +1,112 @@
|
||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||
from django.db.models import Q, Count
|
||||
from django.urls import reverse
|
||||
from django.views import generic
|
||||
|
||||
from access.mixin import OrganizationPermission
|
||||
|
||||
from itam.models.device import DeviceOperatingSystem
|
||||
from itam.models.operating_system import OperatingSystem, OperatingSystemVersion
|
||||
from itam.forms.operating_system.update import Update
|
||||
|
||||
|
||||
|
||||
class IndexView(PermissionRequiredMixin, OrganizationPermission, generic.ListView):
|
||||
model = OperatingSystem
|
||||
permission_required = 'itam.view_operating_system'
|
||||
template_name = 'itam/operating_system_index.html.j2'
|
||||
context_object_name = "operating_systems"
|
||||
paginate_by = 10
|
||||
|
||||
|
||||
def get_queryset(self):
|
||||
|
||||
if self.request.user.is_superuser:
|
||||
|
||||
return OperatingSystem.objects.filter().order_by('name')
|
||||
|
||||
else:
|
||||
|
||||
return OperatingSystem.objects.filter(Q(organization__in=self.user_organizations()) | Q(is_global = True)).order_by('name')
|
||||
|
||||
|
||||
|
||||
class View(OrganizationPermission, generic.UpdateView):
|
||||
model = OperatingSystem
|
||||
permission_required = [
|
||||
'itam.view_operating_system'
|
||||
]
|
||||
template_name = 'itam/operating_system.html.j2'
|
||||
|
||||
form_class = Update
|
||||
|
||||
context_object_name = "operating_system"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
operating_system_versions = OperatingSystemVersion.objects.filter(operating_system=self.kwargs['pk']).order_by('name').annotate(installs=Count("deviceoperatingsystem"))
|
||||
context['operating_system_versions'] = operating_system_versions
|
||||
|
||||
installs = DeviceOperatingSystem.objects.filter(operating_system_version__operating_system_id=self.kwargs['pk'])
|
||||
context['installs'] = installs
|
||||
|
||||
context['content_title'] = self.object.name
|
||||
|
||||
return context
|
||||
|
||||
def get_success_url(self, **kwargs):
|
||||
|
||||
return reverse('ITAM:_operating_system_view', args=(self.kwargs['pk'],))
|
||||
|
||||
|
||||
|
||||
class Add(PermissionRequiredMixin, OrganizationPermission, generic.CreateView):
|
||||
model = OperatingSystem
|
||||
permission_required = [
|
||||
'access.add_operating_system',
|
||||
]
|
||||
template_name = 'form.html.j2'
|
||||
fields = [
|
||||
'name',
|
||||
'organization',
|
||||
'is_global'
|
||||
]
|
||||
|
||||
|
||||
def get_success_url(self, **kwargs):
|
||||
|
||||
return reverse('ITAM:Operating Systems')
|
||||
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
context['content_title'] = 'Add Operating System'
|
||||
|
||||
return context
|
||||
|
||||
|
||||
|
||||
class Delete(PermissionRequiredMixin, OrganizationPermission, generic.DeleteView):
|
||||
|
||||
model = OperatingSystem
|
||||
|
||||
permission_required = [
|
||||
'access.delete_operating_system',
|
||||
]
|
||||
|
||||
template_name = 'form.html.j2'
|
||||
|
||||
|
||||
def get_success_url(self, **kwargs):
|
||||
|
||||
return reverse('ITAM:Operating Systems')
|
||||
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
context['content_title'] = 'Delete ' + self.object.name
|
||||
|
||||
return context
|
88
app/itam/views/operating_system_version.py
Normal file
88
app/itam/views/operating_system_version.py
Normal file
@ -0,0 +1,88 @@
|
||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||
from django.urls import reverse
|
||||
from django.views import generic
|
||||
|
||||
from access.mixin import OrganizationPermission
|
||||
|
||||
from ..models.operating_system import OperatingSystem, OperatingSystemVersion
|
||||
|
||||
|
||||
|
||||
class View(OrganizationPermission, generic.UpdateView):
|
||||
model = OperatingSystemVersion
|
||||
permission_required = [
|
||||
'itam.view_operating_systemversion'
|
||||
]
|
||||
template_name = 'form.html.j2'
|
||||
|
||||
fields = [
|
||||
"name",
|
||||
]
|
||||
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
context['content_title'] = self.object.operating_system.name + ' ' + self.object.name
|
||||
|
||||
return context
|
||||
|
||||
|
||||
def get_success_url(self, **kwargs):
|
||||
|
||||
return reverse('ITAM:_operating_system_view', args=(self.kwargs['operating_system_id'],))
|
||||
|
||||
|
||||
|
||||
class Add(PermissionRequiredMixin, OrganizationPermission, generic.CreateView):
|
||||
model = OperatingSystemVersion
|
||||
permission_required = [
|
||||
'access.add_operating_systemversion',
|
||||
]
|
||||
template_name = 'form.html.j2'
|
||||
fields = [
|
||||
'name'
|
||||
]
|
||||
|
||||
def form_valid(self, form):
|
||||
operating_system = OperatingSystem.objects.get(pk=self.kwargs['pk'])
|
||||
|
||||
form.instance.is_global = operating_system.is_global
|
||||
form.instance.organization_id = operating_system.organization.id
|
||||
form.instance.operating_system_id = self.kwargs['pk']
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
def get_success_url(self, **kwargs):
|
||||
|
||||
return reverse('ITAM:_operating_system_view', args=(self.kwargs['pk'],))
|
||||
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
context['content_title'] = 'Add Operating System Version'
|
||||
|
||||
return context
|
||||
|
||||
|
||||
|
||||
class Delete(PermissionRequiredMixin, OrganizationPermission, generic.DeleteView):
|
||||
model = OperatingSystemVersion
|
||||
permission_required = [
|
||||
'access.delete_operating_system',
|
||||
]
|
||||
template_name = 'form.html.j2'
|
||||
|
||||
|
||||
def get_success_url(self, **kwargs):
|
||||
|
||||
return reverse('ITAM:_operating_system_view', args=(self.kwargs['operating_system_id'],))
|
||||
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
context['content_title'] = 'Delete ' + self.object.name
|
||||
|
||||
return context
|
@ -5,7 +5,7 @@ from django.views import generic
|
||||
from access.mixin import OrganizationPermission
|
||||
|
||||
from itam.models.device import DeviceSoftware
|
||||
from itam.models.software import Software
|
||||
from itam.models.software import Software, SoftwareVersion
|
||||
from itam.forms.software.update import Update as SoftwareUpdate_Form
|
||||
|
||||
class IndexView(PermissionRequiredMixin, OrganizationPermission, generic.ListView):
|
||||
@ -13,6 +13,7 @@ class IndexView(PermissionRequiredMixin, OrganizationPermission, generic.ListVie
|
||||
permission_required = 'itam.view_software'
|
||||
template_name = 'itam/software_index.html.j2'
|
||||
context_object_name = "softwares"
|
||||
paginate_by = 10
|
||||
|
||||
|
||||
def get_queryset(self):
|
||||
@ -42,6 +43,10 @@ class View(OrganizationPermission, generic.UpdateView):
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
software_versions = SoftwareVersion.objects.filter(software=self.kwargs['pk'])
|
||||
|
||||
context['software_versions'] = software_versions
|
||||
|
||||
context['content_title'] = self.object.name
|
||||
|
||||
if self.request.user.is_superuser:
|
||||
|
62
app/itam/views/software_version.py
Normal file
62
app/itam/views/software_version.py
Normal file
@ -0,0 +1,62 @@
|
||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||
from django.views import generic
|
||||
|
||||
from access.mixin import OrganizationPermission
|
||||
|
||||
from ..models.software import Software, SoftwareVersion
|
||||
|
||||
|
||||
class View(OrganizationPermission, generic.UpdateView):
|
||||
model = SoftwareVersion
|
||||
permission_required = [
|
||||
'itam.view_softwareversion'
|
||||
]
|
||||
template_name = 'form.html.j2'
|
||||
|
||||
fields = [
|
||||
"name",
|
||||
]
|
||||
|
||||
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 f"/itam/software/{self.kwargs['software_id']}/"
|
||||
|
||||
|
||||
|
||||
class Add(PermissionRequiredMixin, OrganizationPermission, generic.CreateView):
|
||||
model = SoftwareVersion
|
||||
permission_required = [
|
||||
'access.add_softwareversion',
|
||||
]
|
||||
template_name = 'form.html.j2'
|
||||
fields = [
|
||||
'name'
|
||||
]
|
||||
|
||||
def form_valid(self, form):
|
||||
software = Software.objects.get(pk=self.kwargs['pk'])
|
||||
|
||||
form.instance.is_global = software.is_global
|
||||
form.instance.organization_id = software.organization.id
|
||||
form.instance.software_id = self.kwargs['pk']
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
def get_success_url(self, **kwargs):
|
||||
|
||||
return f"/itam/software/{self.kwargs['pk']}/"
|
||||
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
context['content_title'] = 'Add Software Version'
|
||||
|
||||
return context
|
@ -41,6 +41,81 @@ span#content_header_icon {
|
||||
color: #177ee6;
|
||||
}
|
||||
|
||||
|
||||
span.icon-text {
|
||||
vertical-align: middle;
|
||||
border-radius: 25px;
|
||||
font-size: inherit;
|
||||
border: 1px solid #ccc;
|
||||
line-height: 30px;
|
||||
padding-left: 1px;
|
||||
padding-right: 10px;
|
||||
height: 30px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
span.success {
|
||||
color: #319c3a;
|
||||
}
|
||||
|
||||
span.icon-success {
|
||||
fill: #319c3a;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
margin: 0px;
|
||||
margin-bottom: -7px;
|
||||
padding: 0px;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
|
||||
span.cross {
|
||||
color: #9c3131;
|
||||
}
|
||||
|
||||
span.icon-cross {
|
||||
fill: #9c3131;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
margin: 0px;
|
||||
margin-bottom: -7px;
|
||||
padding: 0px;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
span.change {
|
||||
color: #cab706;
|
||||
}
|
||||
|
||||
span.icon-change {
|
||||
fill: #cab706;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
margin: 0px;
|
||||
margin-bottom: -7px;
|
||||
padding: 0px;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
|
||||
/* span.issue {
|
||||
color: #fc6d26;
|
||||
} */
|
||||
|
||||
span.icon-issue {
|
||||
fill: #fc6d26;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
margin: 0px;
|
||||
/* margin-bottom: -2px; */
|
||||
padding: 0px;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* .icon {
|
||||
display: block;
|
||||
content: none;
|
||||
|
@ -115,6 +115,14 @@ input[type=submit] {
|
||||
/* background-color: #f1f1f1; */
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
padding: 0px;
|
||||
margin: 0px
|
||||
}
|
||||
|
||||
.tablinks {
|
||||
border: 0px;
|
||||
margin: none;
|
||||
padding: none;
|
||||
}
|
||||
|
||||
/* Style the buttons that are used to open the tab content */
|
||||
@ -125,9 +133,12 @@ input[type=submit] {
|
||||
border: none;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
padding: 14px 16px;
|
||||
transition: 0.3s;
|
||||
font-size: inherit;
|
||||
color: #6a6e73;
|
||||
}
|
||||
|
||||
/* Change background color of buttons on hover */
|
||||
@ -146,7 +157,8 @@ input[type=submit] {
|
||||
.tabcontent {
|
||||
width: 100%;
|
||||
display: none;
|
||||
padding: 6px 12px;
|
||||
/* padding: 6px 12px; */
|
||||
padding-bottom: 0px;
|
||||
border: none;
|
||||
border-top: none;
|
||||
}
|
||||
|
@ -2,4 +2,13 @@
|
||||
|
||||
{% block title %}Home{% endblock %}
|
||||
|
||||
{% block body%}{% endblock %}
|
||||
|
||||
{% block body%}
|
||||
|
||||
To Do List:
|
||||
|
||||
<ul>
|
||||
<li>{% include 'icons/issue_link.html.j2' with issue=5 %} - Item History</li>
|
||||
</ul>
|
||||
|
||||
{% endblock %}
|
||||
|
8
app/templates/icons/change_text.html.j2
Normal file
8
app/templates/icons/change_text.html.j2
Normal file
@ -0,0 +1,8 @@
|
||||
<span class="icon-text change">
|
||||
<span class="icon-change">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 -960 960 960" width="20px">
|
||||
<path d="M483-337q-29 0-56.5-10T378-378q-20-20-31-46.28T336-480q0-9.74.8-18.99.8-9.25 3.2-18.01 2-10-1.66-18.82-3.65-8.83-12.59-12.5-9.75-3.68-18.37.51-8.63 4.19-11.63 14.24Q292-521 290-507.63q-2 13.37-2 27.63 0 38 14.71 73.42Q317.42-371.17 344-344q28 28 64.5 42t75.5 14l-27 27q-7 7.36-7 17.18t7 16.82q7 7 16.82 7t17.18-7l59.79-59.79Q562-298 562-312.18T551-337l-59-60q-7.36-7-17.18-7T458-397q-7 7-7 16.82t7 17.18l25 26Zm-7-287q29.7 0 57.35 10.5Q561-603 582-582q20 20 31 46.28T624-480q0 9-.8 18.5T620-443q-2 10 1.5 19t12.59 12q9.91 3 18.89-1.32 8.99-4.33 12.11-14.71Q669-441 670.5-453.5 672-466 672-480q0-38-15-73.5T615-616q-28-28-65-41.5T474-670l29-29q7-7.36 7-17.18T503-733q-7-7-16.82-7T469-733l-59.79 59.79Q398-662 398-647.82T409-623l60 60q7.36 7 17.18 7t16.82-7q7-7 7-16.82T503-597l-27-27Zm4.28 528Q401-96 331-126t-122.5-82.5Q156-261 126-330.96t-30-149.5Q96-560 126-629.5q30-69.5 82.5-122T330.96-834q69.96-30 149.5-30t149.04 30q69.5 30 122 82.5T834-629.28q30 69.73 30 149Q864-401 834-331t-82.5 122.5Q699-156 629.28-126q-69.73 30-149 30Z"/>
|
||||
</svg>
|
||||
</span>
|
||||
{{ icon_text }}
|
||||
</span>
|
8
app/templates/icons/cross_text.html.j2
Normal file
8
app/templates/icons/cross_text.html.j2
Normal file
@ -0,0 +1,8 @@
|
||||
<span class="icon-text cross">
|
||||
<span class="icon-cross">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 -960 960 960" width="20px">
|
||||
<path d="m480-429 116 116q11 11 25.5 10.5T647-314q11-11 11-25.5t-11-25.46L531-480.5l116-115.54q11-10.96 11-25.46T647-647q-11-11-25.5-11T596-647L480-531 364-647q-11-11-25-11t-25 11q-11 11-11 25.5t10.91 25.5L429-480 313-364q-11 11-10.5 25t11.5 25q11 11 25.5 11t25.41-10.91L480-429Zm.28 333Q401-96 331-126t-122.5-82.5Q156-261 126-330.96t-30-149.5Q96-560 126-629.5q30-69.5 82.5-122T330.96-834q69.96-30 149.5-30t149.04 30q69.5 30 122 82.5T834-629.28q30 69.73 30 149Q864-401 834-331t-82.5 122.5Q699-156 629.28-126q-69.73 30-149 30Z" />
|
||||
</svg>
|
||||
</span>
|
||||
{{ icon_text }}
|
||||
</span>
|
6
app/templates/icons/issue_link.html.j2
Normal file
6
app/templates/icons/issue_link.html.j2
Normal file
@ -0,0 +1,6 @@
|
||||
<span class="icon-text issue">
|
||||
<span class="icon-issue">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="25px" viewBox="0 -960 960 960" width="25px"><path d="M480-144q-60 0-109-32.5T302-264h-74q-15.3 0-25.65-10.29Q192-284.58 192-299.79t10.35-25.71Q212.7-336 228-336h60v-60h-60q-15.3 0-25.65-10.29Q192-416.58 192-431.79t10.35-25.71Q212.7-468 228-468h60v-60h-60q-15.3 0-25.65-10.29Q192-548.58 192-563.79t10.35-25.71Q212.7-600 228-600h74q8-26 25.8-47.09Q345.6-668.18 369-684l-56-56q-11-11-10.5-25.5T314-791q11-11 25-11t25 11l76 75q19.86-5 40.43-5t40.57 5l75-75q11-11 25.67-11 14.66 0 25.33 11 11 11 11 25.5T647-740l-56 56q23 16 40 37t27 47h74q15.3 0 25.65 10.29Q768-579.42 768-564.21t-10.35 25.71Q747.3-528 732-528h-60v60h60q15.3 0 25.65 10.29Q768-447.42 768-432.21t-10.35 25.71Q747.3-396 732-396h-60v60h60q15.3 0 25.65 10.29Q768-315.42 768-300.21t-10.35 25.71Q747.3-264 732-264h-74q-20 55-69 87.5T480-144Zm0-72q48.67 0 83.34-35Q598-286 600-336v-192q2-50-33.5-85t-86-35q-50.5 0-85 35T360-528v192q-1 50 34 85t86 35Zm-36.09-120h71.83q15.26 0 25.76-10.29 10.5-10.29 10.5-25.5t-10.32-25.71Q531.35-408 516.09-408h-71.83q-15.26 0-25.76 10.29-10.5 10.29-10.5 25.5t10.32 25.71q10.33 10.5 25.59 10.5Zm0-120h71.83q15.26 0 25.76-10.29 10.5-10.29 10.5-25.5t-10.32-25.71Q531.35-528 516.09-528h-71.83q-15.26 0-25.76 10.29-10.5 10.29-10.5 25.5t10.32 25.71q10.33 10.5 25.59 10.5ZM480-430Z"/></svg>
|
||||
</span>
|
||||
<a href="https://gitlab.com/nofusscomputing/projects/django_template/-/issues/{{ issue }}" target="_blank"> see #{{ issue }}</a>
|
||||
</span>
|
8
app/templates/icons/success_text.html.j2
Normal file
8
app/templates/icons/success_text.html.j2
Normal file
@ -0,0 +1,8 @@
|
||||
<span class="icon-text success">
|
||||
<span class="icon-success">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 -960 960 960" width="20px">
|
||||
<path class="tick" d="m424-408-86-86q-11-11-28-11t-28 11q-11 11-11 28t11 28l114 114q12 12 28 12t28-12l226-226q11-11 11-28t-11-28q-11-11-28-11t-28 11L424-408Zm56 328q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Z"></path>
|
||||
</svg>
|
||||
</span>
|
||||
{{ icon_text }}
|
||||
</span>
|
140
docs/projects/django-template/development/index.md
Normal file
140
docs/projects/django-template/development/index.md
Normal file
@ -0,0 +1,140 @@
|
||||
---
|
||||
title: Django Template Devlopment
|
||||
description: No Fuss Computings NetBox Django Site Template Development
|
||||
date: 2024-05-17
|
||||
template: project.html
|
||||
about: https://gitlab.com/nofusscomputing/infrastructure/configuration-management/django_app
|
||||
---
|
||||
|
||||
This page contains different items related to the development of this application.
|
||||
|
||||
## Icons
|
||||
|
||||
To locate additional icons for use see [material icons](https://fonts.google.com/icons).
|
||||
|
||||
Icons with text:
|
||||
|
||||
- Success `{% include 'icons/success_text.html.j2' with icon_text='success' %}` _denotes yes, success etc_
|
||||
|
||||
- Cross `{% include 'icons/cross_text.html.j2' with icon_text='cross' %}` _denotes no, negative etc_
|
||||
|
||||
- Change `{% include 'icons/change_text.html.j2' with icon_text='change' %}` _denotes that change management needs to run_
|
||||
|
||||
- Issue `{% include 'icons/issue_link.html.j2' with issue=2 %}` _Used to provide a link to an issue on GitLab. i.e. incomplete feature ticket_
|
||||
|
||||
|
||||
## Adding an Application
|
||||
|
||||
1. Install the django application with `pip <app-name>`
|
||||
|
||||
1. Update `app.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 when combined with a prefix of `_` provides the option to limit what URL's are displayed within the navigation menu. A name beginning with an underscore `_` will not be displayed in the menu.
|
||||
|
||||
Once you have completed the above list, your application will display collapsed within the navigation menu with the name of your application.
|
||||
|
||||
|
||||
## Tenancy Setup
|
||||
|
||||
Within your view class include the mixin class `OrganizationPermission`, ensuring that you set the `permission_required` attribute.
|
||||
|
||||
|
||||
### Model Setup
|
||||
|
||||
Any item you wish to be multi-tenant, ensure within your model you include the tenancy model abstract class. The class includes a field called `organization` which links directly to the organization model and is used by the tenancy permission check.
|
||||
|
||||
``` python title="<your app name>/models.py"
|
||||
|
||||
from access.models import TenancyObject
|
||||
|
||||
class YourObject(TenancyObject):
|
||||
...
|
||||
|
||||
```
|
||||
|
||||
|
||||
### View Setup
|
||||
|
||||
The mixin inlcuded in this template `OrganizationPermission` is designed to work with all django built in views and is what does the multi-tenancy permission checks.
|
||||
|
||||
``` python title="<your app name>/views.py"
|
||||
|
||||
from django.db.models import Q
|
||||
|
||||
from access.mixins import OrganizationPermission
|
||||
|
||||
|
||||
class IndexView(OrganizationPermission, generic.ListView):
|
||||
|
||||
model = YourModel
|
||||
|
||||
permission_required = 'access.view_organization'
|
||||
|
||||
# Use this for static success url
|
||||
success_url = f"/organization/" + pk_url_kwarg
|
||||
|
||||
|
||||
# Use this to build dynamic success URL
|
||||
def get_success_url(self, **kwargs):
|
||||
|
||||
return f"/organization/{self.kwargs['pk']}/"
|
||||
|
||||
|
||||
def get_queryset(self):
|
||||
|
||||
return MyModel.objects.filter(Q(organization__in=self.user_organizations()) | Q(is_global = True))
|
||||
|
||||
```
|
||||
|
||||
Using a filter `pk__in=self.user_organizations()` for the queryset using the mixins function `user_organizations`, will limit the query set to only items where the user is a member of the organization.
|
||||
|
||||
|
||||
### Templates
|
||||
|
||||
The base template includes blocks that are designed to assist in rendering your content. The following blocks are available:
|
||||
|
||||
- `title` - The page and title
|
||||
|
||||
- `content_header_icon` - Header icon that is middle aligned with the page title, floating right.
|
||||
|
||||
- `body` - The html content of the page
|
||||
|
||||
``` html title="template.html.j2"
|
||||
|
||||
{% extends 'base.html.j2' %}
|
||||
|
||||
{% block title %}{% endblock %}
|
||||
{% block content_header_icon %}<span title="View History" id="content_header_icon">H</span>{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
your content here
|
||||
|
||||
{% endblock %}
|
||||
|
||||
```
|
@ -1,56 +1,20 @@
|
||||
---
|
||||
title: Django Template
|
||||
description: No Fuss Computings NetBox Django Site Template
|
||||
title: Django ITSM
|
||||
description: No Fuss Computings NetBox Django ITSM
|
||||
date: 2024-05-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. Currently the template style is that of the Red Hat echo system (AWX, Foreman, EDA, Cockpit etc).
|
||||
|
||||
This template has built into it multi-tenancy which can easily added to your django application if using this template.
|
||||
This Django Project is designed to be a tool that forms part of IT Service Management (ITSM). The goal is to provide a system that is not only an IT Information Library (ITIL), but that of which will connect to other ITSM systems, i.e. AWX for automation orchestration. Currently the template style is that of the Red Hat echo system (AWX, Foreman, EDA, Cockpit etc).
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- [API](api.md)
|
||||
|
||||
- [Multi-Tenancy](permissions.md)
|
||||
- [Multi-Tenant](permissions.md)
|
||||
|
||||
- Auto-Generated Navigation Menu
|
||||
- [IT Asset Management (ITAM)](itam/index.md)
|
||||
|
||||
|
||||
## Adding an Application
|
||||
|
||||
1. Install the django application with `pip <app-name>`
|
||||
|
||||
1. Update `app.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 when combined with a prefix of `_` provides the option to limit what URL's are displayed within the navigation menu. A name beginning with an underscore `_` will not be displayed in the menu.
|
||||
|
||||
Once you have completed the above list, your application will display collapsed within the navigation menu with the name of your application.
|
||||
- [Configuration ready for ansible](itam/device.md#configuration)
|
||||
|
@ -6,4 +6,36 @@ template: project.html
|
||||
about: https://gitlab.com/nofusscomputing/infrastructure/configuration-management/django_app
|
||||
---
|
||||
|
||||
This component within ITAM is intended to display information about a device, be it a computer, router or switch etc.
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
For each device within your inventory, the following fields/tabs are available to track items:
|
||||
|
||||
- Name
|
||||
|
||||
- Operating System
|
||||
|
||||
- Software
|
||||
|
||||
- Configuration
|
||||
|
||||
|
||||
### Operating System
|
||||
|
||||
This tab shows the operating system selected as installed on the device. the version `name` is intended to be full [semver](https://semver.org/).
|
||||
|
||||
|
||||
### Software
|
||||
|
||||
This tab displays any action against software. For instance, you can select a piece of software from the inventory and have it set to either `Install` or `Remove` and the ansible config will be updated so that you can pull this config to use within a playbook.
|
||||
|
||||
Configuration for this tab is shown as a `list` of `dict` under the configuration key `software` and is setup for ease of use for the `ansible.builtin.apt` module.
|
||||
|
||||
|
||||
### Configuration
|
||||
|
||||
Although, configuration is generally part of config management. This tab displays in `JSON` format configuration that is ready for use. The intended audience is Ansible users with the fields provided matching established Ansible modules, if they exist.
|
||||
|
||||
This configuration can also be obtained from API endpoint `/api/config/<machine-slug>` where `<machine-slug>` would match the Ansible `inventory_hostname`.
|
||||
|
@ -6,4 +6,13 @@ template: project.html
|
||||
about: https://gitlab.com/nofusscomputing/infrastructure/configuration-management/django_app
|
||||
---
|
||||
|
||||
IT Asset Management (ITAM) is a crucial area of IT Service Management (ITSM).
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- [Devices](device.md)
|
||||
|
||||
- [Operating Systems](operating_system.md)
|
||||
|
||||
- [Software](software.md)
|
||||
|
28
docs/projects/django-template/itam/operating_system.md
Normal file
28
docs/projects/django-template/itam/operating_system.md
Normal file
@ -0,0 +1,28 @@
|
||||
---
|
||||
title: Operating System
|
||||
description: No Fuss Computings Django Template ITAM Operating System
|
||||
date: 2024-05-19
|
||||
template: project.html
|
||||
about: https://gitlab.com/nofusscomputing/infrastructure/configuration-management/django_app
|
||||
---
|
||||
|
||||
This ITAM component tracks Operating systems within your inventory.
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- Name
|
||||
|
||||
- Versions
|
||||
|
||||
- Installations
|
||||
|
||||
|
||||
### Versions
|
||||
|
||||
This tab displays the Operating system version with the intent of the version name to be the `MAJOR` version number of a [semver](https://semver.org/) operating systems version number. For each version installed, the total installations is also displayed.
|
||||
|
||||
|
||||
## Installations
|
||||
|
||||
This tab show all devices with the operating system installed.
|
@ -34,58 +34,3 @@ The workflow is conducted as part of the view and has the following flow:
|
||||
1. confirms that the team the permission came from is part of the same organization as the object the action is being conducted on.
|
||||
|
||||
1. ONLY on success of the above items, grants access.
|
||||
|
||||
|
||||
## Tenancy Setup
|
||||
|
||||
Within your view class include the mixin class `OrganizationPermission`, ensuring that you set the `permission_required` attribute.
|
||||
|
||||
|
||||
### Model Setup
|
||||
|
||||
Any item you wish to be multi-tenant, ensure within your model you include the tenancy model abstract class. The class includes a field called `organization` which links directly to the organization model and is used by the tenancy permission check.
|
||||
|
||||
``` python title="<your app name>/models.py"
|
||||
|
||||
from access.models import TenancyObject
|
||||
|
||||
class YourObject(TenancyObject):
|
||||
...
|
||||
|
||||
```
|
||||
|
||||
|
||||
### View Setup
|
||||
|
||||
The mixin inlcuded in this template `OrganizationPermission` is designed to work with all django built in views and is what does the multi-tenancy permission checks.
|
||||
|
||||
``` python title="<your app name>/views.py"
|
||||
|
||||
from django.db.models import Q
|
||||
|
||||
from access.mixins import OrganizationPermission
|
||||
|
||||
|
||||
class IndexView(OrganizationPermission, generic.ListView):
|
||||
|
||||
model = YourModel
|
||||
|
||||
permission_required = 'access.view_organization'
|
||||
|
||||
# Use this for static success url
|
||||
success_url = f"/organization/" + pk_url_kwarg
|
||||
|
||||
|
||||
# Use this to build dynamic success URL
|
||||
def get_success_url(self, **kwargs):
|
||||
|
||||
return f"/organization/{self.kwargs['pk']}/"
|
||||
|
||||
|
||||
def get_queryset(self):
|
||||
|
||||
return MyModel.objects.filter(Q(organization__in=self.user_organizations()) | Q(is_global = True))
|
||||
|
||||
```
|
||||
|
||||
Using a filter `pk__in=self.user_organizations()` for the queryset using the mixins function `user_organizations`, will limit the query set to only items where the user is a member of the organization.
|
||||
|
@ -1,34 +0,0 @@
|
||||
---
|
||||
title: Template
|
||||
description: No Fuss Computings Django Template Jinja TEmplate
|
||||
date: 2024-05-14
|
||||
template: project.html
|
||||
about: https://gitlab.com/nofusscomputing/infrastructure/configuration-management/django_app
|
||||
---
|
||||
|
||||
|
||||
|
||||
### Template
|
||||
|
||||
The base template includes blocks that are designed to assist in rendering your content. The following blocks are available:
|
||||
|
||||
- `title` - The page and title
|
||||
|
||||
- `content_header_icon` - Header icon that is middle aligned with the page title, floating right.
|
||||
|
||||
- `body` - The html content of the page
|
||||
|
||||
``` html title="template.html.j2"
|
||||
|
||||
{% extends 'base.html.j2' %}
|
||||
|
||||
{% block title %}{% endblock %}
|
||||
{% block content_header_icon %}<span title="View History" id="content_header_icon">H</span>{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
your content here
|
||||
|
||||
{% endblock %}
|
||||
|
||||
```
|
@ -25,16 +25,20 @@ nav:
|
||||
|
||||
- projects/django-template/permissions.md
|
||||
|
||||
- projects/django-template/template.md
|
||||
|
||||
- ITAM:
|
||||
|
||||
- projects/django-template/itam/index.md
|
||||
|
||||
- projects/django-template/itam/device.md
|
||||
|
||||
- projects/django-template/itam/operating_system.md
|
||||
|
||||
- projects/django-template/itam/software.md
|
||||
|
||||
- Development:
|
||||
|
||||
- projects/django-template/development/index.md
|
||||
|
||||
- Operations:
|
||||
|
||||
- operations/index.md
|
||||
|
Reference in New Issue
Block a user