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
+71
View File
@@ -8,16 +8,20 @@ from django.urls import path, reverse
from .models import (
ClientDevice,
DeviceCredential,
DeviceBindingAudit,
DeviceSession,
LicenseEvent,
LicenseSeat,
LegacyMigrationGrant,
MigrationRequest,
SoftwareEntitlement,
SoftwarePlan,
)
from .services import (
LicensingError,
grant_software_entitlement,
create_legacy_migration_grant,
renew_software_entitlement,
revoke_software_entitlement,
)
@@ -351,3 +355,70 @@ class LicenseEventAdmin(ReadOnlyLicenseAdmin):
"reason",
)
list_select_related = ("entitlement", "seat", "device", "actor")
class LegacyMigrationGrantForm(EntitlementGrantForm):
reason = forms.CharField(label="迁移原因", widget=forms.Textarea(attrs={"rows": 4}))
@admin.register(LegacyMigrationGrant)
class LegacyMigrationGrantAdmin(ReadOnlyLicenseAdmin):
list_display = ("user", "product_code", "entitlement", "status", "actor", "created_at")
list_filter = ("product_code", "status", "created_at")
search_fields = ("user__username", "user__email", "reason")
list_select_related = ("user", "entitlement", "actor")
def get_urls(self):
urls = super().get_urls()
return [
path(
"grant/",
self.admin_site.admin_view(self.grant_view),
name="licensing_legacymigrationgrant_grant",
),
] + urls
def grant_view(self, request):
form = LegacyMigrationGrantForm(request.POST or None)
if request.method == "POST" and form.is_valid():
try:
grant = create_legacy_migration_grant(
user=form.cleaned_data["user"],
plan=form.cleaned_data["plan"],
reason=form.cleaned_data["reason"],
actor=request.user,
)
except LicensingError as exc:
form.add_error(None, exc.message)
else:
self.message_user(request, "存量迁移资格已授予。", messages.SUCCESS)
return HttpResponseRedirect(
reverse("admin:licensing_legacymigrationgrant_change", args=(grant.pk,))
)
context = {
**self.admin_site.each_context(request),
"title": "授予存量迁移资格",
"opts": self.model._meta,
"form": form,
}
return TemplateResponse(
request,
"admin/licensing/entitlement_operation.html",
context,
)
@admin.register(MigrationRequest)
class MigrationRequestAdmin(ReadOnlyLicenseAdmin):
list_display = ("request_id", "user", "device", "migration_grant", "status", "expires_at", "confirmed_at")
list_filter = ("status", "device__product_code", "expires_at")
search_fields = ("=request_id", "user__username", "user__email")
list_select_related = ("user", "device", "migration_grant")
@admin.register(DeviceCredential)
class DeviceCredentialAdmin(ReadOnlyLicenseAdmin):
list_display = ("token_prefix", "user", "product_code", "device", "entitlement", "expires_at", "revoked_at")
list_filter = ("product_code", "revoked_at", "expires_at")
search_fields = ("token_prefix", "user__username", "user__email")
list_select_related = ("user", "device", "entitlement", "seat")