""" Django admin configuration for User models. """ from django.contrib import admin from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from django.utils.html import format_html from django.urls import reverse from django.utils.safestring import mark_safe from unfold.admin import ModelAdmin from unfold.decorators import display from import_export import resources from import_export.admin import ImportExportModelAdmin from .models import User, UserRole, UserProfile, UserRideCredit, UserTopList, UserTopListItem class UserResource(resources.ModelResource): """Resource for importing/exporting users.""" class Meta: model = User fields = ( 'id', 'email', 'username', 'first_name', 'last_name', 'date_joined', 'last_login', 'is_active', 'is_staff', 'banned', 'reputation_score', 'mfa_enabled' ) export_order = fields class UserRoleInline(admin.StackedInline): """Inline for user role.""" model = UserRole can_delete = False verbose_name_plural = 'Role' fk_name = 'user' fields = ('role', 'granted_by', 'granted_at') readonly_fields = ('granted_at',) class UserProfileInline(admin.StackedInline): """Inline for user profile.""" model = UserProfile can_delete = False verbose_name_plural = 'Profile & Preferences' fk_name = 'user' fields = ( ('email_notifications', 'email_on_submission_approved', 'email_on_submission_rejected'), ('profile_public', 'show_email'), ('total_submissions', 'approved_submissions'), ) readonly_fields = ('total_submissions', 'approved_submissions') @admin.register(User) class UserAdmin(BaseUserAdmin, ModelAdmin, ImportExportModelAdmin): """Admin interface for User model.""" resource_class = UserResource list_display = [ 'email', 'username', 'display_name_admin', 'role_badge', 'reputation_badge', 'status_badge', 'mfa_badge', 'date_joined', 'last_login', ] list_filter = [ 'is_active', 'is_staff', 'is_superuser', 'banned', 'mfa_enabled', 'oauth_provider', 'date_joined', 'last_login', ] search_fields = [ 'email', 'username', 'first_name', 'last_name', ] ordering = ['-date_joined'] fieldsets = ( ('Account Information', { 'fields': ('email', 'username', 'password') }), ('Personal Information', { 'fields': ('first_name', 'last_name', 'avatar_url', 'bio') }), ('Permissions', { 'fields': ( 'is_active', 'is_staff', 'is_superuser', 'groups', 'user_permissions', ) }), ('Moderation', { 'fields': ( 'banned', 'ban_reason', 'banned_at', 'banned_by', ) }), ('OAuth', { 'fields': ('oauth_provider', 'oauth_sub'), 'classes': ('collapse',) }), ('Security', { 'fields': ('mfa_enabled', 'reputation_score'), }), ('Timestamps', { 'fields': ('date_joined', 'last_login'), 'classes': ('collapse',) }), ) add_fieldsets = ( ('Create New User', { 'classes': ('wide',), 'fields': ('email', 'username', 'password1', 'password2'), }), ) readonly_fields = [ 'date_joined', 'last_login', 'banned_at', 'oauth_provider', 'oauth_sub', ] inlines = [UserRoleInline, UserProfileInline] @display(description="Name", label=True) def display_name_admin(self, obj): """Display user's display name.""" return obj.display_name or '-' @display(description="Role", label=True) def role_badge(self, obj): """Display user role with badge.""" try: role = obj.role.role colors = { 'admin': 'red', 'moderator': 'blue', 'user': 'green', } return format_html( '{}', colors.get(role, 'gray'), role.upper() ) except UserRole.DoesNotExist: return format_html('No Role') @display(description="Reputation", label=True) def reputation_badge(self, obj): """Display reputation score.""" score = obj.reputation_score if score >= 100: color = 'green' elif score >= 50: color = 'blue' elif score >= 0: color = 'gray' else: color = 'red' return format_html( '{}', color, score ) @display(description="Status", label=True) def status_badge(self, obj): """Display user status.""" if obj.banned: return format_html( 'BANNED' ) elif not obj.is_active: return format_html( 'INACTIVE' ) else: return format_html( 'ACTIVE' ) @display(description="MFA", label=True) def mfa_badge(self, obj): """Display MFA status.""" if obj.mfa_enabled: return format_html( '✓ Enabled' ) else: return format_html( '✗ Disabled' ) def get_queryset(self, request): """Optimize queryset with select_related.""" qs = super().get_queryset(request) return qs.select_related('role', 'banned_by') actions = ['ban_users', 'unban_users', 'make_moderator', 'make_user'] @admin.action(description="Ban selected users") def ban_users(self, request, queryset): """Ban selected users.""" count = 0 for user in queryset: if not user.banned: user.ban(reason="Banned by admin", banned_by=request.user) count += 1 self.message_user( request, f"{count} user(s) have been banned." ) @admin.action(description="Unban selected users") def unban_users(self, request, queryset): """Unban selected users.""" count = 0 for user in queryset: if user.banned: user.unban() count += 1 self.message_user( request, f"{count} user(s) have been unbanned." ) @admin.action(description="Set role to Moderator") def make_moderator(self, request, queryset): """Set users' role to moderator.""" from .services import RoleService count = 0 for user in queryset: RoleService.assign_role(user, 'moderator', request.user) count += 1 self.message_user( request, f"{count} user(s) have been set to Moderator role." ) @admin.action(description="Set role to User") def make_user(self, request, queryset): """Set users' role to user.""" from .services import RoleService count = 0 for user in queryset: RoleService.assign_role(user, 'user', request.user) count += 1 self.message_user( request, f"{count} user(s) have been set to User role." ) @admin.register(UserRole) class UserRoleAdmin(ModelAdmin): """Admin interface for UserRole model.""" list_display = ['user', 'role', 'is_moderator', 'is_admin', 'granted_at', 'granted_by'] list_filter = ['role', 'granted_at'] search_fields = ['user__email', 'user__username'] ordering = ['-granted_at'] readonly_fields = ['granted_at'] def get_queryset(self, request): """Optimize queryset.""" qs = super().get_queryset(request) return qs.select_related('user', 'granted_by') @admin.register(UserProfile) class UserProfileAdmin(ModelAdmin): """Admin interface for UserProfile model.""" list_display = [ 'user', 'total_submissions', 'approved_submissions', 'approval_rate', 'email_notifications', 'profile_public', ] list_filter = [ 'email_notifications', 'profile_public', 'show_email', ] search_fields = ['user__email', 'user__username'] readonly_fields = ['created', 'modified', 'total_submissions', 'approved_submissions'] fieldsets = ( ('User', { 'fields': ('user',) }), ('Statistics', { 'fields': ('total_submissions', 'approved_submissions'), }), ('Notification Preferences', { 'fields': ( 'email_notifications', 'email_on_submission_approved', 'email_on_submission_rejected', ) }), ('Privacy Settings', { 'fields': ('profile_public', 'show_email'), }), ('Timestamps', { 'fields': ('created', 'modified'), 'classes': ('collapse',) }), ) @display(description="Approval Rate") def approval_rate(self, obj): """Display approval rate percentage.""" if obj.total_submissions == 0: return '-' rate = (obj.approved_submissions / obj.total_submissions) * 100 if rate >= 80: color = 'green' elif rate >= 60: color = 'blue' elif rate >= 40: color = 'orange' else: color = 'red' return format_html( '{:.1f}%', color, rate ) def get_queryset(self, request): """Optimize queryset.""" qs = super().get_queryset(request) return qs.select_related('user') @admin.register(UserRideCredit) class UserRideCreditAdmin(ModelAdmin): """Admin interface for UserRideCredit model.""" list_display = [ 'user_link', 'ride_link', 'park_link', 'first_ride_date', 'ride_count', 'created', ] list_filter = [ 'first_ride_date', 'created', ] search_fields = [ 'user__email', 'user__username', 'ride__name', 'notes', ] ordering = ['-first_ride_date', '-created'] readonly_fields = ['created', 'modified'] fieldsets = ( ('Credit Information', { 'fields': ('user', 'ride', 'first_ride_date', 'ride_count') }), ('Notes', { 'fields': ('notes',) }), ('Timestamps', { 'fields': ('created', 'modified'), 'classes': ('collapse',) }), ) @display(description='User', ordering='user__username') def user_link(self, obj): url = reverse('admin:users_user_change', args=[obj.user.pk]) return format_html('{}', url, obj.user.username) @display(description='Ride', ordering='ride__name') def ride_link(self, obj): url = reverse('admin:entities_ride_change', args=[obj.ride.pk]) return format_html('{}', url, obj.ride.name) @display(description='Park') def park_link(self, obj): if obj.ride.park: url = reverse('admin:entities_park_change', args=[obj.ride.park.pk]) return format_html('{}', url, obj.ride.park.name) return '-' def get_queryset(self, request): """Optimize queryset.""" qs = super().get_queryset(request) return qs.select_related('user', 'ride', 'ride__park') class UserTopListItemInline(admin.TabularInline): """Inline for top list items.""" model = UserTopListItem extra = 1 fields = ('position', 'content_type', 'object_id', 'notes') ordering = ['position'] @admin.register(UserTopList) class UserTopListAdmin(ModelAdmin): """Admin interface for UserTopList model.""" list_display = [ 'title', 'user_link', 'list_type', 'item_count_display', 'visibility_badge', 'created', ] list_filter = [ 'list_type', 'is_public', 'created', ] search_fields = [ 'title', 'description', 'user__email', 'user__username', ] ordering = ['-created'] readonly_fields = ['created', 'modified', 'item_count'] fieldsets = ( ('List Information', { 'fields': ('user', 'list_type', 'title', 'description') }), ('Privacy', { 'fields': ('is_public',) }), ('Statistics', { 'fields': ('item_count',) }), ('Timestamps', { 'fields': ('created', 'modified'), 'classes': ('collapse',) }), ) inlines = [UserTopListItemInline] @display(description='User', ordering='user__username') def user_link(self, obj): url = reverse('admin:users_user_change', args=[obj.user.pk]) return format_html('{}', url, obj.user.username) @display(description='Items', ordering='items__count') def item_count_display(self, obj): count = obj.item_count return format_html('{}', count) @display(description='Visibility', ordering='is_public') def visibility_badge(self, obj): if obj.is_public: return format_html( 'PUBLIC' ) else: return format_html( 'PRIVATE' ) def get_queryset(self, request): """Optimize queryset.""" qs = super().get_queryset(request) return qs.select_related('user').prefetch_related('items') @admin.register(UserTopListItem) class UserTopListItemAdmin(ModelAdmin): """Admin interface for UserTopListItem model.""" list_display = [ 'position', 'list_link', 'entity_type', 'entity_link', 'created', ] list_filter = [ 'content_type', 'created', ] search_fields = [ 'top_list__title', 'notes', ] ordering = ['top_list', 'position'] readonly_fields = ['created', 'modified'] fieldsets = ( ('Item Information', { 'fields': ('top_list', 'position', 'content_type', 'object_id') }), ('Notes', { 'fields': ('notes',) }), ('Timestamps', { 'fields': ('created', 'modified'), 'classes': ('collapse',) }), ) @display(description='List', ordering='top_list__title') def list_link(self, obj): url = reverse('admin:users_usertoplist_change', args=[obj.top_list.pk]) return format_html('{}', url, obj.top_list.title) @display(description='Type', ordering='content_type') def entity_type(self, obj): return obj.content_type.model.title() @display(description='Entity') def entity_link(self, obj): if obj.content_object: model_name = obj.content_type.model url = reverse(f'admin:entities_{model_name}_change', args=[obj.object_id]) return format_html('{}', url, str(obj.content_object)) return f"ID: {obj.object_id}" def get_queryset(self, request): """Optimize queryset.""" qs = super().get_queryset(request) return qs.select_related('top_list', 'content_type')