Spaces:
Sleeping
Sleeping
| """ | |
| 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 | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| 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 | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| 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'] | |
| 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')}), | |
| ) | |
| def rating_badge(self, obj): | |
| color = '#22c55e' if obj.rating >= 4 else '#f59e0b' if obj.rating >= 3 else '#ef4444' | |
| return format_html( | |
| '<span style="color:{};font-weight:bold">β {}</span>', color, obj.rating | |
| ) | |
| def profile_image_preview(self, obj): | |
| if obj.profile_image_url: | |
| return format_html( | |
| '<img src="{}" style="height:80px;border-radius:8px;"/>', obj.profile_image_url | |
| ) | |
| return 'β' | |
| def mark_verified(self, request, queryset): | |
| queryset.update(is_verified=True, background_check_passed=True) | |
| def mark_featured(self, request, queryset): | |
| for c in queryset: | |
| c.is_featured = not c.is_featured | |
| c.save(update_fields=['is_featured']) | |
| def mark_unavailable(self, request, queryset): | |
| queryset.update(is_available=False) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Pet Admin | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| 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 | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| 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' | |
| 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'] | |
| 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 | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| 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 | |