@ -54,7 +54,8 @@ INSTALLED_APPS = [
|
||||
'itam.apps.ItamConfig',
|
||||
'settings.apps.SettingsConfig',
|
||||
'drf_spectacular',
|
||||
'drf_spectacular_sidecar'
|
||||
'drf_spectacular_sidecar',
|
||||
'config_management.apps.ConfigManagementConfig',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
@ -242,6 +243,5 @@ if DEBUG:
|
||||
# Apps Under Development
|
||||
INSTALLED_APPS += [
|
||||
'information.apps.InformationConfig',
|
||||
'config_management.apps.ConfigManagementConfig',
|
||||
'project_management.apps.ProjectManagementConfig',
|
||||
]
|
||||
|
34
app/config_management/migrations/0001_initial.py
Normal file
34
app/config_management/migrations/0001_initial.py
Normal file
@ -0,0 +1,34 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-02 14:48
|
||||
|
||||
import access.fields
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('access', '0002_alter_team_organization'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ConfigGroups',
|
||||
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)),
|
||||
('config', models.JSONField(blank=True, default=None, null=True)),
|
||||
('organization', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization')),
|
||||
('parent', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='config_management.configgroups')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Config Groups',
|
||||
},
|
||||
),
|
||||
]
|
@ -1 +0,0 @@
|
||||
from django.db import models
|
88
app/config_management/models/groups.py
Normal file
88
app/config_management/models/groups.py
Normal file
@ -0,0 +1,88 @@
|
||||
from django.db import models
|
||||
|
||||
from access.fields import *
|
||||
from access.models import TenancyObject
|
||||
|
||||
from core.mixin.history_save import SaveHistory
|
||||
|
||||
|
||||
|
||||
class GroupsCommonFields(TenancyObject, models.Model):
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
id = models.AutoField(
|
||||
primary_key=True,
|
||||
unique=True,
|
||||
blank=False
|
||||
)
|
||||
|
||||
created = AutoCreatedField()
|
||||
|
||||
modified = AutoLastModifiedField()
|
||||
|
||||
def __str__(self):
|
||||
|
||||
return self.name
|
||||
|
||||
|
||||
|
||||
class ConfigGroups(GroupsCommonFields, SaveHistory):
|
||||
|
||||
|
||||
parent = models.ForeignKey(
|
||||
'self',
|
||||
on_delete=models.CASCADE,
|
||||
default = None,
|
||||
null = True,
|
||||
blank= True
|
||||
)
|
||||
|
||||
|
||||
name = models.CharField(
|
||||
blank = False,
|
||||
max_length = 50,
|
||||
unique = False,
|
||||
)
|
||||
|
||||
|
||||
config = models.JSONField(
|
||||
blank = True,
|
||||
default = None,
|
||||
null = True,
|
||||
)
|
||||
|
||||
|
||||
|
||||
def count_children(self) -> int:
|
||||
""" Count all child groups recursively
|
||||
|
||||
Returns:
|
||||
int: Total count of ALL child-groups
|
||||
"""
|
||||
|
||||
count = 0
|
||||
|
||||
children = ConfigGroups.objects.filter(parent=self.pk)
|
||||
|
||||
for child in children.all():
|
||||
|
||||
count += 1
|
||||
|
||||
count += child.count_children()
|
||||
|
||||
return count
|
||||
|
||||
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
||||
self.is_global = False
|
||||
|
||||
if self.parent:
|
||||
self.organization = ConfigGroups.objects.get(id=self.parent.id).organization
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
107
app/config_management/templates/config_management/group.html.j2
Normal file
107
app/config_management/templates/config_management/group.html.j2
Normal file
@ -0,0 +1,107 @@
|
||||
{% extends 'base.html.j2' %}
|
||||
|
||||
{% 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='{% if group.parent %}{% url 'Config Management:_group_view' pk=group.parent.id %}{% else %}{% url 'Config Management:_group_index' %}{% endif %}';"
|
||||
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 {% if group.parent %}Parent{% else %}Groups{% endif %}</button>
|
||||
|
||||
<button id="defaultOpen" class="tablinks" onclick="openCity(event, 'Details')">Details</button>
|
||||
<button id="defaultOpen" class="tablinks" onclick="openCity(event, 'Children')">Child Groups</button>
|
||||
<button id="defaultOpen" class="tablinks" onclick="openCity(event, 'Software')">Software</button>
|
||||
<button class="tablinks" onclick="openCity(event, 'Notes')">Notes</button>
|
||||
|
||||
</div>
|
||||
|
||||
<form method="post">
|
||||
<div id="Details" class="tabcontent">
|
||||
<h3>Details</h3>
|
||||
|
||||
{% csrf_token %}
|
||||
{{ form }}
|
||||
{% include 'icons/issue_link.html.j2' with issue=13 %}<br>
|
||||
<input type="submit" value="Submit">
|
||||
|
||||
<script>
|
||||
document.getElementById("defaultOpen").click();
|
||||
</script>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="Children" class="tabcontent">
|
||||
<h3>Child Groups</h3>
|
||||
<table class="data">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Sub-Groups</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
{% if child_groups %}
|
||||
{% for group in child_groups %}
|
||||
<tr>
|
||||
<td><a href="{% url 'Config Management:_group_view' pk=group.id %}">{{ group.name }}</a></td>
|
||||
<td>{{ group.count_children }}</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="4">Nothing Found</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div id="Software" class="tabcontent">
|
||||
<h3>
|
||||
Software
|
||||
</h3>
|
||||
|
||||
</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>
|
||||
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
@ -0,0 +1,47 @@
|
||||
{% extends 'base.html.j2' %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
<input type="button" value="New Group" onclick="window.location='{% url 'Config Management:_group_add' %}';">
|
||||
<table class="data">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Organization</th>
|
||||
<th>Sub-Groups</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
{% if groups %}
|
||||
{% for group in groups %}
|
||||
<tr>
|
||||
<td><a href="{% url 'Config Management:_group_view' pk=group.id %}">{{ group.name }}</a></td>
|
||||
<td>{{ group.organization }}</td>
|
||||
<td>{{ group.count_children }}</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="4">Nothing Found</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
<br>
|
||||
<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 %}
|
@ -0,0 +1,9 @@
|
||||
{% extends 'base.html.j2' %}
|
||||
|
||||
{% block body %}
|
||||
what will go here?
|
||||
|
||||
<ul>
|
||||
<li><a href="{% url 'Config Management:_group_index' %}">Groups</a></li>
|
||||
</ul>
|
||||
{% endblock %}
|
@ -1,9 +1,15 @@
|
||||
from django.urls import path
|
||||
|
||||
from .views import ConfigIndex
|
||||
from config_management.views.index import ConfigIndex
|
||||
from config_management.views.groups import GroupIndexView, GroupAdd, GroupDelete, GroupView
|
||||
|
||||
app_name = "Config Management"
|
||||
|
||||
urlpatterns = [
|
||||
path('', ConfigIndex.as_view(), name='Config Management'),
|
||||
path('group', GroupIndexView.as_view(), name='_group_index'),
|
||||
path('group/add', GroupAdd.as_view(), name='_group_add'),
|
||||
path('group/<int:pk>', GroupView.as_view(), name='_group_view'),
|
||||
path('group/<int:pk>/delete', GroupDelete.as_view(), name='_group_delete'),
|
||||
|
||||
]
|
||||
|
192
app/config_management/views/groups.py
Normal file
192
app/config_management/views/groups.py
Normal file
@ -0,0 +1,192 @@
|
||||
from django.contrib.auth import decorators as auth_decorator
|
||||
|
||||
from django.db.models import Count, 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.forms.comment import AddNoteForm
|
||||
from core.models.notes import Notes
|
||||
|
||||
from settings.models.user_settings import UserSettings
|
||||
|
||||
from config_management.models.groups import ConfigGroups
|
||||
|
||||
|
||||
|
||||
class GroupIndexView(OrganizationPermission, generic.ListView):
|
||||
|
||||
context_object_name = "groups"
|
||||
|
||||
model = ConfigGroups
|
||||
|
||||
paginate_by = 10
|
||||
|
||||
permission_required = 'config_management.view_groups'
|
||||
|
||||
template_name = 'config_management/group_index.html.j2'
|
||||
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
context['content_title'] = 'Config Groups'
|
||||
|
||||
return context
|
||||
|
||||
|
||||
def get_queryset(self):
|
||||
|
||||
if self.request.user.is_superuser:
|
||||
|
||||
return self.model.objects.filter(parent=None).order_by('name')
|
||||
|
||||
else:
|
||||
|
||||
return self.model.objects.filter(Q(parent=None, organization__in=self.user_organizations()) | Q(parent=None, is_global = True)).order_by('name')
|
||||
|
||||
|
||||
|
||||
|
||||
class GroupAdd(OrganizationPermission, generic.CreateView):
|
||||
|
||||
fields = [
|
||||
'name',
|
||||
'parent',
|
||||
'organization',
|
||||
]
|
||||
|
||||
model = ConfigGroups
|
||||
|
||||
permission_required = [
|
||||
'config_management.add_groups',
|
||||
]
|
||||
|
||||
template_name = 'form.html.j2'
|
||||
|
||||
|
||||
def get_initial(self):
|
||||
|
||||
return {
|
||||
'organization': UserSettings.objects.get(user = self.request.user).default_organization
|
||||
}
|
||||
|
||||
|
||||
def get_success_url(self, **kwargs):
|
||||
|
||||
return reverse('Config Management:_group_index')
|
||||
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
context['content_title'] = 'New Group'
|
||||
|
||||
return context
|
||||
|
||||
|
||||
|
||||
class GroupView(OrganizationPermission, generic.UpdateView):
|
||||
|
||||
context_object_name = "group"
|
||||
|
||||
model = ConfigGroups
|
||||
|
||||
permission_required = [
|
||||
'config_management.view_groups',
|
||||
]
|
||||
|
||||
template_name = 'config_management/group.html.j2'
|
||||
|
||||
fields = [
|
||||
'name',
|
||||
'parent',
|
||||
'config',
|
||||
]
|
||||
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
context['child_groups'] = ConfigGroups.objects.filter(parent=self.kwargs['pk'])
|
||||
|
||||
context['notes_form'] = AddNoteForm(prefix='note')
|
||||
context['notes'] = Notes.objects.filter(config_group=self.kwargs['pk'])
|
||||
|
||||
context['model_pk'] = self.kwargs['pk']
|
||||
context['model_name'] = self.model._meta.verbose_name.replace(' ', '')
|
||||
|
||||
context['model_delete_url'] = reverse('Config Management:_group_delete', args=(self.kwargs['pk'],))
|
||||
|
||||
context['content_title'] = self.object.name
|
||||
|
||||
# if self.request.user.is_superuser:
|
||||
|
||||
# context['device_software'] = DeviceSoftware.objects.filter(
|
||||
# software=self.kwargs['pk']
|
||||
# ).order_by(
|
||||
# 'device',
|
||||
# 'organization'
|
||||
# )
|
||||
|
||||
# elif not self.request.user.is_superuser:
|
||||
# context['device_software'] = DeviceSoftware.objects.filter(
|
||||
# Q(device__in=self.user_organizations(),
|
||||
# software=self.kwargs['pk'])
|
||||
# ).order_by(
|
||||
# 'device',
|
||||
# 'organization'
|
||||
# )
|
||||
|
||||
return context
|
||||
|
||||
|
||||
@method_decorator(auth_decorator.permission_required("itam.change_software", raise_exception=True))
|
||||
def post(self, request, *args, **kwargs):
|
||||
|
||||
item = ConfigGroups.objects.get(pk=self.kwargs['pk'])
|
||||
|
||||
notes = AddNoteForm(request.POST, prefix='note')
|
||||
|
||||
if notes.is_bound and notes.is_valid() and notes.instance.note != '':
|
||||
|
||||
notes.instance.organization = item.organization
|
||||
notes.instance.config_group = item
|
||||
notes.instance.usercreated = request.user
|
||||
|
||||
notes.save()
|
||||
|
||||
return super().post(request, *args, **kwargs)
|
||||
|
||||
|
||||
def get_success_url(self, **kwargs):
|
||||
|
||||
return reverse('Config Management:_group_view', args=(self.kwargs['pk'],))
|
||||
|
||||
|
||||
|
||||
class GroupDelete(OrganizationPermission, generic.DeleteView):
|
||||
|
||||
model = ConfigGroups
|
||||
|
||||
permission_required = [
|
||||
'config_management.delete_groups',
|
||||
]
|
||||
|
||||
template_name = 'form.html.j2'
|
||||
|
||||
def get_success_url(self, **kwargs):
|
||||
|
||||
return reverse('Config Management:_group_index')
|
||||
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
context['content_title'] = 'Delete ' + self.object.name
|
||||
|
||||
return context
|
@ -6,7 +6,7 @@ class ConfigIndex(generic.View):
|
||||
|
||||
permission_required = 'itam.view_device'
|
||||
|
||||
template_name = 'form.html.j2'
|
||||
template_name = 'config_management/index.html.j2'
|
||||
|
||||
|
||||
def get(self, request):
|
20
app/core/migrations/0007_notes_config_group.py
Normal file
20
app/core/migrations/0007_notes_config_group.py
Normal file
@ -0,0 +1,20 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-02 14:48
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('config_management', '0001_initial'),
|
||||
('core', '0006_alter_history_user'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='notes',
|
||||
name='config_group',
|
||||
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='config_management.configgroups'),
|
||||
),
|
||||
]
|
@ -4,6 +4,8 @@ from django.db import models
|
||||
from access.fields import *
|
||||
from access.models import TenancyObject
|
||||
|
||||
from config_management.models.groups import ConfigGroups
|
||||
|
||||
from itam.models.device import Device
|
||||
from itam.models.software import Software
|
||||
from itam.models.operating_system import OperatingSystem
|
||||
@ -70,6 +72,14 @@ class Notes(NotesCommonFields):
|
||||
blank= True
|
||||
)
|
||||
|
||||
config_group = models.ForeignKey(
|
||||
ConfigGroups,
|
||||
on_delete=models.CASCADE,
|
||||
default = None,
|
||||
null = True,
|
||||
blank= True
|
||||
)
|
||||
|
||||
device = models.ForeignKey(
|
||||
Device,
|
||||
on_delete=models.CASCADE,
|
||||
|
@ -14,7 +14,7 @@ uritemplate==4.1.1
|
||||
coreapi==2.3.3
|
||||
|
||||
drf-spectacular==0.27.2
|
||||
drf-spectacular-sidecar==2024.6.1
|
||||
drf-spectacular[sidecar]==0.27.2
|
||||
|
||||
django_split_settings==1.3.1
|
||||
|
||||
|
Reference in New Issue
Block a user