diff --git a/app/app/settings.py b/app/app/settings.py
index 74b8a14e..0f4d2922 100644
--- a/app/app/settings.py
+++ b/app/app/settings.py
@@ -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',
]
diff --git a/app/config_management/migrations/0001_initial.py b/app/config_management/migrations/0001_initial.py
new file mode 100644
index 00000000..71bbd5a9
--- /dev/null
+++ b/app/config_management/migrations/0001_initial.py
@@ -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',
+ },
+ ),
+ ]
diff --git a/app/config_management/models.py b/app/config_management/models.py
deleted file mode 100644
index 137941ff..00000000
--- a/app/config_management/models.py
+++ /dev/null
@@ -1 +0,0 @@
-from django.db import models
diff --git a/app/config_management/models/groups.py b/app/config_management/models/groups.py
new file mode 100644
index 00000000..95ff4a66
--- /dev/null
+++ b/app/config_management/models/groups.py
@@ -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)
+
+
diff --git a/app/config_management/templates/config_management/group.html.j2 b/app/config_management/templates/config_management/group.html.j2
new file mode 100644
index 00000000..557454db
--- /dev/null
+++ b/app/config_management/templates/config_management/group.html.j2
@@ -0,0 +1,107 @@
+{% extends 'base.html.j2' %}
+
+{% block body %}
+
+
+
+
+
+
+
+ Back to {% if group.parent %}Parent{% else %}Groups{% endif %}
+
+
Details
+
Child Groups
+
Software
+
Notes
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/app/config_management/templates/config_management/group_index.html.j2 b/app/config_management/templates/config_management/group_index.html.j2
new file mode 100644
index 00000000..c6c8f4d7
--- /dev/null
+++ b/app/config_management/templates/config_management/group_index.html.j2
@@ -0,0 +1,47 @@
+{% extends 'base.html.j2' %}
+
+{% block body %}
+
+
+
+
+ Name
+ Organization
+ Sub-Groups
+
+
+ {% if groups %}
+ {% for group in groups %}
+
+ {{ group.name }}
+ {{ group.organization }}
+ {{ group.count_children }}
+
+
+ {% endfor %}
+ {% else %}
+
+ Nothing Found
+
+ {% endif %}
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/app/config_management/templates/config_management/index.html.j2 b/app/config_management/templates/config_management/index.html.j2
new file mode 100644
index 00000000..0833030d
--- /dev/null
+++ b/app/config_management/templates/config_management/index.html.j2
@@ -0,0 +1,9 @@
+{% extends 'base.html.j2' %}
+
+{% block body %}
+what will go here?
+
+
+{% endblock %}
diff --git a/app/config_management/urls.py b/app/config_management/urls.py
index 518b51e2..15504c5c 100644
--- a/app/config_management/urls.py
+++ b/app/config_management/urls.py
@@ -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/', GroupView.as_view(), name='_group_view'),
+ path('group//delete', GroupDelete.as_view(), name='_group_delete'),
+
]
diff --git a/app/config_management/views/groups.py b/app/config_management/views/groups.py
new file mode 100644
index 00000000..5ddeffb1
--- /dev/null
+++ b/app/config_management/views/groups.py
@@ -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
diff --git a/app/config_management/views.py b/app/config_management/views/index.py
similarity index 85%
rename from app/config_management/views.py
rename to app/config_management/views/index.py
index 5cb69d88..0ca73bff 100644
--- a/app/config_management/views.py
+++ b/app/config_management/views/index.py
@@ -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):
diff --git a/app/core/migrations/0007_notes_config_group.py b/app/core/migrations/0007_notes_config_group.py
new file mode 100644
index 00000000..f02152c2
--- /dev/null
+++ b/app/core/migrations/0007_notes_config_group.py
@@ -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'),
+ ),
+ ]
diff --git a/app/core/models/notes.py b/app/core/models/notes.py
index 3bb36a75..22225f0d 100644
--- a/app/core/models/notes.py
+++ b/app/core/models/notes.py
@@ -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,
diff --git a/requirements.txt b/requirements.txt
index 3ef32299..a289859f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -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