diff --git a/backend/tprm/migrations/0003_entityassessment_representatives.py b/backend/tprm/migrations/0003_entityassessment_representatives.py
new file mode 100644
index 000000000..66d0fb8e1
--- /dev/null
+++ b/backend/tprm/migrations/0003_entityassessment_representatives.py
@@ -0,0 +1,24 @@
+# Generated by Django 5.1 on 2024-10-01 09:14
+
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("tprm", "0002_alter_entity_reference_link"),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="entityassessment",
+ name="representatives",
+ field=models.ManyToManyField(
+ blank=True,
+ related_name="entity_assessments",
+ to=settings.AUTH_USER_MODEL,
+ verbose_name="Representative",
+ ),
+ ),
+ ]
diff --git a/backend/tprm/models.py b/backend/tprm/models.py
index 6fe6815bd..9a5063f65 100644
--- a/backend/tprm/models.py
+++ b/backend/tprm/models.py
@@ -40,6 +40,12 @@ class Conclusion(models.TextChoices):
dependency = models.IntegerField(default=0, verbose_name=_("Dependency"))
maturity = models.IntegerField(default=0, verbose_name=_("Maturity"))
trust = models.IntegerField(default=0, verbose_name=_("Trust"))
+ representatives = models.ManyToManyField(
+ User,
+ blank=True,
+ verbose_name=_("Representative"),
+ related_name="entity_assessments",
+ )
entity = models.ForeignKey(
Entity,
on_delete=models.CASCADE,
diff --git a/backend/tprm/serializers.py b/backend/tprm/serializers.py
index 398b8ab19..b5fd5dbda 100644
--- a/backend/tprm/serializers.py
+++ b/backend/tprm/serializers.py
@@ -8,7 +8,6 @@
from django.contrib.auth import get_user_model
from tprm.models import Entity, EntityAssessment, Representative, Solution
from django.utils.translation import gettext_lazy as _
-from ciso_assistant.settings import EMAIL_HOST, EMAIL_HOST_RESCUE
import structlog
@@ -39,7 +38,7 @@ class EntityAssessmentReadSerializer(BaseModelSerializer):
entity = FieldsRelatedField()
folder = FieldsRelatedField()
solutions = FieldsRelatedField(many=True)
- authors = FieldsRelatedField(many=True)
+ representatives = FieldsRelatedField(many=True)
reviewers = FieldsRelatedField(many=True)
class Meta:
@@ -48,7 +47,7 @@ class Meta:
class EntityAssessmentWriteSerializer(BaseModelSerializer):
- create_audit = serializers.BooleanField(default=True)
+ create_audit = serializers.BooleanField(default=False)
framework = serializers.PrimaryKeyRelatedField(
queryset=Framework.objects.all(), required=False
)
@@ -82,23 +81,31 @@ def _create_or_update_audit(self, instance, audit_data):
],
)
- if not instance.compliance_assessment:
- enclave = Folder.objects.create(
- content_type=Folder.ContentType.ENCLAVE,
- name=f"{instance.project.name}/{instance.name}",
- parent_folder=instance.folder,
- )
- audit.folder = enclave
- audit.save()
+ enclave = Folder.objects.create(
+ content_type=Folder.ContentType.ENCLAVE,
+ name=f"{instance.project.name}/{instance.name}",
+ parent_folder=instance.folder,
+ )
+ audit.folder = enclave
+ audit.save()
audit.create_requirement_assessments()
- audit.authors.set(instance.authors.all())
audit.reviewers.set(instance.reviewers.all())
+ audit.authors.set(instance.representatives.all())
instance.compliance_assessment = audit
instance.save()
+ else:
+ if instance.compliance_assessment:
+ audit = instance.compliance_assessment
+ audit.reviewers.set(instance.reviewers.all())
+ audit.authors.set(instance.representatives.all())
+ instance.save()
def _assign_third_party_respondents(
- self, instance: EntityAssessment, third_party_users: set[User]
+ self,
+ instance: EntityAssessment,
+ third_party_users: set[User],
+ old_third_party_users: set[User] = set(),
):
if instance.compliance_assessment:
enclave = instance.compliance_assessment.folder
@@ -119,25 +126,32 @@ def _assign_third_party_respondents(
if not user.is_third_party:
logger.warning("User is not a third-party", user=user)
user.user_groups.add(respondents)
+ for user in old_third_party_users:
+ if not user.is_third_party:
+ logger.warning("User is not a third-party", user=user)
+ user.user_groups.remove(respondents)
def create(self, validated_data):
audit_data = self._extract_audit_data(validated_data)
instance = super().create(validated_data)
self._create_or_update_audit(instance, audit_data)
- self._assign_third_party_respondents(instance, set(instance.authors.all()))
+ self._assign_third_party_respondents(
+ instance, set(instance.representatives.all())
+ )
return instance
def update(self, instance: EntityAssessment, validated_data):
audit_data = self._extract_audit_data(validated_data)
- new_authors = set(validated_data.get("authors", [])) - set(
- instance.authors.all()
+ representatives = set(validated_data.get("representatives", []))
+ old_representatives = set(instance.representatives.all()) - set(
+ validated_data.get("representatives", [])
)
instance = super().update(instance, validated_data)
- if not instance.compliance_assessment:
- self._create_or_update_audit(instance, audit_data)
-
- self._assign_third_party_respondents(instance, new_authors)
+ self._create_or_update_audit(instance, audit_data)
+ self._assign_third_party_respondents(
+ instance, representatives, old_representatives
+ )
return instance
class Meta:
diff --git a/frontend/src/lib/components/Forms/ModelForm/EntityAssessmentForm.svelte b/frontend/src/lib/components/Forms/ModelForm/EntityAssessmentForm.svelte
index c349a41ba..c49383b70 100644
--- a/frontend/src/lib/components/Forms/ModelForm/EntityAssessmentForm.svelte
+++ b/frontend/src/lib/components/Forms/ModelForm/EntityAssessmentForm.svelte
@@ -144,11 +144,20 @@
multiple
options={getOptions({ objects: model.foreignKeys['authors'], label: 'email' })}
field="authors"
- helpText={m.entityAssessmentAuthorHelpText()}
cacheLock={cacheLocks['authors']}
bind:cachedValue={formDataCache['authors']}
label={m.authors()}
/>
+