222 lines
7.6 KiB
Python
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
|