2026-01-04 10:32:40 +03:00

222 lines
7.6 KiB
Python

"""
Observations admin configuration.
"""
from django.contrib import admin
from django.utils.html import format_html
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 format_html('<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