Change Impact_Choices to DB
This commit is contained in:
parent
a5a31f4dcf
commit
a314816150
10 changed files with 138 additions and 68 deletions
BIN
db.sqlite3
BIN
db.sqlite3
Binary file not shown.
|
@ -6,6 +6,7 @@ from django.utils.translation import gettext_lazy as _
|
|||
|
||||
from .models import (
|
||||
Control,
|
||||
ImpactChoice,
|
||||
Incident,
|
||||
LikelihoodChoice,
|
||||
Notification,
|
||||
|
@ -239,3 +240,10 @@ class UserAdmin(BaseUserAdmin):
|
|||
@admin.register(LikelihoodChoice)
|
||||
class LikelihoodChoiceAdmin(admin.ModelAdmin):
|
||||
list_display = ("value", "name", "description")
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# ImpactChoice
|
||||
# ---------------------------------------------------------------------------
|
||||
@admin.register(ImpactChoice)
|
||||
class ImpactChoiceAdmin(admin.ModelAdmin):
|
||||
list_display = ("value", "name", "description")
|
|
@ -1,5 +1,6 @@
|
|||
from .auditlog import AuditLog
|
||||
from .control import Control
|
||||
from .impact_choice import ImpactChoice
|
||||
from .incident import Incident
|
||||
from .likelihood_choice import LikelihoodChoice
|
||||
from .notification import Notification
|
||||
|
@ -11,4 +12,4 @@ from .risk import Risk
|
|||
from .user import User
|
||||
|
||||
|
||||
__all__ = ["AuditLog", "Control", "Incident", "LikelihoodChoice", "Notification", "NotificationKind", "NotificationPreference", "NotificationRule", "ResidualRisk", "Risk", "User"]
|
||||
__all__ = ["AuditLog", "Control", "ImpactChoice", "Incident", "LikelihoodChoice", "Notification", "NotificationKind", "NotificationPreference", "NotificationRule", "ResidualRisk", "Risk", "User"]
|
13
risks/models/impact_choice.py
Normal file
13
risks/models/impact_choice.py
Normal file
|
@ -0,0 +1,13 @@
|
|||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
class ImpactChoice(models.Model):
|
||||
"""
|
||||
Impact choices for Risks and Controls.
|
||||
"""
|
||||
name = models.CharField(_("Impact Name"), max_length=50)
|
||||
description = models.TextField(_("Description"), blank=True, null=True)
|
||||
value = models.IntegerField(_("Numeric Value"), unique=True, default=1)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.value} - {self.name} ({self.description})"
|
|
@ -1,5 +1,6 @@
|
|||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from .impact_choice import ImpactChoice
|
||||
from .likelihood_choice import LikelihoodChoice
|
||||
from .risk import Risk
|
||||
|
||||
|
@ -21,7 +22,12 @@ class ResidualRisk(models.Model):
|
|||
verbose_name=_("Likelihood"),
|
||||
related_name="residual_risks",
|
||||
)
|
||||
impact = models.IntegerField(choices=Risk.IMPACT_CHOICES, default=1)
|
||||
impact = models.ForeignKey(
|
||||
ImpactChoice,
|
||||
on_delete=models.PROTECT,
|
||||
verbose_name=_("Impact"),
|
||||
related_name="residual_risks",
|
||||
)
|
||||
score = models.IntegerField(editable=False)
|
||||
level = models.CharField(max_length=50, editable=False)
|
||||
review_required = models.BooleanField(default=False)
|
||||
|
|
|
@ -2,6 +2,7 @@ from django.db import models
|
|||
from django.conf import settings
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from multiselectfield import MultiSelectField
|
||||
from .impact_choice import ImpactChoice
|
||||
from .likelihood_choice import LikelihoodChoice
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
@ -19,13 +20,6 @@ class Risk(models.Model):
|
|||
("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")),
|
||||
|
@ -59,7 +53,12 @@ class Risk(models.Model):
|
|||
verbose_name=_("Likelihood"),
|
||||
related_name="risks",
|
||||
)
|
||||
impact = models.IntegerField(choices=IMPACT_CHOICES, default=1)
|
||||
impact = models.ForeignKey(
|
||||
ImpactChoice,
|
||||
on_delete=models.PROTECT,
|
||||
verbose_name=_("impact"),
|
||||
related_name="risks",
|
||||
)
|
||||
|
||||
# Calculated fields
|
||||
score = models.IntegerField(editable=False)
|
||||
|
|
|
@ -1,16 +1,19 @@
|
|||
from django import template
|
||||
from django.utils.html import format_html
|
||||
from ..models import Control, Incident, Risk, LikelihoodChoice
|
||||
from ..models import Control, ImpactChoice, Incident, Risk, LikelihoodChoice
|
||||
|
||||
register = template.Library()
|
||||
|
||||
_RISK_STATUS_MAP = dict(Risk.STATUS_CHOICES)
|
||||
_CONTROL_STATUS_MAP = dict(Control.STATUS_CHOICES)
|
||||
_INCIDENT_STATUS_MAP = dict(Incident.STATUS_CHOICES)
|
||||
_IMPACT_LABELS = dict(Risk.IMPACT_CHOICES)
|
||||
LEVEL_ID_MAP = {"Low": 1, "Medium": 2, "High": 3, "Critical": 4}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Likelihood
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Likelihood Label
|
||||
def get_likelihood_label(likelihood_obj):
|
||||
if not likelihood_obj:
|
||||
return ""
|
||||
|
@ -22,6 +25,7 @@ def likelihood_id_label(likelihood):
|
|||
return f"{likelihood.value} ({likelihood.name})"
|
||||
return likelihood
|
||||
|
||||
# Likelihood Value
|
||||
def get_likelihood_value(likelihood):
|
||||
if isinstance(likelihood, LikelihoodChoice):
|
||||
return likelihood.value
|
||||
|
@ -31,6 +35,7 @@ def get_likelihood_value(likelihood):
|
|||
def likelihood_value(likelihood_obj):
|
||||
return get_likelihood_value(likelihood_obj)
|
||||
|
||||
# Likelihood Class
|
||||
def get_likelihood_class(likelihood):
|
||||
value = get_likelihood_value(likelihood)
|
||||
LIKELIHOOD_MAP = {
|
||||
|
@ -45,6 +50,52 @@ def get_likelihood_class(likelihood):
|
|||
def likelihood_class(likelihood):
|
||||
return get_likelihood_class(likelihood)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Impact
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Impact Label
|
||||
def get_impact_label(impact_obj):
|
||||
if not impact_obj:
|
||||
return ""
|
||||
return f"{impact_obj.value} ({impact_obj.name})"
|
||||
|
||||
@register.filter
|
||||
def impact_id_label(impact):
|
||||
if isinstance(impact, ImpactChoice):
|
||||
return f"{impact.value} ({impact.name})"
|
||||
return impact
|
||||
|
||||
# Impact Value
|
||||
def get_impact_value(impact):
|
||||
if isinstance(impact, ImpactChoice):
|
||||
return impact.value
|
||||
return impact
|
||||
|
||||
@register.filter
|
||||
def impact_value(impact_obj):
|
||||
return get_impact_value(impact_obj)
|
||||
|
||||
# Impact Class
|
||||
def get_impact_class(impact):
|
||||
value = get_impact_value(impact)
|
||||
IMPACT_MAP = {
|
||||
1: "is-control-verylow",
|
||||
2: "is-control-low",
|
||||
3: "is-control-mid",
|
||||
4: "is-control-high",
|
||||
5: "is-control-veryhigh",
|
||||
}
|
||||
return IMPACT_MAP.get(value, "is-light")
|
||||
|
||||
@register.filter
|
||||
def impact_class(impact):
|
||||
return get_impact_class(impact)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Unsorted
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@register.simple_tag
|
||||
def sort_url(request, field, current_sort, current_dir):
|
||||
query = request.GET.copy()
|
||||
|
@ -90,15 +141,6 @@ def _short(label: str) -> str:
|
|||
return label.split(sep, 1)[0].strip()
|
||||
return label.strip()
|
||||
|
||||
@register.filter
|
||||
def impact_id_label(val):
|
||||
try:
|
||||
i = int(val)
|
||||
except (TypeError, ValueError):
|
||||
return ""
|
||||
label = _IMPACT_LABELS.get(i, "")
|
||||
short = _short(str(label)) if label else ""
|
||||
return format_html("{} ({})", i, short) if label else format_html("{}", i)
|
||||
|
||||
@register.filter
|
||||
def risk_status_label(code):
|
||||
|
@ -139,13 +181,6 @@ LEVEL_MAP = {
|
|||
"Critical": "is-control-veryhigh",
|
||||
}
|
||||
|
||||
@register.filter
|
||||
def impact_class(val):
|
||||
try:
|
||||
return IMPACT_MAP.get(int(val), "is-light")
|
||||
except (TypeError, ValueError):
|
||||
return "is-light"
|
||||
|
||||
@register.filter
|
||||
def level_class(level):
|
||||
return LEVEL_MAP.get(str(level), "is-light")
|
||||
|
|
|
@ -13,7 +13,7 @@ from rest_framework import viewsets
|
|||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
from .forms import RiskStatusForm, ControlStatusForm, IncidentStatusForm, ResidualReviewForm
|
||||
from .models import AuditLog, Risk, Control, ResidualRisk, AuditLog, Incident, LikelihoodChoice, Notification
|
||||
from .models import AuditLog, ImpactChoice, Risk, Control, ResidualRisk, AuditLog, Incident, LikelihoodChoice, Notification
|
||||
from .serializers import (
|
||||
ControlSerializer, RiskSerializer, ResidualRiskSerializer,
|
||||
UserSerializer, AuditSerializer, IncidentSerializer,
|
||||
|
@ -466,18 +466,24 @@ def update_residual_review(request, risk_id):
|
|||
def risk_matrix(request):
|
||||
"""Show gross/net risk matrix."""
|
||||
risks = Risk.objects.select_related("owner", "residual_risk").all()
|
||||
impacts = sorted(Risk.IMPACT_CHOICES, key=lambda x: x[0])
|
||||
impacts = ImpactChoice.objects.all().order_by('value')
|
||||
likelihoods = LikelihoodChoice.objects.all().order_by('value')
|
||||
|
||||
# Erstelle die Matrizen mit den Werten der LikelihoodChoice-Objekte
|
||||
gross_matrix = {i: {likelihood.value: [] for likelihood in likelihoods} for i, _ in impacts}
|
||||
net_matrix = {i: {likelihood.value: [] for likelihood in likelihoods} for i, _ in impacts}
|
||||
gross_matrix = {
|
||||
impact.value: {likelihood.value: [] for likelihood in likelihoods}
|
||||
for impact in impacts
|
||||
}
|
||||
net_matrix = {
|
||||
impact.value: {likelihood.value: [] for likelihood in likelihoods}
|
||||
for impact in impacts
|
||||
}
|
||||
|
||||
for r in risks:
|
||||
gross_matrix[r.impact][r.likelihood.value].append(r)
|
||||
gross_matrix[r.impact.value][r.likelihood.value].append(r)
|
||||
rr = getattr(r, "residual_risk", None)
|
||||
if rr:
|
||||
net_matrix[rr.impact][rr.likelihood.value].append(r)
|
||||
net_matrix[rr.impact.value][rr.likelihood.value].append(r)
|
||||
|
||||
return render(request, "risks/risk_matrix.html", {
|
||||
"impacts": impacts,
|
||||
|
|
|
@ -107,8 +107,8 @@
|
|||
<div class="column is-half">
|
||||
<button class="risk-chip {{ risk.impact|impact_class }}" type="button">
|
||||
<span class="chip-head">{% trans "Impact" %}</span>
|
||||
<span class="chip-id">{{ risk.impact }}</span>
|
||||
<span class="chip-label">{{ risk.get_impact_display }}</span>
|
||||
<span class="chip-id">{{ risk.impact.value }} - {{ risk.impact.name }}</span>
|
||||
<span class="chip-label">{{ risk.impact.description }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
@ -153,8 +153,8 @@
|
|||
<div class="column is-half">
|
||||
<button class="risk-chip {{ risk.residual_risk.impact|impact_class }}" type="button">
|
||||
<span class="chip-head">{% trans "Impact" %}</span>
|
||||
<span class="chip-id">{{ risk.residual_risk.impact }}</span>
|
||||
<span class="chip-label">{{ risk.residual_risk.get_impact_display }}</span>
|
||||
<span class="chip-id">{{ risk.residual_risk.impact.value }} - {{ risk.residual_risk.impact.name }}</span>
|
||||
<span class="chip-label">{{ risk.residual_risk.impact.description }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -17,16 +17,18 @@
|
|||
<tr>
|
||||
<th class="has-text-left">{% trans "Impact" %} * {% trans "Likelihood" %}</th>
|
||||
{% for likelihood in likelihoods %}
|
||||
<th class="{{ likelihood.value|likelihood_class|to_bg }}">{{ likelihood.value }} ({{ likelihood.name }}) <br> {{ likelihood.description }}</th>
|
||||
<th class="{{ likelihood.value|likelihood_class|to_bg }}">
|
||||
{{ likelihood.value }} ({{ likelihood.name }}) <br> {{ likelihood.description }}
|
||||
</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for i_val, i_label in impacts reversed %}
|
||||
{% for impact in impacts reversed %}
|
||||
<tr>
|
||||
<th class="has-text-left {{ i_val|impact_class|to_bg }}">{{ i_label }}</th>
|
||||
<th class="has-text-left {{ impact.value|impact_class|to_bg }}">{{ impact.value }} - {{ impact.name }}<br>{{ impact.description }}</th>
|
||||
{% for likelihood in likelihoods %}
|
||||
{% with s=i_val|mul:likelihood.value %}
|
||||
{% with s=impact.value|mul:likelihood.value %}
|
||||
<td class="risk-matrix-cell {{ s|score_bg_class }}">
|
||||
<div class="is-flex is-justify-content-center is-align-items-center">
|
||||
{% if s <= 4 %}
|
||||
|
@ -71,18 +73,18 @@
|
|||
<tr>
|
||||
<th class="has-text-left">{% trans "Impact" %} / {% trans "Likelihood" %}</th>
|
||||
{% for likelihood in likelihoods %}
|
||||
<th class="{{ likelihood.value|likelihood_class|to_bg }}">{{ likelihood.value }} ({{ likelihood.name }})</th>
|
||||
<th class="{{ likelihood.value|likelihood_class|to_bg }}">{{ likelihood.value }} ({{ likelihood.name }}) <br> {{ likelihood.description }}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for i_val, i_label in impacts reversed %}
|
||||
{% for impact in impacts reversed %}
|
||||
<tr>
|
||||
<th class="has-text-left {{ i_val|impact_class|to_bg }}">{{ i_label }}</th>
|
||||
<th class="has-text-left {{ impact.value|impact_class|to_bg }}">{{ impact.value }} - {{ impact.name }}<br>{{ impact.description }}</th>
|
||||
{% for likelihood in likelihoods %}
|
||||
{% with row=gross_matrix|dict_get:i_val %}
|
||||
{% with row=gross_matrix|dict_get:impact.value %}
|
||||
{% with cell=row|dict_get:likelihood.value %}
|
||||
{% with s=i_val|mul:likelihood.value %}
|
||||
{% with s=impact.value|mul:likelihood.value %}
|
||||
<td class="risk-matrix-cell {{ likelihood|likelihood_class }}">
|
||||
{% if cell %}
|
||||
<ul class="risk-cell-list">
|
||||
|
@ -112,33 +114,33 @@
|
|||
<tr>
|
||||
<th class="has-text-left">{% trans "Impact" %} / {% trans "Likelihood" %}</th>
|
||||
{% for likelihood in likelihoods %}
|
||||
<th class="{{ likelihood.value|likelihood_class|to_bg }}">{{ likelihood.value }} ({{ likelihood.name }})</th>
|
||||
<th class="{{ likelihood.value|likelihood_class|to_bg }}">{{ likelihood.value }} ({{ likelihood.name }}) <br> {{ likelihood.description }}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for i_val, i_label in impacts reversed %}
|
||||
{% for impact in impacts reversed %}
|
||||
<tr>
|
||||
<th class="has-text-left {{ i_val|impact_class|to_bg }}">{{ i_label }}</th>
|
||||
<th class="has-text-left {{ impact.value|impact_class|to_bg }}">{{ impact.value }} - {{ impact.name }}<br>{{ impact.description }}</th>
|
||||
{% for likelihood in likelihoods %}
|
||||
{% with row=net_matrix|dict_get:i_val %}
|
||||
{% with cell=row|dict_get:likelihood.value %}
|
||||
{% with s=i_val|mul:likelihood.value %}
|
||||
<td class="risk-matrix-cell {{ likelihood|likelihood_class }}">
|
||||
{% if cell %}
|
||||
<ul class="risk-cell-list">
|
||||
{% for risk in cell %}
|
||||
<li style="list-style:none;">
|
||||
<a href="{% url 'risks:show_risk' risk.id %}" class="tag">{{ risk.title }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<span class="has-text-grey">–</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
{% with row=net_matrix|dict_get:impact.value %}
|
||||
{% with cell=row|dict_get:likelihood.value %}
|
||||
{% with s=impact.value|mul:likelihood.value %}
|
||||
<td class="risk-matrix-cell {{ likelihood|likelihood_class }}">
|
||||
{% if cell %}
|
||||
<ul class="risk-cell-list">
|
||||
{% for risk in cell %}
|
||||
<li style="list-style:none;">
|
||||
<a href="{% url 'risks:show_risk' risk.id %}" class="tag">{{ risk.title }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<span class="has-text-grey">–</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
</tr>
|
||||
|
|
Loading…
Add table
Reference in a new issue