feat(config_management): assign software action to config group

!22 #43
This commit is contained in:
2024-06-08 07:17:20 +09:30
parent ae81ee8863
commit 0c382a73e5
9 changed files with 388 additions and 3 deletions

View File

@ -0,0 +1,33 @@
def merge_software(software: list, new_software: list) -> list:
""" Merge two lists of software actions
Args:
software (list(dict)): Original list to merge over
new_software (list(dict)): new list to use to merge over
Returns:
list(dict): merged list of software actions
"""
merge_software = []
merge: dict = {}
for original in software:
merge.update({
original['name']: original
})
for new in new_software:
merge.update({
new['name']: new
})
for key, value in merge.items():
merge_software = merge_software + [ value ]
return merge_software

View File

@ -0,0 +1,21 @@
from django import forms
from django.db.models import Q
from config_management.models.groups import ConfigGroupSoftware
from itam.models.software import Software
class SoftwareAdd(forms.ModelForm):
class Meta:
model = ConfigGroupSoftware
fields = [
'software',
'action'
]
def __init__(self, *args, **kwargs):
organizations = kwargs.pop('organizations')
super().__init__(*args, **kwargs)
self.fields['software'].queryset = Software.objects.filter(Q(organization_id__in=organizations) | Q(is_global = True))

View File

@ -0,0 +1,22 @@
from django import forms
from django.db.models import Q
from config_management.models.groups import ConfigGroupSoftware
from itam.models.software import Software, SoftwareVersion
class SoftwareUpdate(forms.ModelForm):
class Meta:
model = ConfigGroupSoftware
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)

View File

@ -0,0 +1,36 @@
# Generated by Django 5.0.6 on 2024-06-07 21:43
import access.fields
import access.models
import django.db.models.deletion
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('access', '0003_alter_team_organization'),
('config_management', '0003_alter_configgrouphosts_organization_and_more'),
('itam', '0013_alter_device_organization_and_more'),
]
operations = [
migrations.CreateModel(
name='ConfigGroupSoftware',
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)),
('action', models.CharField(blank=True, choices=[('1', 'Install'), ('0', 'Remove')], default=None, max_length=1, null=True)),
('config_group', models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to='config_management.configgroups')),
('organization', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists])),
('software', models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to='itam.software')),
('version', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='itam.softwareversion')),
],
options={
'ordering': ['-action', 'software'],
},
),
]

View File

@ -7,9 +7,12 @@ from django.forms import ValidationError
from access.fields import *
from access.models import TenancyObject
from app.helpers.merge_software import merge_software
from core.mixin.history_save import SaveHistory
from itam.models.device import Device
from itam.models.device import Device, DeviceSoftware
from itam.models.software import Software, SoftwareVersion
@ -125,6 +128,43 @@ class ConfigGroups(GroupsCommonFields, SaveHistory):
config.update(self.config)
softwares = ConfigGroupSoftware.objects.filter(config_group=self.id)
software_actions = {
"software": []
}
for software in softwares:
if software.action:
if int(software.action) == 1:
state = 'present'
elif int(software.action) == 0:
state = 'absent'
software_action = {
"name": software.software.slug,
"state": state
}
if software.version:
software_action['version'] = software.version.name
software_actions['software'] = software_actions['software'] + [ software_action ]
if len(software_actions['software']) > 0: # don't add empty software as it prevents parent software from being added
if 'software' not in config.keys():
config['software'] = []
config['software'] = merge_software(config['software'], software_actions['software'])
return json.dumps(config)
@ -177,3 +217,51 @@ class ConfigGroupHosts(GroupsCommonFields, SaveHistory):
null = False,
blank= False
)
class ConfigGroupSoftware(GroupsCommonFields, SaveHistory):
""" A way to configure software to install/remove per config group """
class Meta:
ordering = [
'-action',
'software'
]
config_group = models.ForeignKey(
ConfigGroups,
on_delete=models.CASCADE,
default = None,
null = False,
blank= False
)
software = models.ForeignKey(
Software,
on_delete=models.CASCADE,
default = None,
null = False,
blank= False
)
action = models.CharField(
max_length=1,
choices=DeviceSoftware.Actions,
default=None,
null=True,
blank = True,
)
version = models.ForeignKey(
SoftwareVersion,
on_delete=models.CASCADE,
default = None,
null = True,
blank= True
)

View File

@ -120,6 +120,45 @@
Software
</h3>
<input type="button" value="Add Software Action" onclick="window.location='{% url 'Config Management:_group_software_add' model_pk %}';">
<table>
<thead>
<th>Name</th>
<th>Category</th>
<th>Action</th>
<th>Desired Version</th>
<th>&nbsp;</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>{{ software.software.category }}</td>
<td>
{% url 'Config Management:_group_software_change' group_id=group.id pk=software.id as icon_link %}
{% if software.get_action_display == 'Install' %}
{% include 'icons/success_text.html.j2' with icon_text=software.get_action_display icon_link=icon_link %}
{% elif software.get_action_display == 'Remove'%}
{% include 'icons/cross_text.html.j2' with icon_text=software.get_action_display %}
{% else %}
{% include 'icons/add_link.html.j2' with icon_text='Add' %}
{% endif %}
</td>
<td>
{% if software.version %}
{{ software.version }}
{% else %}
-
{% endif %}
</td>
<td>&nbsp;</td>
</tr>
{% endfor %}
{% else %}
<td colspan="5">Nothing Found</td>
{% endif %}
</table>
</div>
<div id="Configuration" class="tabcontent">

