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 %} + + + +
+ + + + + + + +
+ +
+
+

Details

+ + {% csrf_token %} + {{ form }} + {% include 'icons/issue_link.html.j2' with issue=13 %}
+ + + + +
+ +
+

Child Groups

+ + + + + + + {% if child_groups %} + {% for group in child_groups %} + + + + + + {% endfor %} + {% else %} + + + + {% endif %} +
NameSub-Groups 
{{ group.name }}{{ group.count_children }} 
Nothing Found
+ +
+ + +
+

+ Software +

+ +
+ +
+

+ Notes +

+ {{ notes_form }} + +
+ {% if notes %} + {% for note in notes %} + {% include 'note.html.j2' %} + {% endfor %} + {% endif %} +
+ +
+ +
+ +{% 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 %} + + + + + + + + + + {% if groups %} + {% for group in groups %} + + + + + + + {% endfor %} + {% else %} + + + + {% endif %} +
NameOrganizationSub-Groups 
{{ group.name }}{{ group.organization }}{{ group.count_children }} 
Nothing Found
+
+ + +{% 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