mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 08:51:13 -05:00
- Created a base email template (base.html) for consistent styling across all emails. - Added moderation approval email template (moderation_approved.html) to notify users of approved submissions. - Added moderation rejection email template (moderation_rejected.html) to inform users of required changes for their submissions. - Created password reset email template (password_reset.html) for users requesting to reset their passwords. - Developed a welcome email template (welcome.html) to greet new users and provide account details and tips for using ThrillWiki.
373 lines
11 KiB
Python
373 lines
11 KiB
Python
"""
|
|
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
|
|
|
|
|
|
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(
|
|
'<span style="background-color: {}; color: white; padding: 3px 8px; border-radius: 3px; font-size: 11px;">{}</span>',
|
|
colors.get(role, 'gray'),
|
|
role.upper()
|
|
)
|
|
except UserRole.DoesNotExist:
|
|
return format_html('<span style="color: gray;">No Role</span>')
|
|
|
|
@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(
|
|
'<span style="color: {}; font-weight: bold;">{}</span>',
|
|
color,
|
|
score
|
|
)
|
|
|
|
@display(description="Status", label=True)
|
|
def status_badge(self, obj):
|
|
"""Display user status."""
|
|
if obj.banned:
|
|
return format_html(
|
|
'<span style="background-color: red; color: white; padding: 3px 8px; border-radius: 3px; font-size: 11px;">BANNED</span>'
|
|
)
|
|
elif not obj.is_active:
|
|
return format_html(
|
|
'<span style="background-color: orange; color: white; padding: 3px 8px; border-radius: 3px; font-size: 11px;">INACTIVE</span>'
|
|
)
|
|
else:
|
|
return format_html(
|
|
'<span style="background-color: green; color: white; padding: 3px 8px; border-radius: 3px; font-size: 11px;">ACTIVE</span>'
|
|
)
|
|
|
|
@display(description="MFA", label=True)
|
|
def mfa_badge(self, obj):
|
|
"""Display MFA status."""
|
|
if obj.mfa_enabled:
|
|
return format_html(
|
|
'<span style="color: green;">✓ Enabled</span>'
|
|
)
|
|
else:
|
|
return format_html(
|
|
'<span style="color: gray;">✗ Disabled</span>'
|
|
)
|
|
|
|
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(
|
|
'<span style="color: {}; font-weight: bold;">{:.1f}%</span>',
|
|
color,
|
|
rate
|
|
)
|
|
|
|
def get_queryset(self, request):
|
|
"""Optimize queryset."""
|
|
qs = super().get_queryset(request)
|
|
return qs.select_related('user')
|