View File

@ -1,6 +1,7 @@
from django.urls import path
from config_management.views.groups import GroupIndexView, GroupAdd, GroupDelete, GroupView, GroupHostAdd, GroupHostDelete
from config_management.views.groups.groups import GroupIndexView, GroupAdd, GroupDelete, GroupView, GroupHostAdd, GroupHostDelete
from config_management.views.groups.software import GroupSoftwareAdd, GroupSoftwareChange, GroupSoftwareDelete
app_name = "Config Management"
@ -8,9 +9,14 @@ urlpatterns = [
path('group', GroupIndexView.as_view(), name='Groups'),
path('group/add', GroupAdd.as_view(), name='_group_add'),
path('group/<int:pk>', GroupView.as_view(), name='_group_view'),
path('group/<int:group_id>/child', GroupAdd.as_view(), name='_group_add_child'),
path('group/<int:pk>/delete', GroupDelete.as_view(), name='_group_delete'),
path("group/<int:pk>/software/add", GroupSoftwareAdd.as_view(), name="_group_software_add"),
path("group/<int:group_id>/software/<int:pk>", GroupSoftwareChange.as_view(), name="_group_software_change"),
path("group/<int:group_id>/software/<int:pk>/delete", GroupSoftwareDelete.as_view(), name="_group_software_delete"),
path('group/<int:group_id>/host', GroupHostAdd.as_view(), name='_group_add_host'),
path('group/<int:group_id>/host/<int:pk>/delete', GroupHostDelete.as_view(), name='_group_delete_host'),

View File

@ -16,7 +16,7 @@ from itam.models.device import Device
from settings.models.user_settings import UserSettings
from config_management.forms.group_hosts import ConfigGroupHostsForm
from config_management.models.groups import ConfigGroups, ConfigGroupHosts
from config_management.models.groups import ConfigGroups, ConfigGroupHosts, ConfigGroupSoftware
@ -146,6 +146,9 @@ class GroupView(OrganizationPermission, generic.UpdateView):
context['model_delete_url'] = reverse('Config Management:_group_delete', args=(self.kwargs['pk'],))
softwares = ConfigGroupSoftware.objects.filter(config_group=self.kwargs['pk'])[:50]
context['softwares'] = softwares
context['content_title'] = self.object.name
# if self.request.user.is_superuser:

View File

@ -0,0 +1,137 @@
from django.urls import reverse
from django.views import generic
from access.mixin import OrganizationPermission
from itam.models.software import Software
from config_management.forms.group.add_software import SoftwareAdd
from config_management.forms.group.change_software import SoftwareUpdate
from config_management.models.groups import ConfigGroups, ConfigGroupSoftware
class GroupSoftwareAdd(OrganizationPermission, generic.CreateView):
form_class = SoftwareAdd
model = ConfigGroupSoftware
parent_model = ConfigGroups
permission_required = [
'config_management.add_configgroupsoftware',
]
template_name = 'form.html.j2'
def form_valid(self, form):
config_group = ConfigGroups.objects.get(pk=self.kwargs['pk'])
form.instance.organization_id = config_group.organization.id
form.instance.config_group = config_group
software = Software.objects.get(pk=form.instance.software.id)
if ConfigGroupSoftware.objects.filter(
config_group=config_group,
software=software
).exists():
existing_object = ConfigGroupSoftware.objects.get(
device=device,
software=software
)
existing_object.action = form.instance.action
existing_object.save()
return HttpResponseRedirect(self.get_success_url())
else:
return super().form_valid(form)
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
obj = ConfigGroups.objects.get(pk=self.kwargs['pk'])
kwargs['organizations'] = [ obj.organization.id ]
return kwargs
def get_success_url(self, **kwargs):
return reverse('Config Management:_group_view', args=(self.kwargs['pk'],))
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['content_title'] = 'Add Software Action'
return context
class GroupSoftwareChange(OrganizationPermission, generic.UpdateView):
form_class = SoftwareUpdate
model = ConfigGroupSoftware
permission_required = [
'config_management.change_configgroupsoftware'
]
template_name = 'form.html.j2'
def form_valid(self, form):
config_group = ConfigGroups.objects.get(pk=self.kwargs['group_id'])
form.instance.organization_id = config_group.organization.id
form.instance.config_group = config_group
return super().form_valid(form)
def get_success_url(self, **kwargs):
return reverse('Config Management:_group_view', args=(self.kwargs['group_id'],))
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['model_delete_url'] = reverse('Config Management:_group_software_delete', args=(self.kwargs['group_id'], self.kwargs['pk'],))
context['content_title'] = 'Edit Software Action'
return context
class GroupSoftwareDelete(OrganizationPermission, generic.DeleteView):
model = ConfigGroupSoftware
permission_required = [
'config_management.delete_configgroupsoftware',
]
template_name = 'form.html.j2'
def get_success_url(self, **kwargs):
return reverse('Config Management:_group_view', args=(self.kwargs['pk'],))
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['content_title'] = 'Delete '
return context