""" Django Admin Configuration for PawCare All sensitive mutations via admin are auto-captured by AuditLogMiddleware. Caregivers are managed exclusively through this panel. """ from django.contrib import admin from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from django.utils.html import format_html from api.models import ( User, Service, Caregiver, CaregiverService, Pet, Booking, Conversation, Message, ) from audit.models import AuditLog # ───────────────────────────────────────────────────────────────────────────── # User Admin # ───────────────────────────────────────────────────────────────────────────── @admin.register(User) class UserAdmin(BaseUserAdmin): list_display = ['email', 'full_name', 'role', 'is_active', 'date_joined'] list_filter = ['role', 'is_active', 'is_staff'] search_fields = ['email', 'first_name', 'last_name'] ordering = ['-date_joined'] readonly_fields = ['id', 'date_joined', 'last_login'] fieldsets = ( ('Account', {'fields': ('id', 'email', 'password')}), ('Personal Info', {'fields': ('first_name', 'last_name', 'avatar_url')}), ('Encrypted PII', {'fields': ('phone', 'address'), 'classes': ('collapse',)}), ('Role & Permissions', {'fields': ('role', 'is_active', 'is_staff', 'is_superuser', 'groups', 'user_permissions')}), ('Timestamps', {'fields': ('date_joined', 'last_login')}), ) add_fieldsets = ( (None, { 'classes': ('wide',), 'fields': ('email', 'first_name', 'last_name', 'role', 'password1', 'password2'), }), ) # ───────────────────────────────────────────────────────────────────────────── # Service Admin # ───────────────────────────────────────────────────────────────────────────── @admin.register(Service) class ServiceAdmin(admin.ModelAdmin): list_display = ['name', 'icon', 'base_price', 'duration_minutes', 'is_active'] list_editable = ['is_active', 'base_price'] search_fields = ['name'] # ───────────────────────────────────────────────────────────────────────────── # Caregiver Admin — primary management interface # ───────────────────────────────────────────────────────────────────────────── class CaregiverServiceInline(admin.TabularInline): model = CaregiverService extra = 1 fields = ['service', 'price_per_hour', 'is_active'] @admin.register(Caregiver) class CaregiverAdmin(admin.ModelAdmin): list_display = [ 'user', 'city', 'rating_badge', 'total_bookings', 'is_available', 'is_verified', 'is_featured', 'created_at', ] list_filter = ['is_available', 'is_verified', 'is_featured', 'background_check_passed', 'city'] search_fields = ['user__email', 'user__first_name', 'user__last_name', 'city'] readonly_fields = ['id', 'rating', 'total_reviews', 'total_bookings', 'created_at', 'updated_at', 'profile_image_preview'] inlines = [CaregiverServiceInline] list_per_page = 25 actions = ['mark_verified', 'mark_featured', 'mark_unavailable'] fieldsets = ( ('Identity', {'fields': ('id', 'user', 'profile_image_preview', 'profile_image_url')}), ('Profile', {'fields': ('bio', 'years_of_experience', 'specializations', 'certifications', 'languages')}), ('Location', {'fields': ('city', 'country', 'latitude', 'longitude')}), ('Media', {'fields': ('gallery_images',), 'classes': ('collapse',)}), ('Stats', {'fields': ('rating', 'total_reviews', 'total_bookings')}), ('Status', {'fields': ('is_available', 'is_verified', 'is_featured', 'background_check_passed')}), ('Sensitive', {'fields': ('emergency_contact',), 'classes': ('collapse',)}), ('Timestamps', {'fields': ('created_at', 'updated_at')}), ) @admin.display(description='Rating') def rating_badge(self, obj): color = '#22c55e' if obj.rating >= 4 else '#f59e0b' if obj.rating >= 3 else '#ef4444' return format_html( '★ {}', color, obj.rating ) @admin.display(description='Profile Image') def profile_image_preview(self, obj): if obj.profile_image_url: return format_html( '', obj.profile_image_url ) return '—' @admin.action(description='✅ Mark selected as Verified') def mark_verified(self, request, queryset): queryset.update(is_verified=True, background_check_passed=True) @admin.action(description='⭐ Toggle Featured') def mark_featured(self, request, queryset): for c in queryset: c.is_featured = not c.is_featured c.save(update_fields=['is_featured']) @admin.action(description='🔴 Mark as Unavailable') def mark_unavailable(self, request, queryset): queryset.update(is_available=False) # ───────────────────────────────────────────────────────────────────────────── # Pet Admin # ───────────────────────────────────────────────────────────────────────────── @admin.register(Pet) class PetAdmin(admin.ModelAdmin): list_display = ['name', 'pet_type', 'owner', 'is_vaccinated', 'is_neutered'] list_filter = ['pet_type', 'is_vaccinated', 'is_neutered'] search_fields = ['name', 'owner__email', 'breed'] readonly_fields = ['id', 'created_at', 'updated_at'] # ───────────────────────────────────────────────────────────────────────────── # Booking Admin # ───────────────────────────────────────────────────────────────────────────── @admin.register(Booking) class BookingAdmin(admin.ModelAdmin): list_display = [ 'short_id', 'user', 'caregiver', 'service', 'status', 'scheduled_start', 'price_total', 'rating', ] list_filter = ['status', 'service', 'payment_status'] search_fields = ['user__email', 'caregiver__user__email'] readonly_fields = ['id', 'price_subtotal', 'price_fees', 'price_total', 'created_at', 'updated_at'] date_hierarchy = 'scheduled_start' @admin.display(description='ID') def short_id(self, obj): return str(obj.id)[:8] + '…' # ───────────────────────────────────────────────────────────────────────────── # Messaging Admin # ───────────────────────────────────────────────────────────────────────────── class MessageInline(admin.TabularInline): model = Message extra = 0 readonly_fields = ['sender', 'created_at', 'is_read'] fields = ['sender', 'message_type', 'content', 'is_read', 'created_at'] @admin.register(Conversation) class ConversationAdmin(admin.ModelAdmin): list_display = ['id', 'booking', 'created_at', 'updated_at'] readonly_fields = ['id', 'created_at', 'updated_at'] inlines = [MessageInline] # ───────────────────────────────────────────────────────────────────────────── # Audit Log Admin — read-only # ───────────────────────────────────────────────────────────────────────────── @admin.register(AuditLog) class AuditLogAdmin(admin.ModelAdmin): list_display = ['timestamp', 'actor_email', 'actor_role', 'action', 'resource_type', 'ip_address', 'response_status'] list_filter = ['action', 'actor_role', 'response_status'] search_fields = ['actor_email', 'resource_id', 'request_path', 'description'] readonly_fields = [f.name for f in AuditLog._meta.fields] date_hierarchy = 'timestamp' def has_add_permission(self, request): return False def has_change_permission(self, request, obj=None): return False def has_delete_permission(self, request, obj=None): return False