mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 03:51:09 -05:00
370 lines
11 KiB
Python
370 lines
11 KiB
Python
from typing import Any
|
|
from django.contrib import admin
|
|
from django.contrib.auth.admin import UserAdmin as DjangoUserAdmin
|
|
from django.utils.html import format_html
|
|
from django.contrib.auth.models import Group
|
|
from django.http import HttpRequest
|
|
from django.db.models import QuerySet
|
|
from .models import (
|
|
User,
|
|
UserProfile,
|
|
EmailVerification,
|
|
PasswordReset,
|
|
TopList,
|
|
TopListItem,
|
|
)
|
|
|
|
|
|
class UserProfileInline(admin.StackedInline[UserProfile, admin.options.AdminSite]):
|
|
model = UserProfile
|
|
can_delete = False
|
|
verbose_name_plural = "Profile"
|
|
fieldsets = (
|
|
(
|
|
"Personal Info",
|
|
{"fields": ("display_name", "avatar", "pronouns", "bio")},
|
|
),
|
|
(
|
|
"Social Media",
|
|
{"fields": ("twitter", "instagram", "youtube", "discord")},
|
|
),
|
|
(
|
|
"Ride Credits",
|
|
{
|
|
"fields": (
|
|
"coaster_credits",
|
|
"dark_ride_credits",
|
|
"flat_ride_credits",
|
|
"water_ride_credits",
|
|
)
|
|
},
|
|
),
|
|
)
|
|
|
|
|
|
class TopListItemInline(admin.TabularInline[TopListItem]):
|
|
model = TopListItem
|
|
extra = 1
|
|
fields = ("content_type", "object_id", "rank", "notes")
|
|
ordering = ("rank",)
|
|
|
|
|
|
@admin.register(User)
|
|
class CustomUserAdmin(DjangoUserAdmin[User]):
|
|
list_display = (
|
|
"username",
|
|
"email",
|
|
"get_avatar",
|
|
"get_status",
|
|
"role",
|
|
"date_joined",
|
|
"last_login",
|
|
"get_credits",
|
|
)
|
|
list_filter = (
|
|
"is_active",
|
|
"is_staff",
|
|
"role",
|
|
"is_banned",
|
|
"groups",
|
|
"date_joined",
|
|
)
|
|
search_fields = ("username", "email")
|
|
ordering = ("-date_joined",)
|
|
actions = [
|
|
"activate_users",
|
|
"deactivate_users",
|
|
"ban_users",
|
|
"unban_users",
|
|
]
|
|
inlines: list[type[admin.StackedInline[UserProfile]]] = [UserProfileInline]
|
|
|
|
fieldsets = (
|
|
(None, {"fields": ("username", "password")}),
|
|
("Personal info", {"fields": ("email", "pending_email")}),
|
|
(
|
|
"Roles and Permissions",
|
|
{
|
|
"fields": ("role", "groups", "user_permissions"),
|
|
"description": (
|
|
"Role determines group membership. Groups determine permissions."
|
|
),
|
|
},
|
|
),
|
|
(
|
|
"Status",
|
|
{
|
|
"fields": ("is_active", "is_staff", "is_superuser"),
|
|
"description": "These are automatically managed based on role.",
|
|
},
|
|
),
|
|
(
|
|
"Ban Status",
|
|
{
|
|
"fields": ("is_banned", "ban_reason", "ban_date"),
|
|
},
|
|
),
|
|
(
|
|
"Preferences",
|
|
{
|
|
"fields": ("theme_preference",),
|
|
},
|
|
),
|
|
("Important dates", {"fields": ("last_login", "date_joined")}),
|
|
)
|
|
add_fieldsets = (
|
|
(
|
|
None,
|
|
{
|
|
"classes": ("wide",),
|
|
"fields": (
|
|
"username",
|
|
"email",
|
|
"password1",
|
|
"password2",
|
|
"role",
|
|
),
|
|
},
|
|
),
|
|
)
|
|
|
|
@admin.display(description="Avatar")
|
|
def get_avatar(self, obj: User) -> str:
|
|
profile = getattr(obj, "profile", None)
|
|
if profile and getattr(profile, "avatar", None):
|
|
return format_html(
|
|
'<img src="{0}" width="30" height="30" style="border-radius:50%;" />',
|
|
getattr(profile.avatar, "url", ""), # type: ignore
|
|
)
|
|
return format_html(
|
|
'<div style="width:30px; height:30px; border-radius:50%; '
|
|
"background-color:#007bff; color:white; display:flex; "
|
|
'align-items:center; justify-content:center;">{0}</div>',
|
|
getattr(obj, "username", "?")[0].upper(), # type: ignore
|
|
)
|
|
|
|
@admin.display(description="Status")
|
|
def get_status(self, obj: User) -> str:
|
|
if getattr(obj, "is_banned", False):
|
|
return format_html('<span style="color: red;">{}</span>', "Banned")
|
|
if not getattr(obj, "is_active", True):
|
|
return format_html('<span style="color: orange;">{}</span>', "Inactive")
|
|
if getattr(obj, "is_superuser", False):
|
|
return format_html('<span style="color: purple;">{}</span>', "Superuser")
|
|
if getattr(obj, "is_staff", False):
|
|
return format_html('<span style="color: blue;">{}</span>', "Staff")
|
|
return format_html('<span style="color: green;">{}</span>', "Active")
|
|
|
|
@admin.display(description="Ride Credits")
|
|
def get_credits(self, obj: User) -> str:
|
|
try:
|
|
profile = getattr(obj, "profile", None)
|
|
if not profile:
|
|
return "-"
|
|
return format_html(
|
|
"RC: {0}<br>DR: {1}<br>FR: {2}<br>WR: {3}",
|
|
getattr(profile, "coaster_credits", 0),
|
|
getattr(profile, "dark_ride_credits", 0),
|
|
getattr(profile, "flat_ride_credits", 0),
|
|
getattr(profile, "water_ride_credits", 0),
|
|
)
|
|
except UserProfile.DoesNotExist:
|
|
return "-"
|
|
|
|
@admin.action(description="Activate selected users")
|
|
def activate_users(self, request: HttpRequest, queryset: QuerySet[User]) -> None:
|
|
queryset.update(is_active=True)
|
|
|
|
@admin.action(description="Deactivate selected users")
|
|
def deactivate_users(self, request: HttpRequest, queryset: QuerySet[User]) -> None:
|
|
queryset.update(is_active=False)
|
|
|
|
@admin.action(description="Ban selected users")
|
|
def ban_users(self, request: HttpRequest, queryset: QuerySet[User]) -> None:
|
|
from django.utils import timezone
|
|
queryset.update(is_banned=True, ban_date=timezone.now())
|
|
|
|
@admin.action(description="Unban selected users")
|
|
def unban_users(self, request: HttpRequest, queryset: QuerySet[User]) -> None:
|
|
queryset.update(is_banned=False, ban_date=None, ban_reason="")
|
|
|
|
def save_model(
|
|
self,
|
|
request: HttpRequest,
|
|
obj: User,
|
|
form: Any,
|
|
change: bool
|
|
) -> None:
|
|
creating = not obj.pk
|
|
super().save_model(request, obj, form, change)
|
|
if creating and getattr(obj, "role", "USER") != "USER":
|
|
group = Group.objects.filter(name=getattr(obj, "role", None)).first()
|
|
if group:
|
|
obj.groups.add(group) # type: ignore[attr-defined]
|
|
|
|
|
|
@admin.register(UserProfile)
|
|
class UserProfileAdmin(admin.ModelAdmin[UserProfile]):
|
|
list_display = (
|
|
"user",
|
|
"display_name",
|
|
"coaster_credits",
|
|
"dark_ride_credits",
|
|
"flat_ride_credits",
|
|
"water_ride_credits",
|
|
)
|
|
list_filter = (
|
|
"coaster_credits",
|
|
"dark_ride_credits",
|
|
"flat_ride_credits",
|
|
"water_ride_credits",
|
|
)
|
|
search_fields = ("user__username", "user__email", "display_name", "bio")
|
|
|
|
fieldsets = (
|
|
(
|
|
"User Information",
|
|
{"fields": ("user", "display_name", "avatar", "pronouns", "bio")},
|
|
),
|
|
(
|
|
"Social Media",
|
|
{"fields": ("twitter", "instagram", "youtube", "discord")},
|
|
),
|
|
(
|
|
"Ride Credits",
|
|
{
|
|
"fields": (
|
|
"coaster_credits",
|
|
"dark_ride_credits",
|
|
"flat_ride_credits",
|
|
"water_ride_credits",
|
|
)
|
|
},
|
|
),
|
|
)
|
|
|
|
|
|
@admin.register(EmailVerification)
|
|
class EmailVerificationAdmin(admin.ModelAdmin[EmailVerification]):
|
|
list_display = ("user", "created_at", "last_sent", "is_expired")
|
|
list_filter = ("created_at", "last_sent")
|
|
search_fields = ("user__username", "user__email", "token")
|
|
readonly_fields = ("created_at", "last_sent")
|
|
|
|
fieldsets = (
|
|
("Verification Details", {"fields": ("user", "token")}),
|
|
("Timing", {"fields": ("created_at", "last_sent")}),
|
|
)
|
|
|
|
@admin.display(description="Status")
|
|
def is_expired(self, obj: EmailVerification) -> str:
|
|
from django.utils import timezone
|
|
from datetime import timedelta
|
|
|
|
if timezone.now() - getattr(obj, "last_sent", timezone.now()) > timedelta(days=1):
|
|
return format_html('<span style="color: red;">{}</span>', "Expired")
|
|
return format_html('<span style="color: green;">{}</span>', "Valid")
|
|
|
|
|
|
@admin.register(TopList)
|
|
class TopListAdmin(admin.ModelAdmin[TopList]):
|
|
list_display = ("title", "user", "category", "created_at", "updated_at")
|
|
list_filter = ("category", "created_at", "updated_at")
|
|
search_fields = ("title", "user__username", "description")
|
|
inlines: list[type[admin.TabularInline[TopListItem]]] = [TopListItemInline]
|
|
|
|
fieldsets = (
|
|
(
|
|
"Basic Information",
|
|
{"fields": ("user", "title", "category", "description")},
|
|
),
|
|
(
|
|
"Timestamps",
|
|
{"fields": ("created_at", "updated_at"), "classes": ("collapse",)},
|
|
),
|
|
)
|
|
readonly_fields = ("created_at", "updated_at")
|
|
|
|
|
|
@admin.register(TopListItem)
|
|
class TopListItemAdmin(admin.ModelAdmin[TopListItem]):
|
|
list_display = ("top_list", "content_type", "object_id", "rank")
|
|
list_filter = ("top_list__category", "rank")
|
|
search_fields = ("top_list__title", "notes")
|
|
ordering = ("top_list", "rank")
|
|
|
|
fieldsets = (
|
|
("List Information", {"fields": ("top_list", "rank")}),
|
|
("Item Details", {"fields": ("content_type", "object_id", "notes")}),
|
|
)
|
|
|
|
|
|
@admin.register(PasswordReset)
|
|
class PasswordResetAdmin(admin.ModelAdmin[PasswordReset]):
|
|
"""Admin interface for password reset tokens"""
|
|
|
|
list_display = (
|
|
"user",
|
|
"created_at",
|
|
"expires_at",
|
|
"is_expired",
|
|
"used",
|
|
)
|
|
list_filter = (
|
|
"used",
|
|
"created_at",
|
|
"expires_at",
|
|
)
|
|
search_fields = (
|
|
"user__username",
|
|
"user__email",
|
|
"token",
|
|
)
|
|
readonly_fields = (
|
|
"token",
|
|
"created_at",
|
|
"expires_at",
|
|
)
|
|
date_hierarchy = "created_at"
|
|
ordering = ("-created_at",)
|
|
|
|
fieldsets = (
|
|
(
|
|
"Reset Details",
|
|
{
|
|
"fields": (
|
|
"user",
|
|
"token",
|
|
"used",
|
|
)
|
|
},
|
|
),
|
|
(
|
|
"Timing",
|
|
{
|
|
"fields": (
|
|
"created_at",
|
|
"expires_at",
|
|
)
|
|
},
|
|
),
|
|
)
|
|
|
|
@admin.display(description="Status", boolean=True)
|
|
def is_expired(self, obj: PasswordReset) -> str:
|
|
from django.utils import timezone
|
|
|
|
if getattr(obj, "used", False):
|
|
return format_html('<span style="color: blue;">{}</span>', "Used")
|
|
elif timezone.now() > getattr(obj, "expires_at", timezone.now()):
|
|
return format_html('<span style="color: red;">{}</span>', "Expired")
|
|
return format_html('<span style="color: green;">{}</span>', "Valid")
|
|
|
|
def has_add_permission(self, request: HttpRequest) -> bool:
|
|
"""Disable manual creation of password reset tokens"""
|
|
return False
|
|
|
|
def has_change_permission(self, request: HttpRequest, obj: Any = None) -> bool:
|
|
"""Allow viewing but restrict editing of password reset tokens"""
|
|
return getattr(request.user, "is_superuser", False)
|