HH/apps/observations/admin.py
2026-03-28 14:03:56 +03:00

224 lines
7.5 KiB
Python

"""
Observations admin configuration.
"""
from django.contrib import admin
from django.utils.html import format_html, mark_safe
from .models import (
Observation,
ObservationAttachment,
ObservationCategory,
ObservationNote,
ObservationStatusLog,
)
@admin.register(ObservationCategory)
class ObservationCategoryAdmin(admin.ModelAdmin):
"""Admin for ObservationCategory model."""
list_display = ["name_en", "name_ar", "is_active", "sort_order", "observation_count", "created_at"]
list_filter = ["is_active", "created_at"]
search_fields = ["name_en", "name_ar", "description"]
ordering = ["sort_order", "name_en"]
fieldsets = (
(None, {"fields": ("name_en", "name_ar", "description")}),
("Settings", {"fields": ("icon", "sort_order", "is_active")}),
)
def observation_count(self, obj):
return obj.observations.count()
observation_count.short_description = "Observations"
class ObservationAttachmentInline(admin.TabularInline):
"""Inline admin for ObservationAttachment."""
model = ObservationAttachment
extra = 0
readonly_fields = ["filename", "file_type", "file_size", "created_at"]
fields = ["file", "filename", "file_type", "file_size", "description", "created_at"]
class ObservationNoteInline(admin.TabularInline):
"""Inline admin for ObservationNote."""
model = ObservationNote
extra = 0
readonly_fields = ["created_by", "created_at"]
fields = ["note", "is_internal", "created_by", "created_at"]
class ObservationStatusLogInline(admin.TabularInline):
"""Inline admin for ObservationStatusLog."""
model = ObservationStatusLog
extra = 0
readonly_fields = ["from_status", "to_status", "changed_by", "comment", "created_at"]
fields = ["from_status", "to_status", "changed_by", "comment", "created_at"]
can_delete = False
def has_add_permission(self, request, obj=None):
return False
@admin.register(Observation)
class ObservationAdmin(admin.ModelAdmin):
"""Admin for Observation model."""
list_display = [
"tracking_code",
"title_display",
"category",
"severity_badge",
"status_badge",
"reporter_display",
"assigned_department",
"assigned_to",
"created_at",
]
list_filter = ["status", "severity", "category", "assigned_department", "created_at", "triaged_at", "resolved_at"]
search_fields = ["tracking_code", "title", "description", "reporter_name", "reporter_staff_id", "location_text"]
readonly_fields = [
"tracking_code",
"created_at",
"updated_at",
"triaged_at",
"resolved_at",
"closed_at",
"client_ip",
"user_agent",
]
ordering = ["-created_at"]
date_hierarchy = "created_at"
fieldsets = (
("Tracking", {"fields": ("tracking_code", "status")}),
("Content", {"fields": ("category", "title", "description", "severity")}),
("Location & Time", {"fields": ("location_text", "incident_datetime")}),
(
"Reporter Information",
{
"fields": ("reporter_staff_id", "reporter_name", "reporter_phone", "reporter_email"),
"classes": ("collapse",),
},
),
("Assignment", {"fields": ("assigned_department", "assigned_to")}),
("Triage", {"fields": ("triaged_by", "triaged_at"), "classes": ("collapse",)}),
("Resolution", {"fields": ("resolved_by", "resolved_at", "resolution_notes"), "classes": ("collapse",)}),
("Closure", {"fields": ("closed_by", "closed_at"), "classes": ("collapse",)}),
("Action Center", {"fields": ("action_id",), "classes": ("collapse",)}),
(
"Metadata",
{"fields": ("client_ip", "user_agent", "metadata", "created_at", "updated_at"), "classes": ("collapse",)},
),
)
inlines = [ObservationAttachmentInline, ObservationNoteInline, ObservationStatusLogInline]
def title_display(self, obj):
if obj.title:
return obj.title[:50] + "..." if len(obj.title) > 50 else obj.title
return obj.description[:50] + "..." if len(obj.description) > 50 else obj.description
title_display.short_description = "Title/Description"
def severity_badge(self, obj):
colors = {
"low": "#28a745",
"medium": "#ffc107",
"high": "#dc3545",
"critical": "#343a40",
}
color = colors.get(obj.severity, "#6c757d")
return format_html(
'<span style="background-color: {}; color: white; padding: 3px 8px; '
'border-radius: 4px; font-size: 11px;">{}</span>',
color,
obj.get_severity_display(),
)
severity_badge.short_description = "Severity"
def status_badge(self, obj):
colors = {
"new": "#007bff",
"triaged": "#17a2b8",
"assigned": "#17a2b8",
"in_progress": "#ffc107",
"resolved": "#28a745",
"closed": "#6c757d",
"rejected": "#dc3545",
"duplicate": "#6c757d",
}
color = colors.get(obj.status, "#6c757d")
return format_html(
'<span style="background-color: {}; color: white; padding: 3px 8px; '
'border-radius: 4px; font-size: 11px;">{}</span>',
color,
obj.get_status_display(),
)
status_badge.short_description = "Status"
def reporter_display(self, obj):
if obj.is_anonymous:
return mark_safe('<em style="color: #6c757d;">Anonymous</em>')
return obj.reporter_display
reporter_display.short_description = "Reporter"
@admin.register(ObservationAttachment)
class ObservationAttachmentAdmin(admin.ModelAdmin):
"""Admin for ObservationAttachment model."""
list_display = ["observation", "filename", "file_type", "file_size_display", "created_at"]
list_filter = ["file_type", "created_at"]
search_fields = ["observation__tracking_code", "filename", "description"]
readonly_fields = ["file_size", "created_at"]
def file_size_display(self, obj):
if obj.file_size < 1024:
return f"{obj.file_size} B"
elif obj.file_size < 1024 * 1024:
return f"{obj.file_size / 1024:.1f} KB"
else:
return f"{obj.file_size / (1024 * 1024):.1f} MB"
file_size_display.short_description = "Size"
@admin.register(ObservationNote)
class ObservationNoteAdmin(admin.ModelAdmin):
"""Admin for ObservationNote model."""
list_display = ["observation", "note_preview", "created_by", "is_internal", "created_at"]
list_filter = ["is_internal", "created_at"]
search_fields = ["observation__tracking_code", "note", "created_by__email"]
readonly_fields = ["created_at"]
def note_preview(self, obj):
return obj.note[:100] + "..." if len(obj.note) > 100 else obj.note
note_preview.short_description = "Note"
@admin.register(ObservationStatusLog)
class ObservationStatusLogAdmin(admin.ModelAdmin):
"""Admin for ObservationStatusLog model."""
list_display = ["observation", "from_status", "to_status", "changed_by", "created_at"]
list_filter = ["from_status", "to_status", "created_at"]
search_fields = ["observation__tracking_code", "comment", "changed_by__email"]
readonly_fields = ["observation", "from_status", "to_status", "changed_by", "comment", "created_at"]
def has_add_permission(self, request):
return False
def has_change_permission(self, request, obj=None):
return False