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( "/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('调点', 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