Add Ui fix. Add Links in Notifications

This commit is contained in:
Kevin Heyer 2025-09-16 14:15:04 +02:00
parent 77f08ed440
commit c25402bc98
13 changed files with 315 additions and 222 deletions

Binary file not shown.

View file

@ -2,7 +2,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: wira-risk-management\n" "Project-Id-Version: wira-risk-management\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-15 11:24+0200\n" "POT-Creation-Date: 2025-09-16 14:08+0200\n"
"PO-Revision-Date: 2025-09-09 13:45+0200\n" "PO-Revision-Date: 2025-09-09 13:45+0200\n"
"Last-Translator: Kevin Heyer <kevin@example.com>\n" "Last-Translator: Kevin Heyer <kevin@example.com>\n"
"Language-Team: German\n" "Language-Team: German\n"
@ -25,7 +25,7 @@ msgstr "Admin"
msgid "Risks" msgid "Risks"
msgstr "Risiken" msgstr "Risiken"
#: risks/admin.py:34 risks/models.py:184 templates/base.html:54 #: risks/admin.py:34 risks/models.py:191 templates/base.html:54
#: templates/risks/item_control.html:5 templates/risks/list_controls.html:5 #: templates/risks/item_control.html:5 templates/risks/list_controls.html:5
msgid "Controls" msgid "Controls"
msgstr "Maßnahmen" msgstr "Maßnahmen"
@ -38,7 +38,7 @@ msgstr "Restrisiken"
msgid "Reviews" msgid "Reviews"
msgstr "Prüfung" msgstr "Prüfung"
#: risks/admin.py:37 risks/models.py:255 templates/base.html:55 #: risks/admin.py:37 risks/models.py:262 templates/base.html:55
#: templates/risks/item_incident.html:5 templates/risks/item_risk.html:14 #: templates/risks/item_incident.html:5 templates/risks/item_risk.html:14
#: templates/risks/list_incidents.html:5 templates/risks/list_incidents.html:18 #: templates/risks/list_incidents.html:5 templates/risks/list_incidents.html:18
msgid "Incidents" msgid "Incidents"
@ -48,7 +48,7 @@ msgstr "Vorfälle"
msgid "Users" msgid "Users"
msgstr "Benutzer" msgstr "Benutzer"
#: risks/admin.py:166 risks/models.py:362 templates/risks/item_control.html:96 #: risks/admin.py:166 risks/models.py:370 templates/risks/item_control.html:96
#: templates/risks/item_incident.html:88 templates/risks/item_risk.html:245 #: templates/risks/item_incident.html:88 templates/risks/item_risk.html:245
msgid "User" msgid "User"
msgstr "Benutzer" msgstr "Benutzer"
@ -137,11 +137,11 @@ msgstr "Risiko"
msgid "Open" msgid "Open"
msgstr "Offen" msgstr "Offen"
#: risks/models.py:54 risks/models.py:259 #: risks/models.py:54 risks/models.py:266
msgid "In Progress" msgid "In Progress"
msgstr "In Bearbeitung" msgstr "In Bearbeitung"
#: risks/models.py:55 risks/models.py:260 #: risks/models.py:55 risks/models.py:267
msgid "Closed" msgid "Closed"
msgstr "Geschlossen" msgstr "Geschlossen"
@ -182,26 +182,26 @@ msgid "Critical (> 100,000 € existential threat)"
msgstr "Kritisch (> 100.000 € existenzielle Bedrohung)" msgstr "Kritisch (> 100.000 € existenzielle Bedrohung)"
#: risks/models.py:72 templates/risks/dashboard.html:57 #: risks/models.py:72 templates/risks/dashboard.html:57
#: templates/risks/list_risks.html:112 #: templates/risks/list_risks.html:112 templates/risks/list_risks.html:143
msgid "Confidentiality" msgid "Confidentiality"
msgstr "Vertraulichkeit" msgstr "Vertraulichkeit"
#: risks/models.py:73 templates/risks/dashboard.html:63 #: risks/models.py:73 templates/risks/dashboard.html:63
#: templates/risks/list_risks.html:113 #: templates/risks/list_risks.html:113 templates/risks/list_risks.html:150
msgid "Integrity" msgid "Integrity"
msgstr "Integrität" msgstr "Integrität"
#: risks/models.py:74 templates/risks/dashboard.html:69 #: risks/models.py:74 templates/risks/dashboard.html:69
#: templates/risks/list_risks.html:114 #: templates/risks/list_risks.html:114 templates/risks/list_risks.html:157
msgid "Availability" msgid "Availability"
msgstr "Verfügbarkeit" msgstr "Verfügbarkeit"
#: risks/models.py:78 risks/models.py:194 risks/models.py:263 #: risks/models.py:78 risks/models.py:201 risks/models.py:270
#: templates/risks/item_risk.html:188 #: templates/risks/item_risk.html:188
msgid "Title" msgid "Title"
msgstr "Titel" msgstr "Titel"
#: risks/models.py:79 risks/models.py:264 templates/risks/item_control.html:52 #: risks/models.py:79 risks/models.py:271 templates/risks/item_control.html:52
#: templates/risks/item_incident.html:44 #: templates/risks/item_incident.html:44
msgid "Description" msgid "Description"
msgstr "Beschreibung" msgstr "Beschreibung"
@ -238,176 +238,176 @@ msgstr "Aktualisiert am"
msgid "Effects" msgid "Effects"
msgstr "Auswirkungen" msgstr "Auswirkungen"
#: risks/models.py:140 #: risks/models.py:147
msgid "Residual Risk" msgid "Residual Risk"
msgstr "Restrisiko" msgstr "Restrisiko"
#: risks/models.py:141 #: risks/models.py:148
msgid "Residual Risks" msgid "Residual Risks"
msgstr "Restrisiken" msgstr "Restrisiken"
#: risks/models.py:183 templates/risks/item_control.html:33 #: risks/models.py:190 templates/risks/item_control.html:33
#: templates/risks/list_controls.html:18 templates/risks/list_controls.html:97 #: templates/risks/list_controls.html:18 templates/risks/list_controls.html:97
msgid "Control" msgid "Control"
msgstr "Maßnahme" msgstr "Maßnahme"
#: risks/models.py:187 #: risks/models.py:194
msgid "Planned" msgid "Planned"
msgstr "Geplant" msgstr "Geplant"
#: risks/models.py:188 #: risks/models.py:195
msgid "In progress" msgid "In progress"
msgstr "In Bearbeitung" msgstr "In Bearbeitung"
#: risks/models.py:189 #: risks/models.py:196
msgid "Completed" msgid "Completed"
msgstr "Abgeschlossen" msgstr "Abgeschlossen"
#: risks/models.py:190 #: risks/models.py:197
msgid "Verified" msgid "Verified"
msgstr "Verifiziert" msgstr "Verifiziert"
#: risks/models.py:191 #: risks/models.py:198
msgid "Rejected" msgid "Rejected"
msgstr "Abgelehnt" msgstr "Abgelehnt"
#: risks/models.py:222 #: risks/models.py:229
msgid "Auditlog" msgid "Auditlog"
msgstr "Audit-Log" msgstr "Audit-Log"
#: risks/models.py:223 #: risks/models.py:230
msgid "Auditlogs" msgid "Auditlogs"
msgstr "Audit-Logs" msgstr "Audit-Logs"
#: risks/models.py:254 templates/risks/item_incident.html:33 #: risks/models.py:261 templates/risks/item_incident.html:33
#: templates/risks/item_risk.html:218 templates/risks/list_incidents.html:97 #: templates/risks/item_risk.html:218 templates/risks/list_incidents.html:97
msgid "Incident" msgid "Incident"
msgstr "Vorfall" msgstr "Vorfall"
#: risks/models.py:258 #: risks/models.py:265
msgid "Opened" msgid "Opened"
msgstr "Eröffnet" msgstr "Eröffnet"
#: risks/models.py:265 #: risks/models.py:272
msgid "Date reported" msgid "Date reported"
msgstr "Meldedatum" msgstr "Meldedatum"
#: risks/models.py:268 templates/risks/item_incident.html:34 #: risks/models.py:275 templates/risks/item_incident.html:34
#: templates/risks/list_incidents.html:63 #: templates/risks/list_incidents.html:63
#: templates/risks/list_incidents.html:101 #: templates/risks/list_incidents.html:101
msgid "Reported by" msgid "Reported by"
msgstr "Gemeldet von" msgstr "Gemeldet von"
#: risks/models.py:284 #: risks/models.py:291
msgid "Risk created" msgid "Risk created"
msgstr "Risiko erstellt" msgstr "Risiko erstellt"
#: risks/models.py:285 #: risks/models.py:292
msgid "Risk updated" msgid "Risk updated"
msgstr "Risiko Aktualisiert" msgstr "Risiko Aktualisiert"
#: risks/models.py:286 #: risks/models.py:293
msgid "Risk deleted" msgid "Risk deleted"
msgstr "Risiko gelöscht" msgstr "Risiko gelöscht"
#: risks/models.py:287 #: risks/models.py:294
msgid "Risk review required" msgid "Risk review required"
msgstr "Risikoprüfung nötig" msgstr "Risikoprüfung nötig"
#: risks/models.py:288 #: risks/models.py:295
msgid "Risk review completed" msgid "Risk review completed"
msgstr "Risikoprüfung Abgeschlossen" msgstr "Risikoprüfung Abgeschlossen"
#: risks/models.py:290 #: risks/models.py:297
msgid "Control created" msgid "Control created"
msgstr "Maßnahme erstellt" msgstr "Maßnahme erstellt"
#: risks/models.py:291 #: risks/models.py:298
msgid "Control updated" msgid "Control updated"
msgstr "Maßnahme Aktualisiert" msgstr "Maßnahme Aktualisiert"
#: risks/models.py:292 #: risks/models.py:299
msgid "Control deleted" msgid "Control deleted"
msgstr "Maßnahme '{title}' gelöscht" msgstr "Maßnahme '{title}' gelöscht"
#: risks/models.py:294 #: risks/models.py:301
msgid "Residual created" msgid "Residual created"
msgstr "Restrisiko erstellt" msgstr "Restrisiko erstellt"
#: risks/models.py:295 #: risks/models.py:302
msgid "Residual updated" msgid "Residual updated"
msgstr "Restrisiko Aktualisiert" msgstr "Restrisiko Aktualisiert"
#: risks/models.py:296 #: risks/models.py:303
msgid "Residual deleted" msgid "Residual deleted"
msgstr "Restrisiko gelöscht" msgstr "Restrisiko gelöscht"
#: risks/models.py:297 #: risks/models.py:304
msgid "Residual review required" msgid "Residual review required"
msgstr "Restrisikoprüfung nötig" msgstr "Restrisikoprüfung nötig"
#: risks/models.py:298 #: risks/models.py:305
msgid "Residual review completed" msgid "Residual review completed"
msgstr "Restrisiko geprüft" msgstr "Restrisiko geprüft"
#: risks/models.py:300 #: risks/models.py:307
msgid "Incident created" msgid "Incident created"
msgstr "Vorfall erstellt" msgstr "Vorfall erstellt"
#: risks/models.py:301 #: risks/models.py:308
msgid "Incident updated" msgid "Incident updated"
msgstr "Vorfall Aktualisiert" msgstr "Vorfall Aktualisiert"
#: risks/models.py:302 #: risks/models.py:309
msgid "Incident deleted" msgid "Incident deleted"
msgstr "Vorfall gelöscht" msgstr "Vorfall gelöscht"
#: risks/models.py:304 #: risks/models.py:311
msgid "User created" msgid "User created"
msgstr "Benutzer erstellt" msgstr "Benutzer erstellt"
#: risks/models.py:305 #: risks/models.py:312
msgid "User deleted" msgid "User deleted"
msgstr "Benutzer gelöscht" msgstr "Benutzer gelöscht"
#: risks/models.py:314 risks/utils.py:142 #: risks/models.py:321 risks/utils.py:79
msgid "Notification" msgid "Notification"
msgstr "Nachricht" msgstr "Nachricht"
#: risks/models.py:315 templates/base.html:96 #: risks/models.py:322 templates/base.html:96
#: templates/risks/notifications.html:4 #: templates/risks/notifications.html:4
msgid "Notifications" msgid "Notifications"
msgstr "Nachrichten" msgstr "Nachrichten"
#: risks/models.py:411 #: risks/models.py:419
msgid "Notification rule" msgid "Notification rule"
msgstr "Benachrichtigungsregel" msgstr "Benachrichtigungsregel"
#: risks/models.py:412 #: risks/models.py:420
msgid "Notification rules" msgid "Notification rules"
msgstr "Benachrichtigungsregeln" msgstr "Benachrichtigungsregeln"
#: risks/models.py:415 #: risks/models.py:423
msgid "Event" msgid "Event"
msgstr "Aktion" msgstr "Aktion"
#: risks/models.py:420 #: risks/models.py:428
msgid "Show in app" msgid "Show in app"
msgstr "Zeige in der WebApp" msgstr "Zeige in der WebApp"
#: risks/models.py:421 #: risks/models.py:429
msgid "Send via email" msgid "Send via email"
msgstr "Sende via E-Mail" msgstr "Sende via E-Mail"
#: risks/models.py:425 #: risks/models.py:433
msgid "Send to owner/responsible/reporter (if available)" msgid "Send to owner/responsible/reporter (if available)"
msgstr "Sende an Risikoeigner/Verantwortliche/Melder (Wenn vorhanden)" msgstr "Sende an Risikoeigner/Verantwortliche/Melder (Wenn vorhanden)"
#: risks/models.py:428 #: risks/models.py:436
msgid "Send to all staff" msgid "Send to all staff"
msgstr "Sende an alle App-Mitarbeiter" msgstr "Sende an alle App-Mitarbeiter"
#: risks/models.py:430 #: risks/models.py:438
msgid "Extra recipients (emails, comma or newline separated)" msgid "Extra recipients (emails, comma or newline separated)"
msgstr "Zusätzliche Empfänger (E-Mails, durch Komma oder Zeilenumbruch getrennt)" msgstr "Zusätzliche Empfänger (E-Mails, durch Komma oder Zeilenumbruch getrennt)"
@ -426,108 +426,108 @@ msgstr "Benutzer '{u}' löschte"
msgid "Risk created: {t}" msgid "Risk created: {t}"
msgstr "Risiko erstellt: {t}" msgstr "Risiko erstellt: {t}"
#: risks/signals.py:121 #: risks/signals.py:122
#, python-brace-format #, python-brace-format
msgid "Risk updated: {t}" msgid "Risk updated: {t}"
msgstr "Risiko Aktualisiert: {t}" msgstr "Risiko Aktualisiert: {t}"
#: risks/signals.py:134 #: risks/signals.py:136
#, python-brace-format #, python-brace-format
msgid "Risk deleted: {t}" msgid "Risk deleted: {t}"
msgstr "Risiko gelöscht: {t}" msgstr "Risiko gelöscht: {t}"
#: risks/signals.py:181 #: risks/signals.py:184
#, python-brace-format #, python-brace-format
msgid "Control {e}: {t}" msgid "Control {e}: {t}"
msgstr "Maßnahme {e}: {t}" msgstr "Maßnahme {e}: {t}"
#: risks/signals.py:182 risks/signals.py:317 #: risks/signals.py:185 risks/signals.py:326
msgid "created" msgid "created"
msgstr "erstellt" msgstr "erstellt"
#: risks/signals.py:182 risks/signals.py:317 #: risks/signals.py:185 risks/signals.py:326
msgid "updated" msgid "updated"
msgstr "Aktualisiert" msgstr "Aktualisiert"
#: risks/signals.py:196 #: risks/signals.py:200
#, python-brace-format #, python-brace-format
msgid "Control deleted: {t}" msgid "Control deleted: {t}"
msgstr "Maßnahme gelöscht: {t}" msgstr "Maßnahme gelöscht: {t}"
#: risks/signals.py:213 #: risks/signals.py:218
#, python-brace-format #, python-brace-format
msgid "Residual review required for risk '{t}' due to control change" msgid "Residual review required for risk '{t}' due to control change"
msgstr "Restrisikoprüfung nötig für das Risiko: '{t}', da Maßnahmen geändert wurden" msgstr "Restrisikoprüfung nötig für das Risiko: '{t}', da Maßnahmen geändert wurden"
#: risks/signals.py:239 #: risks/signals.py:245
#, python-brace-format #, python-brace-format
msgid "Residual created for risk: {t}" msgid "Residual created for risk: {t}"
msgstr "Restrisiko erstellt für das Risiko: {t}" msgstr "Restrisiko erstellt für das Risiko: {t}"
#: risks/signals.py:257 #: risks/signals.py:264
#, python-brace-format #, python-brace-format
msgid "Residual review required for risk: {t}" msgid "Residual review required for risk: {t}"
msgstr "Restrisikoprüfung benötigt für Risiko {t}" msgstr "Restrisikoprüfung benötigt für Risiko {t}"
#: risks/signals.py:260 #: risks/signals.py:267
#, python-brace-format #, python-brace-format
msgid "Residual review completed for risk: {t}" msgid "Residual review completed for risk: {t}"
msgstr "Restrisikoprüfung Abgeschlossen für Risiko {t}" msgstr "Restrisikoprüfung Abgeschlossen für Risiko {t}"
#: risks/signals.py:263 #: risks/signals.py:270
#, python-brace-format #, python-brace-format
msgid "Residual updated for risk: {t}" msgid "Residual updated for risk: {t}"
msgstr "Restrisiko Aktualisiert für das Risiko: {t}" msgstr "Restrisiko Aktualisiert für das Risiko: {t}"
#: risks/signals.py:280 #: risks/signals.py:288
#, python-brace-format #, python-brace-format
msgid "Residual deleted for risk: {t}" msgid "Residual deleted for risk: {t}"
msgstr "Restrisiko gelöscht für das Risiko: {t}" msgstr "Restrisiko gelöscht für das Risiko: {t}"
#: risks/signals.py:316 #: risks/signals.py:325
#, python-brace-format #, python-brace-format
msgid "Incident {e}: {t}" msgid "Incident {e}: {t}"
msgstr "Vorfall {e}: {t}" msgstr "Vorfall {e}: {t}"
#: risks/signals.py:331 #: risks/signals.py:341
#, python-brace-format #, python-brace-format
msgid "Incident deleted: {t}" msgid "Incident deleted: {t}"
msgstr "Vorfall gelöscht: {t}" msgstr "Vorfall gelöscht: {t}"
#: risks/utils.py:66 #: risks/utils.py:135
#, python-brace-format #, python-brace-format
msgid "Follow-up reached: review required for risk '{t}'" msgid "Follow-up reached: review required for risk '{t}'"
msgstr "Wiedervorlagedatum erreicht: Prüfung nötig für Risiko '{t}'" msgstr "Wiedervorlagedatum erreicht: Prüfung nötig für Risiko '{t}'"
#: risks/views.py:208 #: risks/views.py:218
msgid "Risk has been marked as reviewed and closed." msgid "Risk has been marked as reviewed and closed."
msgstr "Das Risiko wurde geprüft und als geschlossen markiert" msgstr "Das Risiko wurde geprüft und als geschlossen markiert"
#: risks/views.py:210 #: risks/views.py:220
msgid "Not all controls are completed. Risk cannot be closed yet." msgid "Not all controls are completed. Risk cannot be closed yet."
msgstr "Nicht alle Maßnhamen sind abgeschlossen, das Risiko kann nicht geschlossen werden." msgstr "Nicht alle Maßnhamen sind abgeschlossen, das Risiko kann nicht geschlossen werden."
#: risks/views.py:368 #: risks/views.py:378
msgid "Notification marked as read." msgid "Notification marked as read."
msgstr "Nachricht als gelesen markiert" msgstr "Nachricht als gelesen markiert"
#: risks/views.py:378 #: risks/views.py:388
msgid "All notifications marked as read." msgid "All notifications marked as read."
msgstr "Alle Benachrichtigungen wurden als gelesen Markiert" msgstr "Alle Benachrichtigungen wurden als gelesen Markiert"
#: risks/views.py:397 #: risks/views.py:407
msgid "Risk status updated." msgid "Risk status updated."
msgstr "Risikostatus Aktualisiert" msgstr "Risikostatus Aktualisiert"
#: risks/views.py:413 #: risks/views.py:423
msgid "Control status updated." msgid "Control status updated."
msgstr "Maßnahmenstatus Aktualisiert" msgstr "Maßnahmenstatus Aktualisiert"
#: risks/views.py:429 #: risks/views.py:439
msgid "Incident status updated." msgid "Incident status updated."
msgstr "Vorfallstatus Aktualisiert" msgstr "Vorfallstatus Aktualisiert"
#: risks/views.py:446 #: risks/views.py:456
msgid "Residual review flag updated." msgid "Residual review flag updated."
msgstr "Restrisiko geprüft" msgstr "Restrisiko geprüft"
@ -790,11 +790,11 @@ msgstr "Nr."
msgid "Related Risk" msgid "Related Risk"
msgstr "Zugeordnete Risiken" msgstr "Zugeordnete Risiken"
#: templates/risks/list_controls.html:144 #: templates/risks/list_controls.html:164
msgid "No controls found." msgid "No controls found."
msgstr "Keine Maßnahmen vorhanden" msgstr "Keine Maßnahmen vorhanden"
#: templates/risks/list_incidents.html:125 #: templates/risks/list_incidents.html:137
msgid "No incidents found." msgid "No incidents found."
msgstr "Keine Vorfälle gefunden." msgstr "Keine Vorfälle gefunden."

View file

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-15 11:24+0200\n" "POT-Creation-Date: 2025-09-16 14:08+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -31,7 +31,7 @@ msgstr ""
msgid "Risks" msgid "Risks"
msgstr "" msgstr ""
#: risks/admin.py:34 risks/models.py:184 templates/base.html:54 #: risks/admin.py:34 risks/models.py:191 templates/base.html:54
#: templates/risks/item_control.html:5 templates/risks/list_controls.html:5 #: templates/risks/item_control.html:5 templates/risks/list_controls.html:5
msgid "Controls" msgid "Controls"
msgstr "" msgstr ""
@ -44,7 +44,7 @@ msgstr ""
msgid "Reviews" msgid "Reviews"
msgstr "" msgstr ""
#: risks/admin.py:37 risks/models.py:255 templates/base.html:55 #: risks/admin.py:37 risks/models.py:262 templates/base.html:55
#: templates/risks/item_incident.html:5 templates/risks/item_risk.html:14 #: templates/risks/item_incident.html:5 templates/risks/item_risk.html:14
#: templates/risks/list_incidents.html:5 templates/risks/list_incidents.html:18 #: templates/risks/list_incidents.html:5 templates/risks/list_incidents.html:18
msgid "Incidents" msgid "Incidents"
@ -54,7 +54,7 @@ msgstr ""
msgid "Users" msgid "Users"
msgstr "" msgstr ""
#: risks/admin.py:166 risks/models.py:362 templates/risks/item_control.html:96 #: risks/admin.py:166 risks/models.py:370 templates/risks/item_control.html:96
#: templates/risks/item_incident.html:88 templates/risks/item_risk.html:245 #: templates/risks/item_incident.html:88 templates/risks/item_risk.html:245
msgid "User" msgid "User"
msgstr "" msgstr ""
@ -143,11 +143,11 @@ msgstr ""
msgid "Open" msgid "Open"
msgstr "" msgstr ""
#: risks/models.py:54 risks/models.py:259 #: risks/models.py:54 risks/models.py:266
msgid "In Progress" msgid "In Progress"
msgstr "" msgstr ""
#: risks/models.py:55 risks/models.py:260 #: risks/models.py:55 risks/models.py:267
msgid "Closed" msgid "Closed"
msgstr "" msgstr ""
@ -188,26 +188,26 @@ msgid "Critical (> 100,000 € existential threat)"
msgstr "" msgstr ""
#: risks/models.py:72 templates/risks/dashboard.html:57 #: risks/models.py:72 templates/risks/dashboard.html:57
#: templates/risks/list_risks.html:112 #: templates/risks/list_risks.html:112 templates/risks/list_risks.html:143
msgid "Confidentiality" msgid "Confidentiality"
msgstr "" msgstr ""
#: risks/models.py:73 templates/risks/dashboard.html:63 #: risks/models.py:73 templates/risks/dashboard.html:63
#: templates/risks/list_risks.html:113 #: templates/risks/list_risks.html:113 templates/risks/list_risks.html:150
msgid "Integrity" msgid "Integrity"
msgstr "" msgstr ""
#: risks/models.py:74 templates/risks/dashboard.html:69 #: risks/models.py:74 templates/risks/dashboard.html:69
#: templates/risks/list_risks.html:114 #: templates/risks/list_risks.html:114 templates/risks/list_risks.html:157
msgid "Availability" msgid "Availability"
msgstr "" msgstr ""
#: risks/models.py:78 risks/models.py:194 risks/models.py:263 #: risks/models.py:78 risks/models.py:201 risks/models.py:270
#: templates/risks/item_risk.html:188 #: templates/risks/item_risk.html:188
msgid "Title" msgid "Title"
msgstr "" msgstr ""
#: risks/models.py:79 risks/models.py:264 templates/risks/item_control.html:52 #: risks/models.py:79 risks/models.py:271 templates/risks/item_control.html:52
#: templates/risks/item_incident.html:44 #: templates/risks/item_incident.html:44
msgid "Description" msgid "Description"
msgstr "" msgstr ""
@ -244,176 +244,176 @@ msgstr ""
msgid "Effects" msgid "Effects"
msgstr "" msgstr ""
#: risks/models.py:140 #: risks/models.py:147
msgid "Residual Risk" msgid "Residual Risk"
msgstr "" msgstr ""
#: risks/models.py:141 #: risks/models.py:148
msgid "Residual Risks" msgid "Residual Risks"
msgstr "" msgstr ""
#: risks/models.py:183 templates/risks/item_control.html:33 #: risks/models.py:190 templates/risks/item_control.html:33
#: templates/risks/list_controls.html:18 templates/risks/list_controls.html:97 #: templates/risks/list_controls.html:18 templates/risks/list_controls.html:97
msgid "Control" msgid "Control"
msgstr "" msgstr ""
#: risks/models.py:187 #: risks/models.py:194
msgid "Planned" msgid "Planned"
msgstr "" msgstr ""
#: risks/models.py:188 #: risks/models.py:195
msgid "In progress" msgid "In progress"
msgstr "" msgstr ""
#: risks/models.py:189 #: risks/models.py:196
msgid "Completed" msgid "Completed"
msgstr "" msgstr ""
#: risks/models.py:190 #: risks/models.py:197
msgid "Verified" msgid "Verified"
msgstr "" msgstr ""
#: risks/models.py:191 #: risks/models.py:198
msgid "Rejected" msgid "Rejected"
msgstr "" msgstr ""
#: risks/models.py:222 #: risks/models.py:229
msgid "Auditlog" msgid "Auditlog"
msgstr "" msgstr ""
#: risks/models.py:223 #: risks/models.py:230
msgid "Auditlogs" msgid "Auditlogs"
msgstr "" msgstr ""
#: risks/models.py:254 templates/risks/item_incident.html:33 #: risks/models.py:261 templates/risks/item_incident.html:33
#: templates/risks/item_risk.html:218 templates/risks/list_incidents.html:97 #: templates/risks/item_risk.html:218 templates/risks/list_incidents.html:97
msgid "Incident" msgid "Incident"
msgstr "" msgstr ""
#: risks/models.py:258 #: risks/models.py:265
msgid "Opened" msgid "Opened"
msgstr "" msgstr ""
#: risks/models.py:265 #: risks/models.py:272
msgid "Date reported" msgid "Date reported"
msgstr "" msgstr ""
#: risks/models.py:268 templates/risks/item_incident.html:34 #: risks/models.py:275 templates/risks/item_incident.html:34
#: templates/risks/list_incidents.html:63 #: templates/risks/list_incidents.html:63
#: templates/risks/list_incidents.html:101 #: templates/risks/list_incidents.html:101
msgid "Reported by" msgid "Reported by"
msgstr "" msgstr ""
#: risks/models.py:284 #: risks/models.py:291
msgid "Risk created" msgid "Risk created"
msgstr "" msgstr ""
#: risks/models.py:285 #: risks/models.py:292
msgid "Risk updated" msgid "Risk updated"
msgstr "" msgstr ""
#: risks/models.py:286 #: risks/models.py:293
msgid "Risk deleted" msgid "Risk deleted"
msgstr "" msgstr ""
#: risks/models.py:287 #: risks/models.py:294
msgid "Risk review required" msgid "Risk review required"
msgstr "" msgstr ""
#: risks/models.py:288 #: risks/models.py:295
msgid "Risk review completed" msgid "Risk review completed"
msgstr "" msgstr ""
#: risks/models.py:290 #: risks/models.py:297
msgid "Control created" msgid "Control created"
msgstr "" msgstr ""
#: risks/models.py:291 #: risks/models.py:298
msgid "Control updated" msgid "Control updated"
msgstr "" msgstr ""
#: risks/models.py:292 #: risks/models.py:299
msgid "Control deleted" msgid "Control deleted"
msgstr "" msgstr ""
#: risks/models.py:294 #: risks/models.py:301
msgid "Residual created" msgid "Residual created"
msgstr "" msgstr ""
#: risks/models.py:295 #: risks/models.py:302
msgid "Residual updated" msgid "Residual updated"
msgstr "" msgstr ""
#: risks/models.py:296 #: risks/models.py:303
msgid "Residual deleted" msgid "Residual deleted"
msgstr "" msgstr ""
#: risks/models.py:297 #: risks/models.py:304
msgid "Residual review required" msgid "Residual review required"
msgstr "" msgstr ""
#: risks/models.py:298 #: risks/models.py:305
msgid "Residual review completed" msgid "Residual review completed"
msgstr "" msgstr ""
#: risks/models.py:300 #: risks/models.py:307
msgid "Incident created" msgid "Incident created"
msgstr "" msgstr ""
#: risks/models.py:301 #: risks/models.py:308
msgid "Incident updated" msgid "Incident updated"
msgstr "" msgstr ""
#: risks/models.py:302 #: risks/models.py:309
msgid "Incident deleted" msgid "Incident deleted"
msgstr "" msgstr ""
#: risks/models.py:304 #: risks/models.py:311
msgid "User created" msgid "User created"
msgstr "" msgstr ""
#: risks/models.py:305 #: risks/models.py:312
msgid "User deleted" msgid "User deleted"
msgstr "" msgstr ""
#: risks/models.py:314 risks/utils.py:142 #: risks/models.py:321 risks/utils.py:79
msgid "Notification" msgid "Notification"
msgstr "" msgstr ""
#: risks/models.py:315 templates/base.html:96 #: risks/models.py:322 templates/base.html:96
#: templates/risks/notifications.html:4 #: templates/risks/notifications.html:4
msgid "Notifications" msgid "Notifications"
msgstr "" msgstr ""
#: risks/models.py:411 #: risks/models.py:419
msgid "Notification rule" msgid "Notification rule"
msgstr "" msgstr ""
#: risks/models.py:412 #: risks/models.py:420
msgid "Notification rules" msgid "Notification rules"
msgstr "" msgstr ""
#: risks/models.py:415 #: risks/models.py:423
msgid "Event" msgid "Event"
msgstr "" msgstr ""
#: risks/models.py:420 #: risks/models.py:428
msgid "Show in app" msgid "Show in app"
msgstr "" msgstr ""
#: risks/models.py:421 #: risks/models.py:429
msgid "Send via email" msgid "Send via email"
msgstr "" msgstr ""
#: risks/models.py:425 #: risks/models.py:433
msgid "Send to owner/responsible/reporter (if available)" msgid "Send to owner/responsible/reporter (if available)"
msgstr "" msgstr ""
#: risks/models.py:428 #: risks/models.py:436
msgid "Send to all staff" msgid "Send to all staff"
msgstr "" msgstr ""
#: risks/models.py:430 #: risks/models.py:438
msgid "Extra recipients (emails, comma or newline separated)" msgid "Extra recipients (emails, comma or newline separated)"
msgstr "" msgstr ""
@ -432,108 +432,108 @@ msgstr ""
msgid "Risk created: {t}" msgid "Risk created: {t}"
msgstr "" msgstr ""
#: risks/signals.py:121 #: risks/signals.py:122
#, python-brace-format #, python-brace-format
msgid "Risk updated: {t}" msgid "Risk updated: {t}"
msgstr "" msgstr ""
#: risks/signals.py:134 #: risks/signals.py:136
#, python-brace-format #, python-brace-format
msgid "Risk deleted: {t}" msgid "Risk deleted: {t}"
msgstr "" msgstr ""
#: risks/signals.py:181 #: risks/signals.py:184
#, python-brace-format #, python-brace-format
msgid "Control {e}: {t}" msgid "Control {e}: {t}"
msgstr "" msgstr ""
#: risks/signals.py:182 risks/signals.py:317 #: risks/signals.py:185 risks/signals.py:326
msgid "created" msgid "created"
msgstr "" msgstr ""
#: risks/signals.py:182 risks/signals.py:317 #: risks/signals.py:185 risks/signals.py:326
msgid "updated" msgid "updated"
msgstr "" msgstr ""
#: risks/signals.py:196 #: risks/signals.py:200
#, python-brace-format #, python-brace-format
msgid "Control deleted: {t}" msgid "Control deleted: {t}"
msgstr "" msgstr ""
#: risks/signals.py:213 #: risks/signals.py:218
#, python-brace-format #, python-brace-format
msgid "Residual review required for risk '{t}' due to control change" msgid "Residual review required for risk '{t}' due to control change"
msgstr "" msgstr ""
#: risks/signals.py:239 #: risks/signals.py:245
#, python-brace-format #, python-brace-format
msgid "Residual created for risk: {t}" msgid "Residual created for risk: {t}"
msgstr "" msgstr ""
#: risks/signals.py:257 #: risks/signals.py:264
#, python-brace-format #, python-brace-format
msgid "Residual review required for risk: {t}" msgid "Residual review required for risk: {t}"
msgstr "" msgstr ""
#: risks/signals.py:260 #: risks/signals.py:267
#, python-brace-format #, python-brace-format
msgid "Residual review completed for risk: {t}" msgid "Residual review completed for risk: {t}"
msgstr "" msgstr ""
#: risks/signals.py:263 #: risks/signals.py:270
#, python-brace-format #, python-brace-format
msgid "Residual updated for risk: {t}" msgid "Residual updated for risk: {t}"
msgstr "" msgstr ""
#: risks/signals.py:280 #: risks/signals.py:288
#, python-brace-format #, python-brace-format
msgid "Residual deleted for risk: {t}" msgid "Residual deleted for risk: {t}"
msgstr "" msgstr ""
#: risks/signals.py:316 #: risks/signals.py:325
#, python-brace-format #, python-brace-format
msgid "Incident {e}: {t}" msgid "Incident {e}: {t}"
msgstr "" msgstr ""
#: risks/signals.py:331 #: risks/signals.py:341
#, python-brace-format #, python-brace-format
msgid "Incident deleted: {t}" msgid "Incident deleted: {t}"
msgstr "" msgstr ""
#: risks/utils.py:66 #: risks/utils.py:135
#, python-brace-format #, python-brace-format
msgid "Follow-up reached: review required for risk '{t}'" msgid "Follow-up reached: review required for risk '{t}'"
msgstr "" msgstr ""
#: risks/views.py:208 #: risks/views.py:218
msgid "Risk has been marked as reviewed and closed." msgid "Risk has been marked as reviewed and closed."
msgstr "" msgstr ""
#: risks/views.py:210 #: risks/views.py:220
msgid "Not all controls are completed. Risk cannot be closed yet." msgid "Not all controls are completed. Risk cannot be closed yet."
msgstr "" msgstr ""
#: risks/views.py:368 #: risks/views.py:378
msgid "Notification marked as read." msgid "Notification marked as read."
msgstr "" msgstr ""
#: risks/views.py:378 #: risks/views.py:388
msgid "All notifications marked as read." msgid "All notifications marked as read."
msgstr "" msgstr ""
#: risks/views.py:397 #: risks/views.py:407
msgid "Risk status updated." msgid "Risk status updated."
msgstr "" msgstr ""
#: risks/views.py:413 #: risks/views.py:423
msgid "Control status updated." msgid "Control status updated."
msgstr "" msgstr ""
#: risks/views.py:429 #: risks/views.py:439
msgid "Incident status updated." msgid "Incident status updated."
msgstr "" msgstr ""
#: risks/views.py:446 #: risks/views.py:456
msgid "Residual review flag updated." msgid "Residual review flag updated."
msgstr "" msgstr ""
@ -796,11 +796,11 @@ msgstr ""
msgid "Related Risk" msgid "Related Risk"
msgstr "" msgstr ""
#: templates/risks/list_controls.html:144 #: templates/risks/list_controls.html:164
msgid "No controls found." msgid "No controls found."
msgstr "" msgstr ""
#: templates/risks/list_incidents.html:125 #: templates/risks/list_incidents.html:137
msgid "No incidents found." msgid "No incidents found."
msgstr "" msgstr ""

View file

@ -0,0 +1,17 @@
# Generated by Django 5.2.6 on 2025-09-16 12:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("risks", "0027_notification_content_type_notification_kind_and_more"),
]
operations = [
migrations.AddField(
model_name="notification",
name="target_url",
field=models.CharField(blank=True, max_length=500, null=True),
),
]

View file

@ -337,6 +337,7 @@ class Notification(models.Model):
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, null=True, blank=True) content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, null=True, blank=True)
object_id = models.PositiveIntegerField(null=True, blank=True) object_id = models.PositiveIntegerField(null=True, blank=True)
related_object = GenericForeignKey("content_type", "object_id") related_object = GenericForeignKey("content_type", "object_id")
target_url = models.CharField(max_length=500, blank=True, null=True)
def __str__(self): def __str__(self):
user_display = self.user.username if self.user else "System" user_display = self.user.username if self.user else "System"

View file

@ -101,6 +101,7 @@ def risk_saved(sender, instance: Risk, created, **kwargs):
NotificationKind.RISK_CREATED, NotificationKind.RISK_CREATED,
message=_("Risk created: {t}").format(t=instance.title), message=_("Risk created: {t}").format(t=instance.title),
users=[instance.owner] if instance.owner_id else None, users=[instance.owner] if instance.owner_id else None,
obj=instance,
) )
else: else:
# Diff audit log # Diff audit log
@ -120,6 +121,7 @@ def risk_saved(sender, instance: Risk, created, **kwargs):
NotificationKind.RISK_UPDATED, NotificationKind.RISK_UPDATED,
message=_("Risk updated: {t}").format(t=instance.title), message=_("Risk updated: {t}").format(t=instance.title),
users=[instance.owner] if instance.owner_id else None, users=[instance.owner] if instance.owner_id else None,
obj=instance,
) )
@ -133,6 +135,7 @@ def risk_deleted(sender, instance: Risk, **kwargs):
NotificationKind.RISK_DELETED, NotificationKind.RISK_DELETED,
message=_("Risk deleted: {t}").format(t=instance.title), message=_("Risk deleted: {t}").format(t=instance.title),
users=[instance.owner] if instance.owner_id else None, users=[instance.owner] if instance.owner_id else None,
obj=instance,
) )
@ -182,6 +185,7 @@ def control_saved(sender, instance: Control, created, **kwargs):
e=_("created") if created else _("updated"), t=instance.title e=_("created") if created else _("updated"), t=instance.title
), ),
users=[instance.responsible] if instance.responsible_id else None, users=[instance.responsible] if instance.responsible_id else None,
obj=instance,
) )
@ -195,6 +199,7 @@ def control_deleted(sender, instance: Control, **kwargs):
NotificationKind.CONTROL_DELETED, NotificationKind.CONTROL_DELETED,
message=_("Control deleted: {t}").format(t=instance.title), message=_("Control deleted: {t}").format(t=instance.title),
users=[instance.responsible] if instance.responsible_id else None, users=[instance.responsible] if instance.responsible_id else None,
obj=instance,
) )
@ -211,7 +216,8 @@ def control_risks_changed(sender, instance: Control, action, **kwargs):
notify_event( notify_event(
NotificationKind.RESIDUAL_REVIEW_REQUIRED, NotificationKind.RESIDUAL_REVIEW_REQUIRED,
message=_("Residual review required for risk '{t}' due to control change").format(t=risk.title), message=_("Residual review required for risk '{t}' due to control change").format(t=risk.title),
users=_risk_stakeholders(risk) users=_risk_stakeholders(risk),
obj=instance,
) )
@ -238,6 +244,7 @@ def residual_saved(sender, instance: ResidualRisk, created, **kwargs):
NotificationKind.RESIDUAL_CREATED, NotificationKind.RESIDUAL_CREATED,
message=_("Residual created for risk: {t}").format(t=instance.risk.title), message=_("Residual created for risk: {t}").format(t=instance.risk.title),
users=[instance.risk.owner] if instance.risk.owner_id else None, users=[instance.risk.owner] if instance.risk.owner_id else None,
obj=instance,
) )
else: else:
old = ResidualRisk.objects.get(pk=instance.pk) old = ResidualRisk.objects.get(pk=instance.pk)
@ -266,6 +273,7 @@ def residual_saved(sender, instance: ResidualRisk, created, **kwargs):
kind, kind,
message=msg.format(t=instance.risk.title), message=msg.format(t=instance.risk.title),
users=[instance.risk.owner] if instance.risk.owner_id else None, users=[instance.risk.owner] if instance.risk.owner_id else None,
obj=instance,
) )
@ -279,6 +287,7 @@ def residual_deleted(sender, instance: ResidualRisk, **kwargs):
NotificationKind.RESIDUAL_DELETED, NotificationKind.RESIDUAL_DELETED,
message=_("Residual deleted for risk: {t}").format(t=instance.risk.title), message=_("Residual deleted for risk: {t}").format(t=instance.risk.title),
users=[instance.risk.owner] if instance.risk.owner_id else None, users=[instance.risk.owner] if instance.risk.owner_id else None,
obj=instance,
) )
@ -317,6 +326,7 @@ def incident_saved(sender, instance: Incident, created, **kwargs):
e=_("created") if created else _("updated"), t=instance.title e=_("created") if created else _("updated"), t=instance.title
), ),
users=[instance.reported_by] if instance.reported_by_id else None, users=[instance.reported_by] if instance.reported_by_id else None,
obj=instance,
) )
@ -330,6 +340,7 @@ def incident_deleted(sender, instance: Incident, **kwargs):
NotificationKind.INCIDENT_DELETED, NotificationKind.INCIDENT_DELETED,
message=_("Incident deleted: {t}").format(t=instance.title), message=_("Incident deleted: {t}").format(t=instance.title),
users=[instance.reported_by] if instance.reported_by_id else None, users=[instance.reported_by] if instance.reported_by_id else None,
obj=instance,
) )

