feat: add legacy device migration flow

This commit is contained in:
QiuSW
2026-07-21 10:06:51 +08:00
parent 7b80ae3a7a
commit 1af47d043e
20 changed files with 1030 additions and 6 deletions
+70
View File
@@ -1,5 +1,6 @@
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import PermissionDenied
from django.core.paginator import Paginator
from django.db.models import Sum
from django.shortcuts import get_object_or_404, redirect
@@ -17,6 +18,12 @@ from apps.billing.services import (
get_balance_snapshot,
)
from apps.users.models import ApiKey
from apps.licensing.models import DeviceCredential, MigrationRequest
from apps.licensing.services import (
LicensingError,
confirm_migration_request,
revoke_device_credential,
)
from .forms import ApiKeyCreateForm, RechargeCreateForm
from .models import DownloadRelease, ImportTemplate
@@ -243,6 +250,69 @@ class RechargePageView(LoginRequiredMixin, FormView):
return redirect(f"{recharge_url}?order_no={order.order_no}")
class MigrationConfirmView(LoginRequiredMixin, TemplateView):
template_name = "portal/migration_confirm.html"
def get_migration_request(self):
migration_request = get_object_or_404(
MigrationRequest.objects.select_related("device", "migration_grant__entitlement"),
request_id=self.kwargs["request_id"],
)
if migration_request.user_id != self.request.user.id:
raise PermissionDenied("迁移请求不属于当前账号")
return migration_request
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["migration_request"] = self.get_migration_request()
return context
def post(self, request, *args, **kwargs):
migration_request = self.get_migration_request()
try:
_request, _credential, created = confirm_migration_request(
request_id=migration_request.request_id,
user=request.user,
)
except LicensingError as exc:
messages.error(request, exc.message)
else:
messages.success(
request,
"设备迁移已确认。" if created else "该设备迁移已确认,无需重复操作。",
)
return redirect("portal-migration-confirm", request_id=migration_request.request_id)
class DeviceCredentialListView(LoginRequiredMixin, TemplateView):
template_name = "portal/device_credentials.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["credentials"] = (
DeviceCredential.objects.filter(user=self.request.user)
.select_related("device", "entitlement", "seat")
.order_by("-created_at", "-id")
)
return context
class DeviceCredentialRevokeView(LoginRequiredMixin, View):
def post(self, request, pk):
credential = get_object_or_404(DeviceCredential, pk=pk, user=request.user)
try:
revoke_device_credential(
credential=credential,
reason="用户自助解绑设备",
actor=request.user,
)
except LicensingError as exc:
messages.error(request, exc.message)
else:
messages.success(request, "设备已解绑,原设备凭证已吊销。")
return redirect("portal-device-credentials")
class RechargeRecordListView(LoginRequiredMixin, TemplateView):
template_name = "portal/recharge_records.html"