"""
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')