View file

@ -17,7 +17,7 @@ urlpatterns = [
path("risks/risks/<int:id>", views.show_risk, name="show_risk"), path("risks/risks/<int:id>", views.show_risk, name="show_risk"),
path("risks/risk_matrix", views.risk_matrix, name="risk_matrix"), path("risks/risk_matrix", views.risk_matrix, name="risk_matrix"),
path("risks/<int:id>/status", views.update_risk_status, name="update_risk_status"), path("risks/<int:id>/status", views.update_risk_status, name="update_risk_status"),
path("risks/<int:pk>/mark_reviewed/", views.mark_risk_reviewed, name="mark_risk_reviewed"), path("risks/<int:pk>/mark_risk_reviewed/", views.mark_risk_reviewed, name="mark_risk_reviewed"),
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Controls # Controls

View file

@ -1,9 +1,10 @@
from datetime import date, datetime from datetime import date, datetime
from typing import Iterable, Optional from typing import Any, Iterable, Optional
from django.conf import settings from django.conf import settings
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.core.mail import send_mail from django.core.mail import send_mail
from django.urls import reverse
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -14,6 +15,74 @@ from .models import (
User = get_user_model() User = get_user_model()
# ---------------------------------------------------------------------------
# notify_event()
# ---------------------------------------------------------------------------
def get_entity_url(obj):
if obj is None:
return None
model_name = obj.__class__.__name__.lower()
mapping = {
"risk": "risks:show_risk",
"control": "risks:show_control",
"residualrisk": "risks:show_risk",
"incident": "risks:show_incident",
"user": "admin:user_detail",
}
view_name = mapping.get(model_name)
if view_name:
return reverse(view_name, args=[obj.pk])
return None
def notify_event(kind: str, *, message: str, users: Optional[Iterable[Any]] = None, obj=None):
"""
Generates in-app notifications and/or emails depending on the NotificationRule.
- users: Basic recipients (owner/responsible/reporter) can be None.
- staff/extra recipients are added from the rule.
"""
rule = NotificationRule.objects.filter(kind=kind).first()
# Defaults (no rule → in-app only)
enabled_in_app = True
enabled_email = False
recipients_users = set()
extra_emails = []
# Base recipients
if users:
recipients_users.update(u for u in users if u and getattr(u, "is_active", False))
# Rule overrides
if rule:
enabled_in_app = rule.enabled_in_app
enabled_email = rule.enabled_email
if rule.to_staff:
recipients_users.update(User.objects.filter(is_staff=True, is_active=True))
extra_emails = _split_emails(rule.extra_recipients)
url = get_entity_url(obj)
# In-App Notifications
if enabled_in_app:
for u in recipients_users:
Notification.objects.create(user=u, message=message, target_url=url)
# Email Notifications
if enabled_email:
emails = [u.email for u in recipients_users if u and u.email] + extra_emails
emails = list(dict.fromkeys(emails))
if emails:
body = f"{message}\n\n{url}" if url else message
send_mail(
_("Notification"),
body,
getattr(settings, "DEFAULT_FROM_EMAIL", "webmaster@localhost"),
emails,
fail_silently=True,
)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# model_diff() # model_diff()
@ -85,6 +154,7 @@ def check_risk_followups():
NotificationKind.RISK_REVIEW_REQUIRED, NotificationKind.RISK_REVIEW_REQUIRED,
message=message, message=message,
users=[risk.owner] if risk.owner_id else None, users=[risk.owner] if risk.owner_id else None,
obj=risk,
) )
@ -97,51 +167,3 @@ def _split_emails(value: str) -> list[str]:
return [] return []
raw = value.replace("\n", ",").split(",") raw = value.replace("\n", ",").split(",")
return [e.strip() for e in raw if "@" in e and e.strip()] return [e.strip() for e in raw if "@" in e and e.strip()]
# ---------------------------------------------------------------------------
# notify_event()
# ---------------------------------------------------------------------------
def notify_event(kind: str, *, message: str, users: Optional[Iterable[User]] = None):
"""
Generates in-app notifications and/or emails depending on the NotificationRule.
- users: Basic recipients (owner/responsible/reporter) can be None.
- staff/extra recipients are added from the rule.
"""
rule = NotificationRule.objects.filter(kind=kind).first()
# Defaults (no rule → in-app only)
enabled_in_app = True
enabled_email = False
recipients_users = set()
extra_emails = []
# Base recipients
if users:
recipients_users.update(u for u in users if u and getattr(u, "is_active", False))
# Rule overrides
if rule:
enabled_in_app = rule.enabled_in_app
enabled_email = rule.enabled_email
if rule.to_staff:
recipients_users.update(User.objects.filter(is_staff=True, is_active=True))
extra_emails = _split_emails(rule.extra_recipients)
# In-App Notifications
if enabled_in_app:
for u in recipients_users:
Notification.objects.create(user=u, message=message)
# Email Notifications
if enabled_email:
emails = [u.email for u in recipients_users if u and u.email] + extra_emails
emails = list(dict.fromkeys(emails)) # de-dupe, preserve order
if emails:
send_mail(
_("Notification"),
message,
getattr(settings, "DEFAULT_FROM_EMAIL", "webmaster@localhost"),
emails,
fail_silently=True, # dont crash on mail error
)

