Files
cmhub/apps/portal/views.py
T

143 lines
5.0 KiB
Python

from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import Sum
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse_lazy
from django.views import View
from django.views.generic import FormView, TemplateView
from apps.billing.models import PointsLedger, RechargeOrder
from apps.billing.services import get_balance_snapshot
from apps.users.models import ApiKey
from .forms import ApiKeyCreateForm
NEW_API_KEY_SESSION_KEY = "portal_new_api_key"
RECORDS_PAGE_SIZE = 50
def _sum_or_zero(queryset, field_name: str):
return queryset.aggregate(total=Sum(field_name))["total"] or 0
def get_portal_account_summary(user) -> dict:
paid_orders = RechargeOrder.objects.filter(
user=user,
status=RechargeOrder.Status.PAID,
)
consume_entries = PointsLedger.objects.filter(
user=user,
change_type=PointsLedger.ChangeType.CONSUME,
)
refund_entries = PointsLedger.objects.filter(
user=user,
change_type=PointsLedger.ChangeType.REFUND,
)
recharge_entries = PointsLedger.objects.filter(
user=user,
change_type=PointsLedger.ChangeType.RECHARGE,
)
consumed_points = abs(int(_sum_or_zero(consume_entries, "points_delta")))
refunded_points = int(_sum_or_zero(refund_entries, "points_delta"))
return {
"recharge_total_amount": _sum_or_zero(paid_orders, "amount_money"),
"recharge_points_total": int(_sum_or_zero(recharge_entries, "points_delta")),
"consumed_points_total": consumed_points,
"refunded_points_total": refunded_points,
"net_used_points": consumed_points - refunded_points,
"paid_recharge_count": paid_orders.count(),
}
def get_recharge_orders_for_user(user):
return RechargeOrder.objects.filter(user=user).order_by("-created_at", "-id")
def get_usage_ledger_entries_for_user(user):
return (
PointsLedger.objects.filter(
user=user,
change_type__in=(
PointsLedger.ChangeType.CONSUME,
PointsLedger.ChangeType.REFUND,
),
)
.select_related("ref_call", "ref_call__api_key")
.order_by("-created_at", "-id")
)
class DashboardView(LoginRequiredMixin, TemplateView):
template_name = "portal/dashboard.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["balance"] = get_balance_snapshot(self.request.user)
context.update(get_portal_account_summary(self.request.user))
context["recent_recharge_orders"] = get_recharge_orders_for_user(
self.request.user
)[:5]
context["recent_usage_entries"] = get_usage_ledger_entries_for_user(
self.request.user
)[:5]
return context
class ApiKeyListCreateView(LoginRequiredMixin, FormView):
template_name = "portal/apikeys.html"
form_class = ApiKeyCreateForm
success_url = reverse_lazy("portal-apikeys")
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["api_keys"] = ApiKey.objects.filter(user=self.request.user).order_by(
"-created_at",
"-id",
)
context["new_api_key"] = self.request.session.pop(NEW_API_KEY_SESSION_KEY, None)
return context
def form_valid(self, form):
name = form.cleaned_data["name"].strip()
api_key, raw_key = ApiKey.create_for_user(self.request.user, name=name)
self.request.session[NEW_API_KEY_SESSION_KEY] = {
"name": api_key.name,
"key_prefix": api_key.key_prefix,
"raw_key": raw_key,
}
messages.success(self.request, "API Key 已生成")
return super().form_valid(form)
class ApiKeyDeleteView(LoginRequiredMixin, View):
def post(self, request, pk):
api_key = get_object_or_404(ApiKey, pk=pk, user=request.user)
if api_key.status != ApiKey.Status.REVOKED:
api_key.status = ApiKey.Status.REVOKED
api_key.save(update_fields=("status", "updated_at"))
messages.success(request, "API Key 已删除")
return redirect("portal-apikeys")
class RechargeRecordListView(LoginRequiredMixin, TemplateView):
template_name = "portal/recharge_records.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
orders = get_recharge_orders_for_user(self.request.user)
context.update(get_portal_account_summary(self.request.user))
context["recharge_orders"] = orders[:RECORDS_PAGE_SIZE]
return context
class UsageRecordListView(LoginRequiredMixin, TemplateView):
template_name = "portal/usage_records.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
entries = get_usage_ledger_entries_for_user(self.request.user)
context.update(get_portal_account_summary(self.request.user))
context["usage_entries"] = entries[:RECORDS_PAGE_SIZE]
return context