petcare-api / api /admin.py
Sameer669
Initial PawCare Django backend with JWT auth, RBAC, audit logging, and HF storage
4f01198
"""
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(
'<span style="color:{};font-weight:bold">β˜… {}</span>', color, obj.rating
)
@admin.display(description='Profile Image')
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 'β€”'
@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