From 719ccd64770f2e4a32d83d546c33f592db280345 Mon Sep 17 00:00:00 2001 From: Fam Zheng Date: Tue, 25 Nov 2025 22:38:57 +0000 Subject: [PATCH] Convert verification_model to ForeignKey with data migration - Convert CodeBatch.verification_model from TextField to ForeignKey(VerificationModel) - Add ForeignKey relationship to CodeBatchResource API - Update QrVerifyView to use verification_model.name instead of string - Create data migration to: - Rename old TextField temporarily - Add new ForeignKey field - Migrate existing model names to VerificationModel entries - Link batches to VerificationModel records - Remove old TextField - GenericManager will now show dropdown/selector UI automatically --- ...onvert_verification_model_to_foreignkey.py | 80 +++++++++++++++++++ api/products/models.py | 5 +- api/products/views.py | 3 +- 3 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 api/products/migrations/0111_convert_verification_model_to_foreignkey.py diff --git a/api/products/migrations/0111_convert_verification_model_to_foreignkey.py b/api/products/migrations/0111_convert_verification_model_to_foreignkey.py new file mode 100644 index 0000000..1dd86c9 --- /dev/null +++ b/api/products/migrations/0111_convert_verification_model_to_foreignkey.py @@ -0,0 +1,80 @@ +# Generated by Django 4.2.23 on 2025-11-25 22:38 + +from django.db import migrations, models +import django.db.models.deletion + + +def migrate_verification_model_data(apps, schema_editor): + """Migrate data from TextField to ForeignKey""" + CodeBatch = apps.get_model('products', 'CodeBatch') + VerificationModel = apps.get_model('products', 'VerificationModel') + + # Query the database directly to get the old TextField values + from django.db import connection + with connection.cursor() as cursor: + # Query the old field (verification_model_old after rename) + cursor.execute("SELECT DISTINCT verification_model_old FROM products_codebatch WHERE verification_model_old IS NOT NULL AND verification_model_old != ''") + rows = cursor.fetchall() + unique_model_names = {row[0] for row in rows if row[0]} + + # Create VerificationModel entries for each unique model name + model_map = {} + for model_name in unique_model_names: + vm, created = VerificationModel.objects.get_or_create(name=model_name) + model_map[model_name] = vm + + # Update all CodeBatch records to use the ForeignKey + with connection.cursor() as cursor: + cursor.execute("SELECT id, verification_model_old FROM products_codebatch WHERE verification_model_old IS NOT NULL AND verification_model_old != ''") + rows = cursor.fetchall() + for batch_id, model_name in rows: + if model_name in model_map: + vm = model_map[model_name] + CodeBatch.objects.filter(pk=batch_id).update(verification_model=vm) + + +def reverse_migrate_verification_model_data(apps, schema_editor): + """Reverse migration: convert ForeignKey back to TextField""" + CodeBatch = apps.get_model('products', 'CodeBatch') + # Extract the name from the ForeignKey and set it in the old field + from django.db import connection + with connection.cursor() as cursor: + cursor.execute(""" + SELECT cb.id, vm.name + FROM products_codebatch cb + LEFT JOIN products_verificationmodel vm ON cb.verification_model_id = vm.id + WHERE cb.verification_model_id IS NOT NULL + """) + rows = cursor.fetchall() + for batch_id, model_name in rows: + if model_name: + CodeBatch.objects.filter(pk=batch_id).update(verification_model_old=model_name) + + +class Migration(migrations.Migration): + + dependencies = [ + ('products', '0110_add_verification_model'), + ] + + operations = [ + # Step 1: Rename the old TextField to a temporary name + migrations.RenameField( + model_name='codebatch', + old_name='verification_model', + new_name='verification_model_old', + ), + # Step 2: Add the new ForeignKey field + migrations.AddField( + model_name='codebatch', + name='verification_model', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='batches', to='products.verificationmodel', verbose_name='验证模型(可选,留空使用默认模型)'), + ), + # Step 3: Migrate the data + migrations.RunPython(migrate_verification_model_data, reverse_migrate_verification_model_data), + # Step 4: Remove the old TextField + migrations.RemoveField( + model_name='codebatch', + name='verification_model_old', + ), + ] diff --git a/api/products/models.py b/api/products/models.py index 6363c8b..9414d43 100644 --- a/api/products/models.py +++ b/api/products/models.py @@ -194,8 +194,9 @@ class CodeBatch(models.Model): verbose_name="小程序入口路径(可选)") mini_prog_entry_env_version = models.CharField(max_length=128, null=True, blank=True, default='release', verbose_name="小程序入口环境版本(可选), 默认release。如为trial则进入体验版") - verification_model = models.TextField(null=True, blank=True, - verbose_name="验证模型名称(可选,留空使用默认模型)") + verification_model = models.ForeignKey('VerificationModel', related_name='batches', null=True, blank=True, + on_delete=models.SET_NULL, + verbose_name="验证模型(可选,留空使用默认模型)") def gen_code(self, n): a = 10 ** (self.num_digits - 1) diff --git a/api/products/views.py b/api/products/views.py index 2929b59..480cc12 100644 --- a/api/products/views.py +++ b/api/products/views.py @@ -326,6 +326,7 @@ class ProductPropertyResource(BaseResource): class CodeBatchResource(BaseResource): tenant = ForeignKey(TenantResource, 'tenant', null=True, blank=True) + verification_model = ForeignKey(VerificationModelResource, 'verification_model', null=True, blank=True) class Meta: queryset = CodeBatch.objects.all().order_by('-pk') resource_name = 'code-batch' @@ -739,7 +740,7 @@ class QrVerifyView(BaseView): t = threading.Thread(target=oss_put, args=(image_name, img_data)) t.run() if sc.batch.feature_comparison_threshold > 0.01: - model_name = sc.batch.verification_model if sc.batch.verification_model else None + model_name = sc.batch.verification_model.name if sc.batch.verification_model else None used_model = self.do_v5_qr_verify(imgs, messages, model_name=model_name) sd.verification_model = used_model if used_model else None else: