diff --git a/db.sqlite3 b/db.sqlite3 index 75fc4c7..a6e5de5 100644 Binary files a/db.sqlite3 and b/db.sqlite3 differ diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po index 065b8ea..bc0ede0 100644 --- a/locale/de/LC_MESSAGES/django.po +++ b/locale/de/LC_MESSAGES/django.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: wira-risk-management\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" "Last-Translator: Kevin Heyer \n" "Language-Team: German\n" @@ -25,7 +25,7 @@ msgstr "Admin" msgid "Risks" 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 msgid "Controls" msgstr "Maßnahmen" @@ -38,7 +38,7 @@ msgstr "Restrisiken" msgid "Reviews" 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/list_incidents.html:5 templates/risks/list_incidents.html:18 msgid "Incidents" @@ -48,7 +48,7 @@ msgstr "Vorfälle" msgid "Users" 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 msgid "User" msgstr "Benutzer" @@ -137,11 +137,11 @@ msgstr "Risiko" msgid "Open" msgstr "Offen" -#: risks/models.py:54 risks/models.py:259 +#: risks/models.py:54 risks/models.py:266 msgid "In Progress" msgstr "In Bearbeitung" -#: risks/models.py:55 risks/models.py:260 +#: risks/models.py:55 risks/models.py:267 msgid "Closed" msgstr "Geschlossen" @@ -182,26 +182,26 @@ msgid "Critical (> 100,000 € – existential threat)" msgstr "Kritisch (> 100.000 € – existenzielle Bedrohung)" #: 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" msgstr "Vertraulichkeit" #: 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" msgstr "Integrität" #: 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" 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 msgid "Title" 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 msgid "Description" msgstr "Beschreibung" @@ -238,176 +238,176 @@ msgstr "Aktualisiert am" msgid "Effects" msgstr "Auswirkungen" -#: risks/models.py:140 +#: risks/models.py:147 msgid "Residual Risk" msgstr "Restrisiko" -#: risks/models.py:141 +#: risks/models.py:148 msgid "Residual Risks" 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 msgid "Control" msgstr "Maßnahme" -#: risks/models.py:187 +#: risks/models.py:194 msgid "Planned" msgstr "Geplant" -#: risks/models.py:188 +#: risks/models.py:195 msgid "In progress" msgstr "In Bearbeitung" -#: risks/models.py:189 +#: risks/models.py:196 msgid "Completed" msgstr "Abgeschlossen" -#: risks/models.py:190 +#: risks/models.py:197 msgid "Verified" msgstr "Verifiziert" -#: risks/models.py:191 +#: risks/models.py:198 msgid "Rejected" msgstr "Abgelehnt" -#: risks/models.py:222 +#: risks/models.py:229 msgid "Auditlog" msgstr "Audit-Log" -#: risks/models.py:223 +#: risks/models.py:230 msgid "Auditlogs" 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 msgid "Incident" msgstr "Vorfall" -#: risks/models.py:258 +#: risks/models.py:265 msgid "Opened" msgstr "Eröffnet" -#: risks/models.py:265 +#: risks/models.py:272 msgid "Date reported" 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:101 msgid "Reported by" msgstr "Gemeldet von" -#: risks/models.py:284 +#: risks/models.py:291 msgid "Risk created" msgstr "Risiko erstellt" -#: risks/models.py:285 +#: risks/models.py:292 msgid "Risk updated" msgstr "Risiko Aktualisiert" -#: risks/models.py:286 +#: risks/models.py:293 msgid "Risk deleted" msgstr "Risiko gelöscht" -#: risks/models.py:287 +#: risks/models.py:294 msgid "Risk review required" msgstr "Risikoprüfung nötig" -#: risks/models.py:288 +#: risks/models.py:295 msgid "Risk review completed" msgstr "Risikoprüfung Abgeschlossen" -#: risks/models.py:290 +#: risks/models.py:297 msgid "Control created" msgstr "Maßnahme erstellt" -#: risks/models.py:291 +#: risks/models.py:298 msgid "Control updated" msgstr "Maßnahme Aktualisiert" -#: risks/models.py:292 +#: risks/models.py:299 msgid "Control deleted" msgstr "Maßnahme '{title}' gelöscht" -#: risks/models.py:294 +#: risks/models.py:301 msgid "Residual created" msgstr "Restrisiko erstellt" -#: risks/models.py:295 +#: risks/models.py:302 msgid "Residual updated" msgstr "Restrisiko Aktualisiert" -#: risks/models.py:296 +#: risks/models.py:303 msgid "Residual deleted" msgstr "Restrisiko gelöscht" -#: risks/models.py:297 +#: risks/models.py:304 msgid "Residual review required" msgstr "Restrisikoprüfung nötig" -#: risks/models.py:298 +#: risks/models.py:305 msgid "Residual review completed" msgstr "Restrisiko geprüft" -#: risks/models.py:300 +#: risks/models.py:307 msgid "Incident created" msgstr "Vorfall erstellt" -#: risks/models.py:301 +#: risks/models.py:308 msgid "Incident updated" msgstr "Vorfall Aktualisiert" -#: risks/models.py:302 +#: risks/models.py:309 msgid "Incident deleted" msgstr "Vorfall gelöscht" -#: risks/models.py:304 +#: risks/models.py:311 msgid "User created" msgstr "Benutzer erstellt" -#: risks/models.py:305 +#: risks/models.py:312 msgid "User deleted" msgstr "Benutzer gelöscht" -#: risks/models.py:314 risks/utils.py:142 +#: risks/models.py:321 risks/utils.py:79 msgid "Notification" msgstr "Nachricht" -#: risks/models.py:315 templates/base.html:96 +#: risks/models.py:322 templates/base.html:96 #: templates/risks/notifications.html:4 msgid "Notifications" msgstr "Nachrichten" -#: risks/models.py:411 +#: risks/models.py:419 msgid "Notification rule" msgstr "Benachrichtigungsregel" -#: risks/models.py:412 +#: risks/models.py:420 msgid "Notification rules" msgstr "Benachrichtigungsregeln" -#: risks/models.py:415 +#: risks/models.py:423 msgid "Event" msgstr "Aktion" -#: risks/models.py:420 +#: risks/models.py:428 msgid "Show in app" msgstr "Zeige in der WebApp" -#: risks/models.py:421 +#: risks/models.py:429 msgid "Send via email" msgstr "Sende via E-Mail" -#: risks/models.py:425 +#: risks/models.py:433 msgid "Send to owner/responsible/reporter (if available)" msgstr "Sende an Risikoeigner/Verantwortliche/Melder (Wenn vorhanden)" -#: risks/models.py:428 +#: risks/models.py:436 msgid "Send to all staff" msgstr "Sende an alle App-Mitarbeiter" -#: risks/models.py:430 +#: risks/models.py:438 msgid "Extra recipients (emails, comma or newline separated)" 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}" msgstr "Risiko erstellt: {t}" -#: risks/signals.py:121 +#: risks/signals.py:122 #, python-brace-format msgid "Risk updated: {t}" msgstr "Risiko Aktualisiert: {t}" -#: risks/signals.py:134 +#: risks/signals.py:136 #, python-brace-format msgid "Risk deleted: {t}" msgstr "Risiko gelöscht: {t}" -#: risks/signals.py:181 +#: risks/signals.py:184 #, python-brace-format msgid "Control {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" msgstr "erstellt" -#: risks/signals.py:182 risks/signals.py:317 +#: risks/signals.py:185 risks/signals.py:326 msgid "updated" msgstr "Aktualisiert" -#: risks/signals.py:196 +#: risks/signals.py:200 #, python-brace-format msgid "Control deleted: {t}" msgstr "Maßnahme gelöscht: {t}" -#: risks/signals.py:213 +#: risks/signals.py:218 #, python-brace-format 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" -#: risks/signals.py:239 +#: risks/signals.py:245 #, python-brace-format msgid "Residual created for risk: {t}" msgstr "Restrisiko erstellt für das Risiko: {t}" -#: risks/signals.py:257 +#: risks/signals.py:264 #, python-brace-format msgid "Residual review required for risk: {t}" msgstr "Restrisikoprüfung benötigt für Risiko {t}" -#: risks/signals.py:260 +#: risks/signals.py:267 #, python-brace-format msgid "Residual review completed for risk: {t}" msgstr "Restrisikoprüfung Abgeschlossen für Risiko {t}" -#: risks/signals.py:263 +#: risks/signals.py:270 #, python-brace-format msgid "Residual updated for risk: {t}" msgstr "Restrisiko Aktualisiert für das Risiko: {t}" -#: risks/signals.py:280 +#: risks/signals.py:288 #, python-brace-format msgid "Residual deleted for risk: {t}" msgstr "Restrisiko gelöscht für das Risiko: {t}" -#: risks/signals.py:316 +#: risks/signals.py:325 #, python-brace-format msgid "Incident {e}: {t}" msgstr "Vorfall {e}: {t}" -#: risks/signals.py:331 +#: risks/signals.py:341 #, python-brace-format msgid "Incident deleted: {t}" msgstr "Vorfall gelöscht: {t}" -#: risks/utils.py:66 +#: risks/utils.py:135 #, python-brace-format msgid "Follow-up reached: review required for risk '{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." 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." 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." msgstr "Nachricht als gelesen markiert" -#: risks/views.py:378 +#: risks/views.py:388 msgid "All notifications marked as read." msgstr "Alle Benachrichtigungen wurden als gelesen Markiert" -#: risks/views.py:397 +#: risks/views.py:407 msgid "Risk status updated." msgstr "Risikostatus Aktualisiert" -#: risks/views.py:413 +#: risks/views.py:423 msgid "Control status updated." msgstr "Maßnahmenstatus Aktualisiert" -#: risks/views.py:429 +#: risks/views.py:439 msgid "Incident status updated." msgstr "Vorfallstatus Aktualisiert" -#: risks/views.py:446 +#: risks/views.py:456 msgid "Residual review flag updated." msgstr "Restrisiko geprüft" @@ -790,11 +790,11 @@ msgstr "Nr." msgid "Related Risk" msgstr "Zugeordnete Risiken" -#: templates/risks/list_controls.html:144 +#: templates/risks/list_controls.html:164 msgid "No controls found." msgstr "Keine Maßnahmen vorhanden" -#: templates/risks/list_incidents.html:125 +#: templates/risks/list_incidents.html:137 msgid "No incidents found." msgstr "Keine Vorfälle gefunden." diff --git a/locale/en/LC_MESSAGES/django.po b/locale/en/LC_MESSAGES/django.po index 008003e..35fb559 100644 --- a/locale/en/LC_MESSAGES/django.po +++ b/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\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" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -31,7 +31,7 @@ msgstr "" msgid "Risks" 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 msgid "Controls" msgstr "" @@ -44,7 +44,7 @@ msgstr "" msgid "Reviews" 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/list_incidents.html:5 templates/risks/list_incidents.html:18 msgid "Incidents" @@ -54,7 +54,7 @@ msgstr "" msgid "Users" 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 msgid "User" msgstr "" @@ -143,11 +143,11 @@ msgstr "" msgid "Open" msgstr "" -#: risks/models.py:54 risks/models.py:259 +#: risks/models.py:54 risks/models.py:266 msgid "In Progress" msgstr "" -#: risks/models.py:55 risks/models.py:260 +#: risks/models.py:55 risks/models.py:267 msgid "Closed" msgstr "" @@ -188,26 +188,26 @@ msgid "Critical (> 100,000 € – existential threat)" msgstr "" #: 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" msgstr "" #: 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" msgstr "" #: 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" 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 msgid "Title" 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 msgid "Description" msgstr "" @@ -244,176 +244,176 @@ msgstr "" msgid "Effects" msgstr "" -#: risks/models.py:140 +#: risks/models.py:147 msgid "Residual Risk" msgstr "" -#: risks/models.py:141 +#: risks/models.py:148 msgid "Residual Risks" 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 msgid "Control" msgstr "" -#: risks/models.py:187 +#: risks/models.py:194 msgid "Planned" msgstr "" -#: risks/models.py:188 +#: risks/models.py:195 msgid "In progress" msgstr "" -#: risks/models.py:189 +#: risks/models.py:196 msgid "Completed" msgstr "" -#: risks/models.py:190 +#: risks/models.py:197 msgid "Verified" msgstr "" -#: risks/models.py:191 +#: risks/models.py:198 msgid "Rejected" msgstr "" -#: risks/models.py:222 +#: risks/models.py:229 msgid "Auditlog" msgstr "" -#: risks/models.py:223 +#: risks/models.py:230 msgid "Auditlogs" 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 msgid "Incident" msgstr "" -#: risks/models.py:258 +#: risks/models.py:265 msgid "Opened" msgstr "" -#: risks/models.py:265 +#: risks/models.py:272 msgid "Date reported" 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:101 msgid "Reported by" msgstr "" -#: risks/models.py:284 +#: risks/models.py:291 msgid "Risk created" msgstr "" -#: risks/models.py:285 +#: risks/models.py:292 msgid "Risk updated" msgstr "" -#: risks/models.py:286 +#: risks/models.py:293 msgid "Risk deleted" msgstr "" -#: risks/models.py:287 +#: risks/models.py:294 msgid "Risk review required" msgstr "" -#: risks/models.py:288 +#: risks/models.py:295 msgid "Risk review completed" msgstr "" -#: risks/models.py:290 +#: risks/models.py:297 msgid "Control created" msgstr "" -#: risks/models.py:291 +#: risks/models.py:298 msgid "Control updated" msgstr "" -#: risks/models.py:292 +#: risks/models.py:299 msgid "Control deleted" msgstr "" -#: risks/models.py:294 +#: risks/models.py:301 msgid "Residual created" msgstr "" -#: risks/models.py:295 +#: risks/models.py:302 msgid "Residual updated" msgstr "" -#: risks/models.py:296 +#: risks/models.py:303 msgid "Residual deleted" msgstr "" -#: risks/models.py:297 +#: risks/models.py:304 msgid "Residual review required" msgstr "" -#: risks/models.py:298 +#: risks/models.py:305 msgid "Residual review completed" msgstr "" -#: risks/models.py:300 +#: risks/models.py:307 msgid "Incident created" msgstr "" -#: risks/models.py:301 +#: risks/models.py:308 msgid "Incident updated" msgstr "" -#: risks/models.py:302 +#: risks/models.py:309 msgid "Incident deleted" msgstr "" -#: risks/models.py:304 +#: risks/models.py:311 msgid "User created" msgstr "" -#: risks/models.py:305 +#: risks/models.py:312 msgid "User deleted" msgstr "" -#: risks/models.py:314 risks/utils.py:142 +#: risks/models.py:321 risks/utils.py:79 msgid "Notification" msgstr "" -#: risks/models.py:315 templates/base.html:96 +#: risks/models.py:322 templates/base.html:96 #: templates/risks/notifications.html:4 msgid "Notifications" msgstr "" -#: risks/models.py:411 +#: risks/models.py:419 msgid "Notification rule" msgstr "" -#: risks/models.py:412 +#: risks/models.py:420 msgid "Notification rules" msgstr "" -#: risks/models.py:415 +#: risks/models.py:423 msgid "Event" msgstr "" -#: risks/models.py:420 +#: risks/models.py:428 msgid "Show in app" msgstr "" -#: risks/models.py:421 +#: risks/models.py:429 msgid "Send via email" msgstr "" -#: risks/models.py:425 +#: risks/models.py:433 msgid "Send to owner/responsible/reporter (if available)" msgstr "" -#: risks/models.py:428 +#: risks/models.py:436 msgid "Send to all staff" msgstr "" -#: risks/models.py:430 +#: risks/models.py:438 msgid "Extra recipients (emails, comma or newline separated)" msgstr "" @@ -432,108 +432,108 @@ msgstr "" msgid "Risk created: {t}" msgstr "" -#: risks/signals.py:121 +#: risks/signals.py:122 #, python-brace-format msgid "Risk updated: {t}" msgstr "" -#: risks/signals.py:134 +#: risks/signals.py:136 #, python-brace-format msgid "Risk deleted: {t}" msgstr "" -#: risks/signals.py:181 +#: risks/signals.py:184 #, python-brace-format msgid "Control {e}: {t}" msgstr "" -#: risks/signals.py:182 risks/signals.py:317 +#: risks/signals.py:185 risks/signals.py:326 msgid "created" msgstr "" -#: risks/signals.py:182 risks/signals.py:317 +#: risks/signals.py:185 risks/signals.py:326 msgid "updated" msgstr "" -#: risks/signals.py:196 +#: risks/signals.py:200 #, python-brace-format msgid "Control deleted: {t}" msgstr "" -#: risks/signals.py:213 +#: risks/signals.py:218 #, python-brace-format msgid "Residual review required for risk '{t}' due to control change" msgstr "" -#: risks/signals.py:239 +#: risks/signals.py:245 #, python-brace-format msgid "Residual created for risk: {t}" msgstr "" -#: risks/signals.py:257 +#: risks/signals.py:264 #, python-brace-format msgid "Residual review required for risk: {t}" msgstr "" -#: risks/signals.py:260 +#: risks/signals.py:267 #, python-brace-format msgid "Residual review completed for risk: {t}" msgstr "" -#: risks/signals.py:263 +#: risks/signals.py:270 #, python-brace-format msgid "Residual updated for risk: {t}" msgstr "" -#: risks/signals.py:280 +#: risks/signals.py:288 #, python-brace-format msgid "Residual deleted for risk: {t}" msgstr "" -#: risks/signals.py:316 +#: risks/signals.py:325 #, python-brace-format msgid "Incident {e}: {t}" msgstr "" -#: risks/signals.py:331 +#: risks/signals.py:341 #, python-brace-format msgid "Incident deleted: {t}" msgstr "" -#: risks/utils.py:66 +#: risks/utils.py:135 #, python-brace-format msgid "Follow-up reached: review required for risk '{t}'" msgstr "" -#: risks/views.py:208 +#: risks/views.py:218 msgid "Risk has been marked as reviewed and closed." msgstr "" -#: risks/views.py:210 +#: risks/views.py:220 msgid "Not all controls are completed. Risk cannot be closed yet." msgstr "" -#: risks/views.py:368 +#: risks/views.py:378 msgid "Notification marked as read." msgstr "" -#: risks/views.py:378 +#: risks/views.py:388 msgid "All notifications marked as read." msgstr "" -#: risks/views.py:397 +#: risks/views.py:407 msgid "Risk status updated." msgstr "" -#: risks/views.py:413 +#: risks/views.py:423 msgid "Control status updated." msgstr "" -#: risks/views.py:429 +#: risks/views.py:439 msgid "Incident status updated." msgstr "" -#: risks/views.py:446 +#: risks/views.py:456 msgid "Residual review flag updated." msgstr "" @@ -796,11 +796,11 @@ msgstr "" msgid "Related Risk" msgstr "" -#: templates/risks/list_controls.html:144 +#: templates/risks/list_controls.html:164 msgid "No controls found." msgstr "" -#: templates/risks/list_incidents.html:125 +#: templates/risks/list_incidents.html:137 msgid "No incidents found." msgstr "" diff --git a/risks/migrations/0028_notification_target_url.py b/risks/migrations/0028_notification_target_url.py new file mode 100644 index 0000000..11c738c --- /dev/null +++ b/risks/migrations/0028_notification_target_url.py @@ -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), + ), + ] diff --git a/risks/models.py b/risks/models.py index 88ec989..92c7788 100644 --- a/risks/models.py +++ b/risks/models.py @@ -337,6 +337,7 @@ class Notification(models.Model): content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, null=True, blank=True) object_id = models.PositiveIntegerField(null=True, blank=True) related_object = GenericForeignKey("content_type", "object_id") + target_url = models.CharField(max_length=500, blank=True, null=True) def __str__(self): user_display = self.user.username if self.user else "System" diff --git a/risks/signals.py b/risks/signals.py index 303218c..88b1691 100644 --- a/risks/signals.py +++ b/risks/signals.py @@ -101,6 +101,7 @@ def risk_saved(sender, instance: Risk, created, **kwargs): NotificationKind.RISK_CREATED, message=_("Risk created: {t}").format(t=instance.title), users=[instance.owner] if instance.owner_id else None, + obj=instance, ) else: # Diff audit log @@ -120,6 +121,7 @@ def risk_saved(sender, instance: Risk, created, **kwargs): NotificationKind.RISK_UPDATED, message=_("Risk updated: {t}").format(t=instance.title), 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, message=_("Risk deleted: {t}").format(t=instance.title), 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 ), 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, message=_("Control deleted: {t}").format(t=instance.title), 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( NotificationKind.RESIDUAL_REVIEW_REQUIRED, 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, message=_("Residual created for risk: {t}").format(t=instance.risk.title), users=[instance.risk.owner] if instance.risk.owner_id else None, + obj=instance, ) else: old = ResidualRisk.objects.get(pk=instance.pk) @@ -266,6 +273,7 @@ def residual_saved(sender, instance: ResidualRisk, created, **kwargs): kind, message=msg.format(t=instance.risk.title), 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, message=_("Residual deleted for risk: {t}").format(t=instance.risk.title), 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 ), 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, message=_("Incident deleted: {t}").format(t=instance.title), users=[instance.reported_by] if instance.reported_by_id else None, + obj=instance, ) diff --git a/risks/urls.py b/risks/urls.py index 287fef3..8a6f311 100644 --- a/risks/urls.py +++ b/risks/urls.py @@ -17,7 +17,7 @@ urlpatterns = [ path("risks/risks/", views.show_risk, name="show_risk"), path("risks/risk_matrix", views.risk_matrix, name="risk_matrix"), path("risks//status", views.update_risk_status, name="update_risk_status"), - path("risks//mark_reviewed/", views.mark_risk_reviewed, name="mark_risk_reviewed"), + path("risks//mark_risk_reviewed/", views.mark_risk_reviewed, name="mark_risk_reviewed"), # ----------------------------------------------------------------------- # Controls diff --git a/risks/utils.py b/risks/utils.py index 71e8940..a730c12 100644 --- a/risks/utils.py +++ b/risks/utils.py @@ -1,9 +1,10 @@ from datetime import date, datetime -from typing import Iterable, Optional +from typing import Any, Iterable, Optional from django.conf import settings from django.contrib.auth import get_user_model from django.core.mail import send_mail +from django.urls import reverse from django.utils.timezone import now from django.utils.translation import gettext_lazy as _ @@ -14,6 +15,74 @@ from .models import ( 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() @@ -85,6 +154,7 @@ def check_risk_followups(): NotificationKind.RISK_REVIEW_REQUIRED, message=message, users=[risk.owner] if risk.owner_id else None, + obj=risk, ) @@ -97,51 +167,3 @@ def _split_emails(value: str) -> list[str]: return [] raw = value.replace("\n", ",").split(",") 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, # don’t crash on mail error - ) diff --git a/risks/views.py b/risks/views.py index 89f1632..16a13d9 100644 --- a/risks/views.py +++ b/risks/views.py @@ -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 Risk, Control, ResidualRisk, AuditLog, Incident, Notification +from .models import AuditLog, Risk, Control, ResidualRisk, AuditLog, Incident, Notification from .serializers import ( ControlSerializer, RiskSerializer, ResidualRiskSerializer, UserSerializer, AuditSerializer, IncidentSerializer, @@ -205,6 +205,16 @@ def mark_risk_reviewed(request, pk): risk.status = "closed" risk._changed_by = request.user 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.")) else: messages.error(request, _("Not all controls are completed. Risk cannot be closed yet.")) diff --git a/templates/risks/list_controls.html b/templates/risks/list_controls.html index 8a73368..5f136cd 100644 --- a/templates/risks/list_controls.html +++ b/templates/risks/list_controls.html @@ -108,12 +108,18 @@ {{ c.id }} {{ c.title }} - {% if c.risk %} - - {{ c.risk.title }} - + {% if c.risks.all %} + {% else %} - – + - {% endif %} @@ -123,7 +129,21 @@ – {% endif %} - {{ c.get_status_display }} + + + {% if c.due_date %} {{ c.due_date|date:"d.m.Y" }} diff --git a/templates/risks/list_incidents.html b/templates/risks/list_incidents.html index c762ced..2394cf0 100644 --- a/templates/risks/list_incidents.html +++ b/templates/risks/list_incidents.html @@ -117,9 +117,21 @@ {% endif %} - {{ i.get_status_display }} - {{ i.date_reported|date:"d.m.Y" }} - {{ i.reported_by|default:"–" }} + + + + {{ i.date_reported|date:"d.m.Y" }} + {{ i.reported_by|default:"–" }} {% empty %} {% trans "No incidents found." %} diff --git a/templates/risks/list_risks.html b/templates/risks/list_risks.html index 71f0725..ac88940 100644 --- a/templates/risks/list_risks.html +++ b/templates/risks/list_risks.html @@ -140,21 +140,21 @@ {% if "1" in r.cia %} - + {% endif %} {% if "2" in r.cia %} - + {% endif %} {% if "3" in r.cia %} - + {% endif %} diff --git a/templates/risks/notifications.html b/templates/risks/notifications.html index 24e3b49..6b220c7 100644 --- a/templates/risks/notifications.html +++ b/templates/risks/notifications.html @@ -34,8 +34,8 @@ {% trans "New" %} {% endif %} - {% if n.get_link %} - {{ n.message }} + {% if n.target_url %} + {{ n.message }} {% else %} {{ n.message }} {% endif %}