From d331015329c6a30743c26b3f680a94807276e594 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Wed, 3 Dec 2025 12:04:38 +0000 Subject: [PATCH] More migration updates: - Create new "models" (without moving the existing tables) - Data migration for PartCataegoryParameterTemplate model - Remove PartParameterTemplate and PartParameter models --- .../migrations/0040_auto_20251203_1151.py | 256 ++++++++++++++++++ .../migrations/0144_auto_20251203_1045.py | 88 +++++- ...rtparametertemplate_model_type_and_more.py | 33 +++ 3 files changed, 376 insertions(+), 1 deletion(-) create mode 100644 src/backend/InvenTree/common/migrations/0040_auto_20251203_1151.py create mode 100644 src/backend/InvenTree/part/migrations/0146_remove_partparametertemplate_model_type_and_more.py diff --git a/src/backend/InvenTree/common/migrations/0040_auto_20251203_1151.py b/src/backend/InvenTree/common/migrations/0040_auto_20251203_1151.py new file mode 100644 index 0000000000..cf66556ed1 --- /dev/null +++ b/src/backend/InvenTree/common/migrations/0040_auto_20251203_1151.py @@ -0,0 +1,256 @@ +# Generated by Django 5.2.8 on 2025-12-03 11:51 + +import InvenTree.models +import InvenTree.validators +from django.conf import settings +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + """Create new ParameterTemplate and Parameter models. + + Ref: https://github.com/inventree/InvenTree/pull/10699 + + These models have been migrated from the following (existing) models: + - part.PartParameterTemplate -> common.ParameterTemplate + - part.PartParameter -> common.Parameter + + To preserve the existing data, we will use the existing database tables. + + For this to work, the existing models (part.PartParameterTemplate and part.PartParameter) + must have already had schema and migrations applied: + + - part/migrations/0144_auto_20251203_1045.py + - part/migrations/0145_auto_20251203_1120.py + """ + + dependencies = [ + ("common", "0039_emailthread_emailmessage"), + ("part", "0145_auto_20251203_1120"), + ] + + operations = [ + migrations.CreateModel( + name="ParameterTemplate", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "metadata", + models.JSONField( + blank=True, + help_text="JSON metadata field, for use by external plugins", + null=True, + verbose_name="Plugin Metadata", + ), + ), + ( + "model_type", + models.ForeignKey( + blank=True, null=True, + help_text="Target model type for this parameter template", + on_delete=django.db.models.deletion.SET_NULL, + to="contenttypes.contenttype", + verbose_name="Model type", + ), + ), + ( + "name", + models.CharField( + help_text="Parameter Name", + max_length=100, + unique=True, + verbose_name="Name", + ), + ), + ( + "units", + models.CharField( + blank=True, + help_text="Physical units for this parameter", + max_length=25, + validators=[InvenTree.validators.validate_physical_units], + verbose_name="Units", + ), + ), + ( + "description", + models.CharField( + blank=True, + help_text="Parameter description", + max_length=250, + verbose_name="Description", + ), + ), + ( + "checkbox", + models.BooleanField( + default=False, + help_text="Is this parameter a checkbox?", + verbose_name="Checkbox", + ), + ), + ( + "choices", + models.CharField( + blank=True, + help_text="Valid choices for this parameter (comma-separated)", + max_length=5000, + verbose_name="Choices", + ), + ), + ( + "selectionlist", + models.ForeignKey( + blank=True, + help_text="Selection list for this parameter", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="templates", + to="common.selectionlist", + verbose_name="Selection List", + ), + ), + ( + "enabled", + models.BooleanField( + default=True, + help_text="Is this parameter template enabled?", + verbose_name="Enabled", + ), + ) + ], + options={ + "verbose_name": "Parameter Template", + "verbose_name_plural": "Parameter Templates", + "db_table": "part_partparametertemplate", + "managed": False, + }, + bases=(InvenTree.models.PluginValidationMixin, models.Model), + ), + migrations.CreateModel( + name="Parameter", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "metadata", + models.JSONField( + blank=True, + help_text="JSON metadata field, for use by external plugins", + null=True, + verbose_name="Plugin Metadata", + ), + ), + ( + "updated", + models.DateTimeField( + blank=True, + default=None, + help_text="Timestamp of last update", + null=True, + verbose_name="Updated", + ), + ), + ( + "model_type", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="contenttypes.contenttype", + ), + ), + ( + "model_id", + models.PositiveIntegerField( + help_text="ID of the target model for this parameter", + verbose_name="Model ID", + ), + ), + ( + "data", + models.CharField( + help_text="Parameter Value", + max_length=500, + validators=[django.core.validators.MinLengthValidator(1)], + verbose_name="Data", + ), + ), + ( + "data_numeric", + models.FloatField(blank=True, default=None, null=True), + ), + ( + "note", + models.CharField( + blank=True, + help_text="Optional note field", + max_length=500, + verbose_name="Note", + ), + ), + ( + "template", + models.ForeignKey( + help_text="Parameter template", + on_delete=django.db.models.deletion.CASCADE, + related_name="parameters", + to="common.parametertemplate", + verbose_name="Template", + ), + ), + ( + "updated_by", + models.ForeignKey( + blank=True, + help_text="User who last updated this object", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="%(class)s_updated", + to=settings.AUTH_USER_MODEL, + verbose_name="Update By", + ), + ), + ], + options={ + "verbose_name": "Parameter", + "verbose_name_plural": "Parameters", + "unique_together": {("model_type", "model_id", "template")}, + "db_table": "part_partparameter", + "managed": False, + }, + bases=(InvenTree.models.PluginValidationMixin, models.Model), + ), + migrations.AddIndex( + model_name="parameter", + index=models.Index( + fields=["model_type", "model_id"], name="common_para_model_t_244405_idx" + ), + ), + migrations.AlterModelOptions( + name="parameter", + options={"verbose_name": "Parameter", "verbose_name_plural": "Parameters"}, + ), + migrations.AlterModelOptions( + name="parametertemplate", + options={ + "verbose_name": "Parameter Template", + "verbose_name_plural": "Parameter Templates", + }, + ), + ] \ No newline at end of file diff --git a/src/backend/InvenTree/part/migrations/0144_auto_20251203_1045.py b/src/backend/InvenTree/part/migrations/0144_auto_20251203_1045.py index a178df8f88..574d32563b 100644 --- a/src/backend/InvenTree/part/migrations/0144_auto_20251203_1045.py +++ b/src/backend/InvenTree/part/migrations/0144_auto_20251203_1045.py @@ -61,6 +61,48 @@ def reverse_update_parameter(apps, schema_editor): ) +def update_category_parameters(apps, schema_editor): + """Copy the 'part_template' field to the new 'template' field.""" + + PartCategoryParameterTemplate = apps.get_model("part", "PartCategoryParameterTemplate") + + category_parameters_to_update = [] + + for cat_param in PartCategoryParameterTemplate.objects.all(): + cat_param.template = cat_param.part_template + category_parameters_to_update.append(cat_param) + + if len(category_parameters_to_update) > 0: + + print(f"Updating {len(category_parameters_to_update)} PartCategoryParameterTemplate records.") + + PartCategoryParameterTemplate.objects.bulk_update( + category_parameters_to_update, + fields=["template"], + ) + + +def reverse_update_category_parameters(apps, schema_editor): + """Copy the 'template' field back to the 'part_template' field.""" + + PartCategoryParameterTemplate = apps.get_model("part", "PartCategoryParameterTemplate") + + category_parameters_to_update = [] + + for cat_param in PartCategoryParameterTemplate.objects.all(): + cat_param.part_template = cat_param.template + category_parameters_to_update.append(cat_param) + + if len(category_parameters_to_update) > 0: + + print(f"Reversing update of {len(category_parameters_to_update)} PartCategoryParameterTemplate records.") + + PartCategoryParameterTemplate.objects.bulk_update( + category_parameters_to_update, + fields=["part_template"], + ) + + class Migration(migrations.Migration): """Data migration for making the PartParameterTemplate and PartParameter models generic. @@ -126,5 +168,49 @@ class Migration(migrations.Migration): update_parameter, reverse_code=reverse_update_parameter, ), - + # Add a new "template" field to the PartCategoryParameterTemplate model + migrations.AddField( + model_name="partcategoryparametertemplate", + name="template", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=deletion.CASCADE, + related_name="part_categories", + to="common.parametertemplate", + ), + ), + # Remove unique constraint on PartCategoryParameterTemplate model + migrations.RemoveConstraint( + model_name="partcategoryparametertemplate", + name="unique_category_parameter_template_pair", + ), + # Perform data migration for the PartCategoryParameterTemplate model + migrations.RunPython( + update_category_parameters, + reverse_code=reverse_update_category_parameters, + ), + # Remove the obsolete "part_template" field from the PartCategoryParameterTemplate model + migrations.RemoveField( + model_name="partcategoryparametertemplate", + name="parameter_template", + ), + # Remove nullable attribute from the new 'template' field + migrations.AlterField( + model_name="partcategoryparametertemplate", + name="template", + field=models.ForeignKey( + on_delete=deletion.CASCADE, + related_name="part_categories", + to="common.parametertemplate", + ), + ), + # Update uniqueness constraint on PartCategoryParameterTemplate model + migrations.AddConstraint( + model_name="partcategoryparametertemplate", + constraint=models.UniqueConstraint( + fields=("category", "template"), + name="unique_category_parameter_pair", + ), + ), ] diff --git a/src/backend/InvenTree/part/migrations/0146_remove_partparametertemplate_model_type_and_more.py b/src/backend/InvenTree/part/migrations/0146_remove_partparametertemplate_model_type_and_more.py new file mode 100644 index 0000000000..a435fd0b4c --- /dev/null +++ b/src/backend/InvenTree/part/migrations/0146_remove_partparametertemplate_model_type_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 5.2.8 on 2025-12-03 12:00 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("part", "0145_auto_20251203_1120"), + ] + + operations = [ + migrations.RemoveField( + model_name="partparametertemplate", + name="model_type", + ), + migrations.RemoveField( + model_name="partparametertemplate", + name="selectionlist", + ), + migrations.DeleteModel( + name="PartParameter", + options={ + "managed": False, + } + ), + migrations.DeleteModel( + name="PartParameterTemplate", + options={ + "managed": False, + } + ), + ]