224 lines
7.5 KiB
Python
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
|