feat(config_management): assign software action to config group
!22 #43
This commit is contained in:
33
app/app/helpers/merge_software.py
Normal file
33
app/app/helpers/merge_software.py
Normal 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
|
21
app/config_management/forms/group/add_software.py
Normal file
21
app/config_management/forms/group/add_software.py
Normal 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))
|
22
app/config_management/forms/group/change_software.py
Normal file
22
app/config_management/forms/group/change_software.py
Normal 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)
|
||||
|
36
app/config_management/migrations/0004_configgroupsoftware.py
Normal file
36
app/config_management/migrations/0004_configgroupsoftware.py
Normal 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'],
|
||||
},
|
||||
),
|
||||
]
|
@ -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
|
||||
)
|
||||
|
||||
|
||||
|
@ -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> </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> </td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<td colspan="5">Nothing Found</td>
|
||||
{% endif %}
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="Configuration" class="tabcontent">
|
||||
|
@ -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'),
|
||||
|
||||
|
@ -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:
|
||||
|
137
app/config_management/views/groups/software.py
Normal file
137
app/config_management/views/groups/software.py
Normal 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
|
Reference in New Issue
Block a user