View file

@ -13,7 +13,7 @@ from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from .forms import RiskStatusForm, ControlStatusForm, IncidentStatusForm, ResidualReviewForm from .forms import RiskStatusForm, ControlStatusForm, IncidentStatusForm, ResidualReviewForm
from .models import Risk, Control, ResidualRisk, AuditLog, Incident, Notification from .models import AuditLog, Risk, Control, ResidualRisk, AuditLog, Incident, Notification
from .serializers import ( from .serializers import (
ControlSerializer, RiskSerializer, ResidualRiskSerializer, ControlSerializer, RiskSerializer, ResidualRiskSerializer,
UserSerializer, AuditSerializer, IncidentSerializer, UserSerializer, AuditSerializer, IncidentSerializer,
@ -205,6 +205,16 @@ def mark_risk_reviewed(request, pk):
risk.status = "closed" risk.status = "closed"
risk._changed_by = request.user risk._changed_by = request.user
risk.save(update_fields=["status", "updated_at"]) risk.save(update_fields=["status", "updated_at"])
# ➜ AuditLog schreiben
AuditLog.objects.create(
user=request.user,
action="reviewed",
model="Risk",
object_id=risk.pk,
changes={"status": {"old": "review_required", "new": "closed"}},
)
messages.success(request, _("Risk has been marked as reviewed and closed.")) messages.success(request, _("Risk has been marked as reviewed and closed."))
else: else:
messages.error(request, _("Not all controls are completed. Risk cannot be closed yet.")) messages.error(request, _("Not all controls are completed. Risk cannot be closed yet."))

View file

@ -108,12 +108,18 @@
<td class="has-text-centered">{{ c.id }}</td> <td class="has-text-centered">{{ c.id }}</td>
<td>{{ c.title }}</td> <td>{{ c.title }}</td>
<td> <td>
{% if c.risk %} {% if c.risks.all %}
<a href="{% url 'risks:show_risk' c.risk.id %}" onclick="event.stopPropagation();"> <ul>
{{ c.risk.title }} {% for r in c.risks.all %}
</a> <li>
<a href="{% url 'risks:show_risk' r.id %}" onclick="event.stopPropagation();">
{{ r.title }}
</a>
</li>
{% endfor %}
</ul>
{% else %} {% else %}
-
{% endif %} {% endif %}
</td> </td>
<td class="has-text-centered"> <td class="has-text-centered">
@ -123,7 +129,21 @@
{% endif %} {% endif %}
</td> </td>
<td class="has-text-centered">{{ c.get_status_display }}</td> <td class="has-text-centered">
<div class="tag
{% if c.status == 'planned' %}
is-info
{% elif c.status == 'completed' %}
is-success
{% elif c.status == 'verified' %}
is-success
{% elif c.status == 'in_progress' %}
is-link is-light
{% endif %}
">
{{ c.get_status_display }}
</div>
</td>
<td class="has-text-centered"> <td class="has-text-centered">
{% if c.due_date %} {% if c.due_date %}
{{ c.due_date|date:"d.m.Y" }} {{ c.due_date|date:"d.m.Y" }}

View file

@ -117,9 +117,21 @@
<span class="has-text-grey"></span> <span class="has-text-grey"></span>
{% endif %} {% endif %}
</td> </td>
<td>{{ i.get_status_display }}</td> <td class="has-text-centered">
<td>{{ i.date_reported|date:"d.m.Y" }}</td> <div class="tag
<td>{{ i.reported_by|default:"" }}</td> {% if i.status == 'open' %}
is-info
{% elif i.status == 'closed' %}
is-success
{% elif i.status == 'in_progress' %}
is-link is-light
{% endif %}
">
{{ i.get_status_display }}
</div>
</td>
<td class="has-text-centered">{{ i.date_reported|date:"d.m.Y" }}</td>
<td class="has-text-centered">{{ i.reported_by|default:"" }}</td>
</tr> </tr>
{% empty %} {% empty %}
<tr><td colspan="6" class="has-text-grey has-text-centered">{% trans "No incidents found." %}</td></tr> <tr><td colspan="6" class="has-text-grey has-text-centered">{% trans "No incidents found." %}</td></tr>

View file

@ -140,21 +140,21 @@
<td class="has-text-centered"> <td class="has-text-centered">
{% if "1" in r.cia %} {% if "1" in r.cia %}
<span class="icon is-small"> <span class="icon is-small">
<i class="fas fa-circle" aria-hidden="true" style="color: grey"></i> <abbr title="{% trans 'Confidentiality' %}"><i class="fas fa-circle" aria-hidden="true" style="color: grey"></i></abbr>
</span> </span>
{% endif %} {% endif %}
</td> </td>
<td class="has-text-centered"> <td class="has-text-centered">
{% if "2" in r.cia %} {% if "2" in r.cia %}
<span class="icon is-small"> <span class="icon is-small">
<i class="fas fa-circle" aria-hidden="true" style="color: grey"></i> <abbr title="{% trans 'Integrity' %}"><i class="fas fa-circle" aria-hidden="true" style="color: grey"></i></abbr>
</span> </span>
{% endif %} {% endif %}
</td> </td>
<td class="has-text-centered"> <td class="has-text-centered">
{% if "3" in r.cia %} {% if "3" in r.cia %}
<span class="icon is-small"> <span class="icon is-small">
<i class="fas fa-circle" aria-hidden="true" style="color: grey"></i> <abbr title="{% trans 'Availability' %}"><i class="fas fa-circle" aria-hidden="true" style="color: grey"></i></abbr>
</span> </span>
{% endif %} {% endif %}
</td> </td>

View file

@ -34,8 +34,8 @@
{% trans "New" %} {% trans "New" %}
</span> </span>
{% endif %} {% endif %}
{% if n.get_link %} {% if n.target_url %}
<a href="{{ n.get_link }}">{{ n.message }}</a> <a href="{{ n.target_url }}">{{ n.message }}</a>
{% else %} {% else %}
{{ n.message }} {{ n.message }}
{% endif %} {% endif %}