diff --git a/app/app/helpers/merge_software.py b/app/app/helpers/merge_software.py new file mode 100644 index 00000000..7c02959b --- /dev/null +++ b/app/app/helpers/merge_software.py @@ -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 \ No newline at end of file diff --git a/app/config_management/forms/group/add_software.py b/app/config_management/forms/group/add_software.py new file mode 100644 index 00000000..9829ca50 --- /dev/null +++ b/app/config_management/forms/group/add_software.py @@ -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)) diff --git a/app/config_management/forms/group/change_software.py b/app/config_management/forms/group/change_software.py new file mode 100644 index 00000000..6bca5c33 --- /dev/null +++ b/app/config_management/forms/group/change_software.py @@ -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) + diff --git a/app/config_management/migrations/0004_configgroupsoftware.py b/app/config_management/migrations/0004_configgroupsoftware.py new file mode 100644 index 00000000..611b6be2 --- /dev/null +++ b/app/config_management/migrations/0004_configgroupsoftware.py @@ -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'], + }, + ), + ] diff --git a/app/config_management/models/groups.py b/app/config_management/models/groups.py index 38090dbc..7c37f00d 100644 --- a/app/config_management/models/groups.py +++ b/app/config_management/models/groups.py @@ -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 + ) + + diff --git a/app/config_management/templates/config_management/group.html.j2 b/app/config_management/templates/config_management/group.html.j2 index 0c47956d..4c76c426 100644 --- a/app/config_management/templates/config_management/group.html.j2 +++ b/app/config_management/templates/config_management/group.html.j2 @@ -120,6 +120,45 @@ Software + +
Name | +Category | +Action | +Desired Version | ++ + {% if softwares %} + {% for software in softwares %} + |
---|---|---|---|---|
{{ software.software }} | +{{ software.software.category }} | ++ {% 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 %} + | ++ {% if software.version %} + {{ software.version }} + {% else %} + - + {% endif %} + | ++ | Nothing Found | + {% endif %} +