Files
cmhub/apps/users/admin.py
T
2026-07-03 16:36:06 +08:00

205 lines
6.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from django import forms
from django.contrib import admin, messages
from django.contrib.admin.utils import quote, unquote
from django.contrib.auth.admin import UserAdmin as DjangoUserAdmin
from django.core.exceptions import PermissionDenied
from django.http import Http404, HttpResponseRedirect
from django.template.response import TemplateResponse
from django.urls import path, reverse
from django.utils.html import format_html
from apps.billing.services import WalletAdjustmentError, adjust_wallet_points
from .models import ApiKey, User, UserWallet
class WalletAdjustmentForm(forms.Form):
points_delta = forms.IntegerField(
label="点数变动",
help_text="正数加点,负数扣点;不可为 0。",
)
reason = forms.CharField(
label="调整原因",
widget=forms.Textarea(attrs={"rows": 4}),
help_text="必填,会写入点数流水用于审计。",
)
def clean_points_delta(self):
points_delta = self.cleaned_data["points_delta"]
if points_delta == 0:
raise forms.ValidationError("点数变动不可为 0。")
return points_delta
def clean_reason(self):
reason = self.cleaned_data["reason"].strip()
if not reason:
raise forms.ValidationError("必须填写调整原因。")
return reason
@admin.register(User)
class UserAdmin(DjangoUserAdmin):
fieldsets = DjangoUserAdmin.fieldsets + (
("Cmhub", {"fields": ("payment_user_id", "status", "created_at")}),
)
readonly_fields = ("created_at",)
list_display = (
"username",
"email",
"status",
"wallet_points_balance",
"is_staff",
"is_active",
"created_at",
)
list_filter = DjangoUserAdmin.list_filter + ("status",)
search_fields = ("username", "email", "payment_user_id")
def get_queryset(self, request):
return super().get_queryset(request).select_related("wallet")
@admin.display(description="points")
def wallet_points_balance(self, obj):
wallet = getattr(obj, "wallet", None)
return wallet.points_balance if wallet else 0
@admin.register(UserWallet)
class UserWalletAdmin(admin.ModelAdmin):
list_display = ("user", "points_balance", "updated_at", "adjust_points_link")
search_fields = ("user__username", "user__email", "user__payment_user_id")
readonly_fields = (
"user",
"points_balance",
"created_at",
"updated_at",
"adjust_points_link",
)
fields = (
"user",
"points_balance",
"created_at",
"updated_at",
"adjust_points_link",
)
ordering = ("user__username",)
list_select_related = ("user",)
def has_add_permission(self, request):
return False
def has_delete_permission(self, request, obj=None):
return False
def get_urls(self):
urls = super().get_urls()
custom_urls = [
path(
"<path:object_id>/adjust-points/",
self.admin_site.admin_view(self.adjust_points_view),
name="users_userwallet_adjust_points",
),
]
return custom_urls + urls
@admin.display(description="手工调点")
def adjust_points_link(self, obj):
if obj is None:
return ""
url = reverse(
"admin:users_userwallet_adjust_points",
args=(quote(obj.pk),),
current_app=self.admin_site.name,
)
return format_html('<a class="button" href="{}">调点</a>', url)
def adjust_points_view(self, request, object_id):
wallet = self.get_object(request, unquote(object_id))
if wallet is None:
raise Http404("UserWallet does not exist.")
if not self.has_change_permission(request, wallet):
raise PermissionDenied
if request.method == "POST":
form = WalletAdjustmentForm(request.POST)
if form.is_valid():
try:
result = adjust_wallet_points(
user=wallet.user,
points_delta=form.cleaned_data["points_delta"],
reason=form.cleaned_data["reason"],
actor=request.user,
)
except WalletAdjustmentError as exc:
form.add_error(None, str(exc))
else:
messages.success(
request,
(
f"已调整 {wallet.user} 点数 {result.points_delta:+d},"
f"当前余额 {result.balance_after}。"
),
)
change_url = reverse(
"admin:users_userwallet_change",
args=(quote(wallet.pk),),
current_app=self.admin_site.name,
)
return HttpResponseRedirect(change_url)
else:
form = WalletAdjustmentForm()
context = {
**self.admin_site.each_context(request),
"title": "手工调整点数",
"opts": self.model._meta,
"original": wallet,
"form": form,
"media": self.media + form.media,
"has_change_permission": self.has_change_permission(request, wallet),
}
return TemplateResponse(
request,
"admin/users/userwallet/adjust_points.html",
context,
)
@admin.register(ApiKey)
class ApiKeyAdmin(admin.ModelAdmin):
list_display = ("key_prefix", "user", "name", "status", "last_used_at", "created_at")
list_filter = ("status", "created_at")
search_fields = ("key_prefix", "user__username", "user__email", "name")
list_select_related = ("user",)
readonly_fields = (
"user",
"key_prefix",
"key_hash_masked",
"last_used_at",
"created_at",
"updated_at",
)
fields = (
"user",
"name",
"status",
"key_prefix",
"key_hash_masked",
"last_used_at",
"created_at",
"updated_at",
)
ordering = ("-created_at", "-id")
@admin.display(description="key hash")
def key_hash_masked(self, obj):
if not obj.key_hash:
return ""
return f"{obj.key_hash[:8]}...{obj.key_hash[-6:]}"
def has_add_permission(self, request):
return False
def has_delete_permission(self, request, obj=None):
return False