2025-09-07 20:52:19 +02:00
|
|
|
from django.contrib import admin
|
|
|
|
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
2025-09-12 13:04:04 +02:00
|
|
|
from django.http import HttpResponseRedirect
|
|
|
|
from django.urls import reverse
|
Refactor risk management application with enhanced localization, user authentication, and UI improvements
- Added verbose names for Incident and ResidualRisk models for better clarity in admin interface.
- Updated impact choices for ResidualRisk and Risk models to ensure consistency and clarity.
- Implemented gettext_lazy for translatable strings in models and choices.
- Enhanced the Risk, ResidualRisk, Control, AuditLog, and Incident models with Meta options for better admin representation.
- Added login required decorators to views for improved security.
- Introduced new CSS variables and classes for better visual representation of risk levels.
- Created custom template tags for dynamic CSS class assignment based on risk likelihood and impact.
- Improved dashboard and statistics views with user authentication checks.
- Updated templates for risks, controls, incidents, and admin interface to include edit and delete options for staff users.
- Added new login and logout templates for user authentication.
- Enhanced list views for risks, controls, and incidents to include action buttons for staff users.
- Improved overall UI/UX with Bulma CSS framework for a more modern look and feel.
2025-09-09 14:25:59 +02:00
|
|
|
from django.utils.translation import gettext_lazy as _
|
2025-09-07 20:52:19 +02:00
|
|
|
|
2025-09-12 13:04:04 +02:00
|
|
|
from .models import (
|
|
|
|
Control,
|
|
|
|
Incident,
|
|
|
|
Notification,
|
|
|
|
NotificationPreference,
|
|
|
|
NotificationRule,
|
|
|
|
Risk,
|
|
|
|
ResidualRisk,
|
|
|
|
User,
|
|
|
|
)
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# Global Admin Settings
|
|
|
|
# ---------------------------------------------------------------------------
|
Refactor risk management application with enhanced localization, user authentication, and UI improvements
- Added verbose names for Incident and ResidualRisk models for better clarity in admin interface.
- Updated impact choices for ResidualRisk and Risk models to ensure consistency and clarity.
- Implemented gettext_lazy for translatable strings in models and choices.
- Enhanced the Risk, ResidualRisk, Control, AuditLog, and Incident models with Meta options for better admin representation.
- Added login required decorators to views for improved security.
- Introduced new CSS variables and classes for better visual representation of risk levels.
- Created custom template tags for dynamic CSS class assignment based on risk likelihood and impact.
- Improved dashboard and statistics views with user authentication checks.
- Updated templates for risks, controls, incidents, and admin interface to include edit and delete options for staff users.
- Added new login and logout templates for user authentication.
- Enhanced list views for risks, controls, and incidents to include action buttons for staff users.
- Improved overall UI/UX with Bulma CSS framework for a more modern look and feel.
2025-09-09 14:25:59 +02:00
|
|
|
admin.site.site_header = _("Administration")
|
|
|
|
admin.site.site_title = _("Admin")
|
|
|
|
admin.site.index_title = _("Administration")
|
2025-09-07 20:52:19 +02:00
|
|
|
|
2025-09-12 13:04:04 +02:00
|
|
|
|
|
|
|
# ---- Inlines ----
|
Add risk status and notification preferences
- Introduced a new `status` field to the `Risk` model with choices for "open", "in_progress", "closed", and "review_required".
- Created a `NotificationPreference` model to manage user notification settings for various events related to risks, controls, residual risks, reviews, users, and incidents.
- Updated the admin interface to include `NotificationPreference` inline with the `User` admin.
- Enhanced signal handlers to send notifications based on user preferences for created, updated, and deleted events for users, risks, controls, and incidents.
- Modified the `check_risk_followups` utility function to update risk status and create notifications for follow-ups.
- Updated serializers and views to accommodate the new `status` field and improved risk listing functionality.
- Added a new section in the risk detail template to display related incidents.
- Removed the unused statistics view from URLs.
2025-09-10 11:54:08 +02:00
|
|
|
class NotificationPreferenceInline(admin.StackedInline):
|
2025-09-12 13:04:04 +02:00
|
|
|
"""Preferences inline for notifications on User model"""
|
Add risk status and notification preferences
- Introduced a new `status` field to the `Risk` model with choices for "open", "in_progress", "closed", and "review_required".
- Created a `NotificationPreference` model to manage user notification settings for various events related to risks, controls, residual risks, reviews, users, and incidents.
- Updated the admin interface to include `NotificationPreference` inline with the `User` admin.
- Enhanced signal handlers to send notifications based on user preferences for created, updated, and deleted events for users, risks, controls, and incidents.
- Modified the `check_risk_followups` utility function to update risk status and create notifications for follow-ups.
- Updated serializers and views to accommodate the new `status` field and improved risk listing functionality.
- Added a new section in the risk detail template to display related incidents.
- Removed the unused statistics view from URLs.
2025-09-10 11:54:08 +02:00
|
|
|
model = NotificationPreference
|
|
|
|
can_delete = False
|
|
|
|
extra = 0
|
|
|
|
fieldsets = (
|
2025-09-12 13:04:04 +02:00
|
|
|
(_("Risks"), {"fields": ("risk_created", "risk_updated", "risk_deleted")}),
|
|
|
|
(_("Controls"), {"fields": ("control_created", "control_updated", "control_deleted")}),
|
|
|
|
(_("Residual risks"), {"fields": ("residual_created", "residual_updated", "residual_deleted")}),
|
|
|
|
(_("Reviews"), {"fields": ("review_required", "review_completed")}),
|
|
|
|
(_("Incidents"), {"fields": ("incident_created", "incident_updated", "incident_deleted")}),
|
|
|
|
(_("Users"), {"fields": ("user_created", "user_deleted")}),
|
Add risk status and notification preferences
- Introduced a new `status` field to the `Risk` model with choices for "open", "in_progress", "closed", and "review_required".
- Created a `NotificationPreference` model to manage user notification settings for various events related to risks, controls, residual risks, reviews, users, and incidents.
- Updated the admin interface to include `NotificationPreference` inline with the `User` admin.
- Enhanced signal handlers to send notifications based on user preferences for created, updated, and deleted events for users, risks, controls, and incidents.
- Modified the `check_risk_followups` utility function to update risk status and create notifications for follow-ups.
- Updated serializers and views to accommodate the new `status` field and improved risk listing functionality.
- Added a new section in the risk detail template to display related incidents.
- Removed the unused statistics view from URLs.
2025-09-10 11:54:08 +02:00
|
|
|
)
|
|
|
|
|
2025-09-12 13:04:04 +02:00
|
|
|
|
2025-09-07 20:52:19 +02:00
|
|
|
class ResidualRiskInline(admin.StackedInline):
|
2025-09-12 13:04:04 +02:00
|
|
|
"""Inline editor for ResidualRisk (one-to-one with Risk)"""
|
2025-09-07 20:52:19 +02:00
|
|
|
model = ResidualRisk
|
|
|
|
extra = 0
|
Refactor risk management application with enhanced localization, user authentication, and UI improvements
- Added verbose names for Incident and ResidualRisk models for better clarity in admin interface.
- Updated impact choices for ResidualRisk and Risk models to ensure consistency and clarity.
- Implemented gettext_lazy for translatable strings in models and choices.
- Enhanced the Risk, ResidualRisk, Control, AuditLog, and Incident models with Meta options for better admin representation.
- Added login required decorators to views for improved security.
- Introduced new CSS variables and classes for better visual representation of risk levels.
- Created custom template tags for dynamic CSS class assignment based on risk likelihood and impact.
- Improved dashboard and statistics views with user authentication checks.
- Updated templates for risks, controls, incidents, and admin interface to include edit and delete options for staff users.
- Added new login and logout templates for user authentication.
- Enhanced list views for risks, controls, and incidents to include action buttons for staff users.
- Improved overall UI/UX with Bulma CSS framework for a more modern look and feel.
2025-09-09 14:25:59 +02:00
|
|
|
can_delete = False
|
2025-09-07 20:52:19 +02:00
|
|
|
readonly_fields = ("score", "level", "review_required")
|
|
|
|
fields = ("likelihood", "impact", "score", "level", "review_required")
|
|
|
|
|
2025-09-12 13:04:04 +02:00
|
|
|
|
2025-09-09 12:00:29 +02:00
|
|
|
class ControlRisksInline(admin.TabularInline):
|
2025-09-12 13:04:04 +02:00
|
|
|
"""M2M relation between Risk and Control"""
|
2025-09-09 12:00:29 +02:00
|
|
|
model = Control.risks.through
|
|
|
|
fk_name = "risk"
|
|
|
|
extra = 1
|
|
|
|
autocomplete_fields = ("control",)
|
2025-09-07 20:52:19 +02:00
|
|
|
|
2025-09-10 10:49:14 +02:00
|
|
|
|
2025-09-12 13:04:04 +02:00
|
|
|
class NotificationInline(admin.TabularInline):
|
|
|
|
"""Inline display of notifications on User model"""
|
|
|
|
model = Notification
|
|
|
|
fields = ("created_at", "message", "read", "sent")
|
|
|
|
readonly_fields = ("created_at", "message")
|
|
|
|
extra = 0
|
|
|
|
ordering = ("-created_at",)
|
2025-09-10 10:49:14 +02:00
|
|
|
|
2025-09-07 20:52:19 +02:00
|
|
|
|
2025-09-12 13:04:04 +02:00
|
|
|
# ---- Shared Mixins ----
|
|
|
|
class ChangedByMixin:
|
|
|
|
"""Automatically track user who created/changed/deleted"""
|
2025-09-07 20:52:19 +02:00
|
|
|
def save_model(self, request, obj, form, change):
|
|
|
|
obj._changed_by = request.user
|
|
|
|
super().save_model(request, obj, form, change)
|
|
|
|
|
|
|
|
def delete_model(self, request, obj):
|
|
|
|
obj._changed_by = request.user
|
|
|
|
super().delete_model(request, obj)
|
|
|
|
|
2025-09-12 13:04:04 +02:00
|
|
|
|
|
|
|
class RedirectOnSaveMixin:
|
|
|
|
"""Redirect to detail view instead of staying in admin"""
|
|
|
|
redirect_url_name = None
|
|
|
|
|
|
|
|
def response_add(self, request, obj, post_url_continue=None):
|
|
|
|
return HttpResponseRedirect(reverse(self.redirect_url_name, args=[obj.pk]))
|
|
|
|
|
|
|
|
def response_change(self, request, obj):
|
|
|
|
return HttpResponseRedirect(reverse(self.redirect_url_name, args=[obj.pk]))
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# Risk
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@admin.register(Risk)
|
|
|
|
class RiskAdmin(ChangedByMixin, RedirectOnSaveMixin, admin.ModelAdmin):
|
|
|
|
redirect_url_name = "risks:show_risk"
|
|
|
|
list_display = ("title", "owner_name", "status", "score", "level", "likelihood", "impact", "follow_up")
|
|
|
|
list_filter = ("status", "level", "likelihood", "impact", "owner")
|
|
|
|
search_fields = ("title", "asset", "process", "category")
|
|
|
|
inlines = [ResidualRiskInline, ControlRisksInline]
|
|
|
|
|
|
|
|
def owner_name(self, obj):
|
|
|
|
if not obj.owner:
|
|
|
|
return "-"
|
|
|
|
return obj.owner.get_full_name() or obj.owner.username
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# Residual Risk
|
|
|
|
# ---------------------------------------------------------------------------
|
2025-09-07 20:52:19 +02:00
|
|
|
@admin.register(ResidualRisk)
|
2025-09-12 13:04:04 +02:00
|
|
|
class ResidualRiskAdmin(ChangedByMixin, RedirectOnSaveMixin, admin.ModelAdmin):
|
|
|
|
redirect_url_name = "risks:show_risk"
|
|
|
|
list_display = ("risk", "score", "level", "likelihood", "impact", "review_required")
|
2025-09-07 20:52:19 +02:00
|
|
|
list_filter = ("level", "likelihood", "impact", "review_required")
|
|
|
|
|
|
|
|
|
2025-09-12 13:04:04 +02:00
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# Control
|
|
|
|
# ---------------------------------------------------------------------------
|
2025-09-07 20:52:19 +02:00
|
|
|
@admin.register(Control)
|
2025-09-12 13:04:04 +02:00
|
|
|
class ControlAdmin(ChangedByMixin, RedirectOnSaveMixin, admin.ModelAdmin):
|
|
|
|
redirect_url_name = "risks:show_control"
|
2025-09-09 12:00:29 +02:00
|
|
|
list_display = ("title", "status", "due_date", "responsible")
|
2025-09-07 20:52:19 +02:00
|
|
|
list_filter = ("status", "due_date")
|
2025-09-12 13:04:04 +02:00
|
|
|
autocomplete_fields = ("risks", "responsible")
|
2025-09-07 20:52:19 +02:00
|
|
|
search_fields = ("title", "description")
|
|
|
|
|
|
|
|
|
2025-09-12 13:04:04 +02:00
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# Incident
|
|
|
|
# ---------------------------------------------------------------------------
|
2025-09-07 20:52:19 +02:00
|
|
|
@admin.register(Incident)
|
2025-09-12 13:04:04 +02:00
|
|
|
class IncidentAdmin(ChangedByMixin, RedirectOnSaveMixin, admin.ModelAdmin):
|
|
|
|
redirect_url_name = "risks:show_incident"
|
2025-09-07 20:52:19 +02:00
|
|
|
list_display = ("title", "date_reported", "reported_by", "status")
|
|
|
|
list_filter = ("status", "date_reported", "reported_by")
|
|
|
|
search_fields = ("title", "description")
|
|
|
|
autocomplete_fields = ("related_risks",)
|
2025-09-12 13:04:04 +02:00
|
|
|
filter_horizontal = ("related_risks",)
|
2025-09-07 20:52:19 +02:00
|
|
|
|
Add risk status and notification preferences
- Introduced a new `status` field to the `Risk` model with choices for "open", "in_progress", "closed", and "review_required".
- Created a `NotificationPreference` model to manage user notification settings for various events related to risks, controls, residual risks, reviews, users, and incidents.
- Updated the admin interface to include `NotificationPreference` inline with the `User` admin.
- Enhanced signal handlers to send notifications based on user preferences for created, updated, and deleted events for users, risks, controls, and incidents.
- Modified the `check_risk_followups` utility function to update risk status and create notifications for follow-ups.
- Updated serializers and views to accommodate the new `status` field and improved risk listing functionality.
- Added a new section in the risk detail template to display related incidents.
- Removed the unused statistics view from URLs.
2025-09-10 11:54:08 +02:00
|
|
|
|
2025-09-12 13:04:04 +02:00
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# Notification
|
|
|
|
# ---------------------------------------------------------------------------
|
2025-09-10 13:44:03 +02:00
|
|
|
@admin.register(Notification)
|
|
|
|
class NotificationAdmin(admin.ModelAdmin):
|
|
|
|
date_hierarchy = "created_at"
|
|
|
|
list_display = ("id", "created_at", "user_display", "short_message", "read", "sent")
|
|
|
|
list_display_links = ("id", "short_message")
|
|
|
|
list_filter = ("read", "sent", "created_at")
|
|
|
|
search_fields = ("message", "user__username", "user__first_name", "user__last_name", "user__email")
|
|
|
|
list_select_related = ("user",)
|
|
|
|
list_editable = ("read", "sent")
|
|
|
|
ordering = ("-created_at",)
|
|
|
|
autocomplete_fields = ("user",)
|
2025-09-12 13:04:04 +02:00
|
|
|
actions = ["mark_as_read", "mark_as_unread", "mark_as_sent", "mark_as_unsent"]
|
2025-09-10 13:44:03 +02:00
|
|
|
|
|
|
|
@admin.display(description=_("User"))
|
|
|
|
def user_display(self, obj):
|
2025-09-12 13:04:04 +02:00
|
|
|
return obj.user.get_full_name() if obj.user else "—"
|
2025-09-10 13:44:03 +02:00
|
|
|
|
|
|
|
@admin.display(description=_("Message"))
|
|
|
|
def short_message(self, obj):
|
|
|
|
msg = obj.message or ""
|
|
|
|
return (msg[:80] + "…") if len(msg) > 80 else msg
|
|
|
|
|
2025-09-12 13:04:04 +02:00
|
|
|
# Bulk actions
|
2025-09-10 13:44:03 +02:00
|
|
|
@admin.action(description=_("Mark selected as read"))
|
|
|
|
def mark_as_read(self, request, queryset):
|
|
|
|
n = queryset.update(read=True)
|
|
|
|
self.message_user(request, _("%(n)d notifications marked as read.") % {"n": n})
|
|
|
|
|
|
|
|
@admin.action(description=_("Mark selected as unread"))
|
|
|
|
def mark_as_unread(self, request, queryset):
|
|
|
|
n = queryset.update(read=False)
|
|
|
|
self.message_user(request, _("%(n)d notifications marked as unread.") % {"n": n})
|
|
|
|
|
|
|
|
@admin.action(description=_("Mark selected as sent"))
|
|
|
|
def mark_as_sent(self, request, queryset):
|
|
|
|
n = queryset.update(sent=True)
|
|
|
|
self.message_user(request, _("%(n)d notifications marked as sent.") % {"n": n})
|
|
|
|
|
|
|
|
@admin.action(description=_("Mark selected as unsent"))
|
|
|
|
def mark_as_unsent(self, request, queryset):
|
|
|
|
n = queryset.update(sent=False)
|
|
|
|
self.message_user(request, _("%(n)d notifications marked as unsent.") % {"n": n})
|
|
|
|
|
|
|
|
|
2025-09-12 13:04:04 +02:00
|
|
|
# ---- Notification Rule ----
|
2025-09-10 14:26:29 +02:00
|
|
|
@admin.register(NotificationRule)
|
|
|
|
class NotificationRuleAdmin(admin.ModelAdmin):
|
|
|
|
list_display = ("kind", "enabled_in_app", "enabled_email", "to_owner", "to_staff", "short_extras")
|
|
|
|
list_editable = ("enabled_in_app", "enabled_email", "to_owner", "to_staff")
|
|
|
|
list_filter = ("enabled_in_app", "enabled_email", "to_owner", "to_staff")
|
|
|
|
search_fields = ("kind", "extra_recipients")
|
|
|
|
ordering = ("kind",)
|
|
|
|
|
|
|
|
@admin.display(description=_("Extra recipients"))
|
|
|
|
def short_extras(self, obj):
|
|
|
|
txt = (obj.extra_recipients or "").replace("\n", ", ")
|
|
|
|
return (txt[:50] + "…") if len(txt) > 50 else txt
|
|
|
|
|
2025-09-12 13:04:04 +02:00
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# User (extension)
|
|
|
|
# ---------------------------------------------------------------------------
|
2025-09-10 13:44:03 +02:00
|
|
|
@admin.register(User)
|
|
|
|
class UserAdmin(BaseUserAdmin):
|
|
|
|
fieldsets = BaseUserAdmin.fieldsets + (
|
|
|
|
(_("SSO Information"), {"fields": ("is_sso_user",)}),
|
|
|
|
)
|
2025-09-12 13:04:04 +02:00
|
|
|
list_display = (
|
|
|
|
"username", "email", "is_staff", "is_superuser", "is_sso_user",
|
|
|
|
"owned_risks_count", "responsible_controls_count"
|
|
|
|
)
|
2025-09-10 13:44:03 +02:00
|
|
|
inlines = [NotificationInline, NotificationPreferenceInline]
|
|
|
|
|
|
|
|
def owned_risks_count(self, obj):
|
|
|
|
return obj.risks_owned.count()
|
|
|
|
owned_risks_count.short_description = _("Risks Owned")
|
|
|
|
|
|
|
|
def responsible_controls_count(self, obj):
|
|
|
|
return obj.controls_responsible.count()
|
2025-09-12 13:04:04 +02:00
|
|
|
responsible_controls_count.short_description = _("Controls Responsible")
|