feat: improve operations admin

This commit is contained in:
QiuSW
2026-07-03 16:36:06 +08:00
parent 547196f39a
commit 79605449ef
15 changed files with 520 additions and 31 deletions
+134 -4
View File
@@ -1,9 +1,42 @@
from django.contrib import admin
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 + (
@@ -14,6 +47,7 @@ class UserAdmin(DjangoUserAdmin):
"username",
"email",
"status",
"wallet_points_balance",
"is_staff",
"is_active",
"created_at",
@@ -21,13 +55,35 @@ class UserAdmin(DjangoUserAdmin):
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")
search_fields = ("user__username", "user__email")
readonly_fields = ("user", "points_balance", "created_at", "updated_at")
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
@@ -35,12 +91,86 @@ class UserWalletAdmin(admin.ModelAdmin):
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",