From 23d5e27524af4a6857afd437120be6f4fdee2768 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Thu, 12 Sep 2024 17:03:49 +0200 Subject: [PATCH 1/8] Write Folder.get_root_folder_id method This fixes the '"Global" is not a valid UUID' error that may be encountered during migrations when adding FolderMixin to an existing model, and thus spares me a few headaches. --- backend/iam/models.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/backend/iam/models.py b/backend/iam/models.py index 252cf1000..dc2167139 100644 --- a/backend/iam/models.py +++ b/backend/iam/models.py @@ -61,6 +61,11 @@ def get_root_folder() -> Self: """class function for general use""" return _get_root_folder() + @staticmethod + def get_root_folder_id() -> uuid.UUID: + """class function for general use""" + return Folder.get_root_folder().id + class ContentType(models.TextChoices): """content type for a folder""" @@ -173,7 +178,7 @@ class FolderMixin(models.Model): Folder, on_delete=models.CASCADE, related_name="%(class)s_folder", - default=Folder.get_root_folder, + default=Folder.get_root_folder_id, ) class Meta: From b7cf5d55e59864878618c4c32c1dd69d55af9df1 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Thu, 12 Sep 2024 17:04:12 +0200 Subject: [PATCH 2/8] chore: Make migrations --- .../0002_alter_globalsettings_folder.py | 21 +++++++++++ ...er_alter_roleassignment_folder_and_more.py | 35 +++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 backend/global_settings/migrations/0002_alter_globalsettings_folder.py create mode 100644 backend/iam/migrations/0006_alter_role_folder_alter_roleassignment_folder_and_more.py diff --git a/backend/global_settings/migrations/0002_alter_globalsettings_folder.py b/backend/global_settings/migrations/0002_alter_globalsettings_folder.py new file mode 100644 index 000000000..b3692d4ed --- /dev/null +++ b/backend/global_settings/migrations/0002_alter_globalsettings_folder.py @@ -0,0 +1,21 @@ +# Generated by Django 5.1.1 on 2024-09-12 15:00 + +import django.db.models.deletion +import iam.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('global_settings', '0001_initial'), + ('iam', '0006_alter_role_folder_alter_roleassignment_folder_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='globalsettings', + name='folder', + field=models.ForeignKey(default=iam.models.Folder.get_root_folder_id, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_folder', to='iam.folder'), + ), + ] diff --git a/backend/iam/migrations/0006_alter_role_folder_alter_roleassignment_folder_and_more.py b/backend/iam/migrations/0006_alter_role_folder_alter_roleassignment_folder_and_more.py new file mode 100644 index 000000000..d0d3cb00e --- /dev/null +++ b/backend/iam/migrations/0006_alter_role_folder_alter_roleassignment_folder_and_more.py @@ -0,0 +1,35 @@ +# Generated by Django 5.1.1 on 2024-09-12 15:00 + +import django.db.models.deletion +import iam.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('iam', '0005_alter_user_managers'), + ] + + operations = [ + migrations.AlterField( + model_name='role', + name='folder', + field=models.ForeignKey(default=iam.models.Folder.get_root_folder_id, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_folder', to='iam.folder'), + ), + migrations.AlterField( + model_name='roleassignment', + name='folder', + field=models.ForeignKey(default=iam.models.Folder.get_root_folder_id, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_folder', to='iam.folder'), + ), + migrations.AlterField( + model_name='user', + name='folder', + field=models.ForeignKey(default=iam.models.Folder.get_root_folder_id, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_folder', to='iam.folder'), + ), + migrations.AlterField( + model_name='usergroup', + name='folder', + field=models.ForeignKey(default=iam.models.Folder.get_root_folder_id, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_folder', to='iam.folder'), + ), + ] From 0f66f5707c3d12ee111656b018916de07428186a Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Thu, 12 Sep 2024 17:04:19 +0200 Subject: [PATCH 3/8] Add folder field to Assessments --- ...t_folder_riskassessment_folder_and_more.py | 187 ++++++++++++++++++ backend/core/models.py | 2 +- backend/core/serializers.py | 2 + 3 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 backend/core/migrations/0025_complianceassessment_folder_riskassessment_folder_and_more.py diff --git a/backend/core/migrations/0025_complianceassessment_folder_riskassessment_folder_and_more.py b/backend/core/migrations/0025_complianceassessment_folder_riskassessment_folder_and_more.py new file mode 100644 index 000000000..7a49eeda9 --- /dev/null +++ b/backend/core/migrations/0025_complianceassessment_folder_riskassessment_folder_and_more.py @@ -0,0 +1,187 @@ +# Generated by Django 5.1.1 on 2024-09-12 15:00 + +import django.db.models.deletion +import iam.models +from django.db import migrations, models + + +def set_assessment_folder(apps, schema_editor): + RiskAssessment = apps.get_model("core", "RiskAssessment") + ComplianceAssessment = apps.get_model("core", "ComplianceAssessment") + for risk_assessment in RiskAssessment.objects.all(): + risk_assessment.folder = risk_assessment.project.folder + risk_assessment.save() + for compliance_assessment in ComplianceAssessment.objects.all(): + compliance_assessment.folder = compliance_assessment.project.folder + compliance_assessment.save() + + +class Migration(migrations.Migration): + dependencies = [ + ("core", "0024_appliedcontrol_owner"), + ("iam", "0006_alter_role_folder_alter_roleassignment_folder_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="complianceassessment", + name="folder", + field=models.ForeignKey( + default=iam.models.Folder.get_root_folder_id, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_folder", + to="iam.folder", + ), + ), + migrations.AddField( + model_name="riskassessment", + name="folder", + field=models.ForeignKey( + default=iam.models.Folder.get_root_folder_id, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_folder", + to="iam.folder", + ), + ), + migrations.AlterField( + model_name="appliedcontrol", + name="folder", + field=models.ForeignKey( + default=iam.models.Folder.get_root_folder_id, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_folder", + to="iam.folder", + ), + ), + migrations.AlterField( + model_name="asset", + name="folder", + field=models.ForeignKey( + default=iam.models.Folder.get_root_folder_id, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_folder", + to="iam.folder", + ), + ), + migrations.AlterField( + model_name="evidence", + name="folder", + field=models.ForeignKey( + default=iam.models.Folder.get_root_folder_id, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_folder", + to="iam.folder", + ), + ), + migrations.AlterField( + model_name="framework", + name="folder", + field=models.ForeignKey( + default=iam.models.Folder.get_root_folder_id, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_folder", + to="iam.folder", + ), + ), + migrations.AlterField( + model_name="loadedlibrary", + name="folder", + field=models.ForeignKey( + default=iam.models.Folder.get_root_folder_id, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_folder", + to="iam.folder", + ), + ), + migrations.AlterField( + model_name="project", + name="folder", + field=models.ForeignKey( + default=iam.models.Folder.get_root_folder_id, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_folder", + to="iam.folder", + ), + ), + migrations.AlterField( + model_name="referencecontrol", + name="folder", + field=models.ForeignKey( + default=iam.models.Folder.get_root_folder_id, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_folder", + to="iam.folder", + ), + ), + migrations.AlterField( + model_name="requirementassessment", + name="folder", + field=models.ForeignKey( + default=iam.models.Folder.get_root_folder_id, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_folder", + to="iam.folder", + ), + ), + migrations.AlterField( + model_name="requirementmappingset", + name="folder", + field=models.ForeignKey( + default=iam.models.Folder.get_root_folder_id, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_folder", + to="iam.folder", + ), + ), + migrations.AlterField( + model_name="requirementnode", + name="folder", + field=models.ForeignKey( + default=iam.models.Folder.get_root_folder_id, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_folder", + to="iam.folder", + ), + ), + migrations.AlterField( + model_name="riskacceptance", + name="folder", + field=models.ForeignKey( + default=iam.models.Folder.get_root_folder_id, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_folder", + to="iam.folder", + ), + ), + migrations.AlterField( + model_name="riskmatrix", + name="folder", + field=models.ForeignKey( + default=iam.models.Folder.get_root_folder_id, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_folder", + to="iam.folder", + ), + ), + migrations.AlterField( + model_name="storedlibrary", + name="folder", + field=models.ForeignKey( + default=iam.models.Folder.get_root_folder_id, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_folder", + to="iam.folder", + ), + ), + migrations.AlterField( + model_name="threat", + name="folder", + field=models.ForeignKey( + default=iam.models.Folder.get_root_folder_id, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_folder", + to="iam.folder", + ), + ), + migrations.RunPython(set_assessment_folder), + ] diff --git a/backend/core/models.py b/backend/core/models.py index 64e3a6506..5fbe7c14c 100644 --- a/backend/core/models.py +++ b/backend/core/models.py @@ -1364,7 +1364,7 @@ def save(self, *args, **kwargs): ########################### Secondary objects ######################### -class Assessment(NameDescriptionMixin, ETADueDateMixin): +class Assessment(NameDescriptionMixin, ETADueDateMixin, FolderMixin): class Status(models.TextChoices): PLANNED = "planned", _("Planned") IN_PROGRESS = "in_progress", _("In progress") diff --git a/backend/core/serializers.py b/backend/core/serializers.py index 74ffd00a2..baaf1456e 100644 --- a/backend/core/serializers.py +++ b/backend/core/serializers.py @@ -188,6 +188,7 @@ class Meta: class RiskAssessmentReadSerializer(AssessmentReadSerializer): str = serializers.CharField(source="__str__") project = FieldsRelatedField(["id", "folder"]) + folder = FieldsRelatedField() risk_scenarios = FieldsRelatedField(many=True) risk_scenarios_count = serializers.IntegerField(source="risk_scenarios.count") risk_matrix = FieldsRelatedField() @@ -535,6 +536,7 @@ class Meta: class ComplianceAssessmentReadSerializer(AssessmentReadSerializer): project = FieldsRelatedField(["id", "folder"]) + folder = FieldsRelatedField() framework = FieldsRelatedField( ["id", "min_score", "max_score", "implementation_groups_definition", "ref_id"] ) From 83f752c9b8c3a64b7045756b0244f7ad1e9f8129 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Thu, 12 Sep 2024 17:58:37 +0200 Subject: [PATCH 4/8] Add folder foreign key to risk assessment and compliance assessment --- frontend/src/lib/utils/crud.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/lib/utils/crud.ts b/frontend/src/lib/utils/crud.ts index da9cf3434..7e41aba00 100644 --- a/frontend/src/lib/utils/crud.ts +++ b/frontend/src/lib/utils/crud.ts @@ -175,6 +175,7 @@ export const URL_MODEL_MAP: ModelMap = { verboseName: 'Risk assessment', verboseNamePlural: 'Risk assessments', foreignKeyFields: [ + { field: 'folder', urlModel: 'folders', urlParams: 'content_type=DO' }, { field: 'project', urlModel: 'projects' }, { field: 'authors', urlModel: 'users' }, { field: 'reviewers', urlModel: 'users' }, @@ -387,6 +388,7 @@ export const URL_MODEL_MAP: ModelMap = { verboseName: 'Compliance assessment', verboseNamePlural: 'Compliance assessments', foreignKeyFields: [ + { field: 'folder', urlModel: 'folders', urlParams: 'content_type=DO' }, { field: 'project', urlModel: 'projects' }, { field: 'framework', urlModel: 'frameworks' }, { field: 'authors', urlModel: 'users' }, From 516cea381a8d67dfd2a0891903d139ceb9df6359 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Thu, 12 Sep 2024 17:59:00 +0200 Subject: [PATCH 5/8] Set assessment default folder to that of its project --- backend/core/models.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/backend/core/models.py b/backend/core/models.py index 5fbe7c14c..34c5356c1 100644 --- a/backend/core/models.py +++ b/backend/core/models.py @@ -31,7 +31,7 @@ from django.utils.html import format_html from django.utils.translation import get_language from django.utils.translation import gettext_lazy as _ -from iam.models import FolderMixin, PublishInRootFolderMixin +from iam.models import Folder, FolderMixin, PublishInRootFolderMixin from library.helpers import update_translations, update_translations_in_object from structlog import get_logger @@ -1409,6 +1409,11 @@ class Status(models.TextChoices): class Meta: abstract = True + def save(self, *args, **kwargs) -> None: + if not self.folder or self.folder == Folder.get_root_folder(): + self.folder = self.project.folder + return super().save(*args, **kwargs) + class RiskAssessment(Assessment): risk_matrix = models.ForeignKey( From 68ef4f079017da0eb7ad0f1a3ecf65d7b025cdc3 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Thu, 12 Sep 2024 18:06:29 +0200 Subject: [PATCH 6/8] Wrap get_root_folder_id body in try/except block This is done to avoid weird states during the first startup where this may be accessed before the root folder actually exists --- backend/iam/models.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/backend/iam/models.py b/backend/iam/models.py index dc2167139..ef770e718 100644 --- a/backend/iam/models.py +++ b/backend/iam/models.py @@ -64,7 +64,10 @@ def get_root_folder() -> Self: @staticmethod def get_root_folder_id() -> uuid.UUID: """class function for general use""" - return Folder.get_root_folder().id + try: + return uuid.UUID(_get_root_folder().id) + except: + return _get_root_folder() class ContentType(models.TextChoices): """content type for a folder""" From 74919b336df6bc83b300c86b830827f2dc57ee59 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Fri, 13 Sep 2024 10:34:59 +0200 Subject: [PATCH 7/8] chore: Run ruff format --- .../0002_alter_globalsettings_folder.py | 16 ++++--- ...er_alter_roleassignment_folder_and_more.py | 47 +++++++++++++------ 2 files changed, 43 insertions(+), 20 deletions(-) diff --git a/backend/global_settings/migrations/0002_alter_globalsettings_folder.py b/backend/global_settings/migrations/0002_alter_globalsettings_folder.py index b3692d4ed..c5c6fd351 100644 --- a/backend/global_settings/migrations/0002_alter_globalsettings_folder.py +++ b/backend/global_settings/migrations/0002_alter_globalsettings_folder.py @@ -6,16 +6,20 @@ class Migration(migrations.Migration): - dependencies = [ - ('global_settings', '0001_initial'), - ('iam', '0006_alter_role_folder_alter_roleassignment_folder_and_more'), + ("global_settings", "0001_initial"), + ("iam", "0006_alter_role_folder_alter_roleassignment_folder_and_more"), ] operations = [ migrations.AlterField( - model_name='globalsettings', - name='folder', - field=models.ForeignKey(default=iam.models.Folder.get_root_folder_id, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_folder', to='iam.folder'), + model_name="globalsettings", + name="folder", + field=models.ForeignKey( + default=iam.models.Folder.get_root_folder_id, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_folder", + to="iam.folder", + ), ), ] diff --git a/backend/iam/migrations/0006_alter_role_folder_alter_roleassignment_folder_and_more.py b/backend/iam/migrations/0006_alter_role_folder_alter_roleassignment_folder_and_more.py index d0d3cb00e..9226e38b4 100644 --- a/backend/iam/migrations/0006_alter_role_folder_alter_roleassignment_folder_and_more.py +++ b/backend/iam/migrations/0006_alter_role_folder_alter_roleassignment_folder_and_more.py @@ -6,30 +6,49 @@ class Migration(migrations.Migration): - dependencies = [ - ('iam', '0005_alter_user_managers'), + ("iam", "0005_alter_user_managers"), ] operations = [ migrations.AlterField( - model_name='role', - name='folder', - field=models.ForeignKey(default=iam.models.Folder.get_root_folder_id, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_folder', to='iam.folder'), + model_name="role", + name="folder", + field=models.ForeignKey( + default=iam.models.Folder.get_root_folder_id, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_folder", + to="iam.folder", + ), ), migrations.AlterField( - model_name='roleassignment', - name='folder', - field=models.ForeignKey(default=iam.models.Folder.get_root_folder_id, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_folder', to='iam.folder'), + model_name="roleassignment", + name="folder", + field=models.ForeignKey( + default=iam.models.Folder.get_root_folder_id, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_folder", + to="iam.folder", + ), ), migrations.AlterField( - model_name='user', - name='folder', - field=models.ForeignKey(default=iam.models.Folder.get_root_folder_id, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_folder', to='iam.folder'), + model_name="user", + name="folder", + field=models.ForeignKey( + default=iam.models.Folder.get_root_folder_id, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_folder", + to="iam.folder", + ), ), migrations.AlterField( - model_name='usergroup', - name='folder', - field=models.ForeignKey(default=iam.models.Folder.get_root_folder_id, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_folder', to='iam.folder'), + model_name="usergroup", + name="folder", + field=models.ForeignKey( + default=iam.models.Folder.get_root_folder_id, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_folder", + to="iam.folder", + ), ), ] From 4c5877b74b39494fe9beec535c6c8923dbed1dc0 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Fri, 13 Sep 2024 10:36:50 +0200 Subject: [PATCH 8/8] Remove unnecessary try/except --- backend/iam/models.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/backend/iam/models.py b/backend/iam/models.py index ef770e718..bb3d04abc 100644 --- a/backend/iam/models.py +++ b/backend/iam/models.py @@ -390,10 +390,7 @@ def get_full_name(self) -> str: def get_short_name(self) -> str: """get user's short name (i.e. first_name or email before @))""" - try: - return self.first_name if self.first_name else self.email.split("@")[0] - except: - return "" + return self.first_name if self.first_name else self.email.split("@")[0] def mailing(self, email_template_name, subject, pk=False): """