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 ""
"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 <kevin@example.com>\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."

View file

@ -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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 ""

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)
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"

View file

@ -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,
)

View file

@ -17,7 +17,7 @@ urlpatterns = [
path("risks/risks/<int:id>", views.show_risk, name="show_risk"),
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: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

View file

@ -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, # dont crash on mail error
)

View file

@ -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."))

View file

@ -108,12 +108,18 @@
<td class="has-text-centered">{{ c.id }}</td>
<td>{{ c.title }}</td>
<td>
{% if c.risk %}
<a href="{% url 'risks:show_risk' c.risk.id %}" onclick="event.stopPropagation();">
{{ c.risk.title }}
</a>
{% if c.risks.all %}
<ul>
{% for r in c.risks.all %}
<li>
<a href="{% url 'risks:show_risk' r.id %}" onclick="event.stopPropagation();">
{{ r.title }}
</a>
</li>
{% endfor %}
</ul>
{% else %}
-
{% endif %}
</td>
<td class="has-text-centered">
@ -123,7 +129,21 @@
{% endif %}
</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">
{% if c.due_date %}
{{ c.due_date|date:"d.m.Y" }}

View file

@ -117,9 +117,21 @@
<span class="has-text-grey"></span>
{% endif %}
</td>
<td>{{ i.get_status_display }}</td>
<td>{{ i.date_reported|date:"d.m.Y" }}</td>
<td>{{ i.reported_by|default:"" }}</td>
<td class="has-text-centered">
<div class="tag
{% 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>
{% empty %}
<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">
{% if "1" in r.cia %}
<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>
{% endif %}
</td>
<td class="has-text-centered">
{% if "2" in r.cia %}
<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>
{% endif %}
</td>
<td class="has-text-centered">
{% if "3" in r.cia %}
<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>
{% endif %}
</td>

View file

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