2025-09-22 08:35:11 +02:00
|
|
|
|
from django.db import models
|
|
|
|
|
from django.conf import settings
|
|
|
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
|
from multiselectfield import MultiSelectField
|
2025-09-22 09:44:51 +02:00
|
|
|
|
from .likelihood_choice import LikelihoodChoice
|
2025-09-22 08:35:11 +02:00
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# Risk
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
class Risk(models.Model):
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
verbose_name = _("Risk")
|
|
|
|
|
verbose_name_plural = _("Risks")
|
|
|
|
|
|
|
|
|
|
STATUS_CHOICES = [
|
|
|
|
|
("open", _("Open")),
|
|
|
|
|
("in_progress", _("In Progress")),
|
|
|
|
|
("closed", _("Closed")),
|
|
|
|
|
("review_required", _("Review required")),
|
|
|
|
|
]
|
|
|
|
|
IMPACT_CHOICES = [
|
|
|
|
|
(1, _("Very Low (< 1,000 € – minor operational impact)")),
|
|
|
|
|
(2, _("Low (1,000–5,000 € – local impact)")),
|
|
|
|
|
(3, _("High (5,000–15,000 € – team-level impact)")),
|
|
|
|
|
(4, _("Severe (50,000–100,000 € – regional impact)")),
|
|
|
|
|
(5, _("Critical (> 100,000 € – existential threat)")),
|
|
|
|
|
]
|
|
|
|
|
CIA_CHOICES = [
|
|
|
|
|
("1", _("Confidentiality")),
|
|
|
|
|
("2", _("Integrity")),
|
|
|
|
|
("3", _("Availability")),
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
# Basic information
|
|
|
|
|
title = models.CharField(_("Title"), max_length=255)
|
|
|
|
|
description = models.TextField(_("Description"), max_length=225, blank=True, null=True)
|
|
|
|
|
asset = models.CharField(_("Asset"), max_length=255, blank=True, null=True)
|
|
|
|
|
process = models.CharField(_("Process"), max_length=255, blank=True, null=True)
|
|
|
|
|
category = models.CharField(_("Category"), max_length=255, blank=True, null=True)
|
|
|
|
|
created_at = models.DateTimeField(_("Created at"), auto_now_add=True)
|
|
|
|
|
updated_at = models.DateTimeField(_("Updated at"), auto_now=True)
|
|
|
|
|
effects = models.TextField(_("Effects"), blank=True, null=True)
|
|
|
|
|
status = models.CharField(
|
|
|
|
|
_("Status"),
|
|
|
|
|
max_length=20,
|
|
|
|
|
choices=STATUS_CHOICES,
|
|
|
|
|
default="open",
|
|
|
|
|
db_index=True,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# CIA Protection Goals
|
|
|
|
|
cia = MultiSelectField(choices=CIA_CHOICES, max_length=100, blank=True, null=True)
|
|
|
|
|
|
|
|
|
|
# Risk evaluation before controls
|
2025-09-22 09:44:51 +02:00
|
|
|
|
likelihood = models.ForeignKey(
|
|
|
|
|
LikelihoodChoice,
|
|
|
|
|
on_delete=models.PROTECT,
|
|
|
|
|
verbose_name=_("Likelihood"),
|
|
|
|
|
related_name="risks",
|
|
|
|
|
)
|
2025-09-22 08:35:11 +02:00
|
|
|
|
impact = models.IntegerField(choices=IMPACT_CHOICES, default=1)
|
|
|
|
|
|
|
|
|
|
# Calculated fields
|
|
|
|
|
score = models.IntegerField(editable=False)
|
|
|
|
|
level = models.CharField(max_length=50, editable=False)
|
|
|
|
|
|
|
|
|
|
# Ownership
|
|
|
|
|
owner = models.ForeignKey(
|
|
|
|
|
settings.AUTH_USER_MODEL,
|
|
|
|
|
on_delete=models.SET_NULL,
|
|
|
|
|
null=True,
|
|
|
|
|
related_name="owned_risks"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Reminder / follow-up date
|
|
|
|
|
follow_up = models.DateField(blank=True, null=True)
|
|
|
|
|
|
|
|
|
|
def save(self, *args, **kwargs):
|
|
|
|
|
# Mark for review if likelihood/impact changed
|
|
|
|
|
if self.pk:
|
|
|
|
|
old = Risk.objects.get(pk=self.pk)
|
|
|
|
|
if old.likelihood != self.likelihood or old.impact != self.impact:
|
|
|
|
|
self.review_required = True
|
|
|
|
|
self.status = "review_required"
|
|
|
|
|
|
|
|
|
|
# Calculate risk score and level
|
2025-09-22 09:44:51 +02:00
|
|
|
|
self.score = self.likelihood.value * self.impact
|
2025-09-22 08:35:11 +02:00
|
|
|
|
if self.score <= 4:
|
|
|
|
|
self.level = "Low"
|
|
|
|
|
elif self.score <= 8:
|
|
|
|
|
self.level = "Medium"
|
|
|
|
|
elif self.score <= 12:
|
|
|
|
|
self.level = "High"
|
|
|
|
|
else:
|
|
|
|
|
self.level = "Critical"
|
|
|
|
|
super().save(*args, **kwargs)
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
return f"{self.title} (Score: {self.score}, Level: {self.level})"
|