diff --git a/src/backend/InvenTree/common/migrations/0040_auto_20251203_1151.py b/src/backend/InvenTree/common/migrations/0040_auto_20251203_1151.py deleted file mode 100644 index cf66556ed1..0000000000 --- a/src/backend/InvenTree/common/migrations/0040_auto_20251203_1151.py +++ /dev/null @@ -1,256 +0,0 @@ -# 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/common/migrations/0040_parametertemplate_parameter.py b/src/backend/InvenTree/common/migrations/0040_parametertemplate_parameter.py index c751861b33..5bfce74256 100644 --- a/src/backend/InvenTree/common/migrations/0040_parametertemplate_parameter.py +++ b/src/backend/InvenTree/common/migrations/0040_parametertemplate_parameter.py @@ -1,226 +1,248 @@ -# Generated by Django 4.2.25 on 2025-11-10 10:51 +# Generated by Django 5.2.8 on 2025-12-03 12:39 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 +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), ("common", "0039_emailthread_emailmessage"), - ("part", "0143_alter_part_image") + ("contenttypes", "0002_remove_content_type_name"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("part", "0144_auto_20251203_1045") ] 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", - }, - 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", + migrations.SeparateDatabaseAndState( + state_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", + ), + ), + ( + "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", + ), + ), + ( + "enabled", + models.BooleanField( + default=True, + help_text="Is this parameter template enabled?", + verbose_name="Enabled", + ), + ), + ( + "model_type", + models.ForeignKey( + blank=True, + help_text="Target model type for this parameter template", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="contenttypes.contenttype", + verbose_name="Model type", + ), + ), + ( + "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", + ), + ), + ], + options={ + "verbose_name": "Parameter Template", + "verbose_name_plural": "Parameter Templates", + "db_table": "part_partparametertemplate", + }, + bases=( + InvenTree.models.ContentTypeMixin, + InvenTree.models.PluginValidationMixin, + models.Model, ), ), ], - options={ - "verbose_name": "Parameter", - "verbose_name_plural": "Parameters", - "unique_together": {("model_type", "model_id", "template")}, - }, - bases=(InvenTree.models.PluginValidationMixin, models.Model), + database_operations=[], ), - migrations.AddIndex( - model_name="parameter", - index=models.Index( - fields=["model_type", "model_id"], name="common_para_model_t_244405_idx" - ), + migrations.SeparateDatabaseAndState( + state_operations=[ + 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_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", + ), + ), + ( + "model_type", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="contenttypes.contenttype", + ), + ), + ( + "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", + ), + ), + ( + "template", + models.ForeignKey( + help_text="Parameter template", + on_delete=django.db.models.deletion.CASCADE, + related_name="parameters", + to="common.parametertemplate", + verbose_name="Template", + ), + ), + ], + options={ + "verbose_name": "Parameter", + "verbose_name_plural": "Parameters", + "db_table": "part_partparameter", + "indexes": [ + models.Index( + fields=["model_type", "model_id"], + name="part_partpa_model_t_198c9d_idx", + ) + ], + "unique_together": {("model_type", "model_id", "template")}, + }, + bases=( + InvenTree.models.ContentTypeMixin, + InvenTree.models.PluginValidationMixin, + models.Model, + ), + ), + ], + database_operations=[], ), ] diff --git a/src/backend/InvenTree/common/migrations/0041_auto_20251028_1112.py b/src/backend/InvenTree/common/migrations/0041_auto_20251028_1112.py deleted file mode 100644 index 6bfd41c8c9..0000000000 --- a/src/backend/InvenTree/common/migrations/0041_auto_20251028_1112.py +++ /dev/null @@ -1,229 +0,0 @@ -# Generated by Django 4.2.25 on 2025-10-28 11:12 - -import structlog - -from django.db import migrations - -logger = structlog.get_logger('inventree') - - -def convert_to_numeric_value(value: str, units: str): - """Convert a value (with units) to a numeric value. - - Defaults to zero if the value cannot be converted. - """ - - import InvenTree.conversion - - # Default value is null - result = None - - if units: - try: - result = InvenTree.conversion.convert_physical_value(value, units) - result = float(result.magnitude) - except Exception: - pass - else: - try: - result = float(value) - except Exception: - pass - - return result - - -def update_global_setting(apps, schema_editor): - """Update global setting key from PART_PARAMETER_ENFORCE_UNITS to PARAMETER_ENFORCE_UNITS.""" - GlobalSetting = apps.get_model("common", "InvenTreeSetting") - - OLD_KEY = 'PART_PARAMETER_ENFORCE_UNITS' - NEW_KEY = 'PARAMETER_ENFORCE_UNITS' - - try: - setting = GlobalSetting.objects.get(key=OLD_KEY) - - if setting is not None: - # Remove any existing new key - GlobalSetting.objects.filter(key=NEW_KEY).delete() - setting.key = NEW_KEY - setting.save() - logger.info(f"Updated global setting key from {OLD_KEY} to {NEW_KEY}.") - except GlobalSetting.DoesNotExist: - logger.info(f"Global setting {OLD_KEY} does not exist; no update needed.") - - -def remove_existing_parameters(apps, schema_editor): - """Remove any existing Parameter or ParameterTemplate objects from the database.""" - - Parameter = apps.get_model("common", "Parameter") - ParameterTemplate = apps.get_model("common", "ParameterTemplate") - - n_params = Parameter.objects.count() - n_templates = ParameterTemplate.objects.count() - - Parameter.objects.all().delete() - ParameterTemplate.objects.all().delete() - - if n_params > 0: - logger.info(f"Removed {n_params} existing Parameter instances.") - - if n_templates > 0: - logger.info(f"Removed {n_templates} existing ParameterTemplate instances.") - - assert Parameter.objects.count() == 0 - assert ParameterTemplate.objects.count() == 0 - - -def copy_part_parameters(apps, schema_editor): - """Forward migration: copy from PartParameterTemplate to ParameterTemplate.""" - - ContentType = apps.get_model("contenttypes", "ContentType") - PartParameterTemplate = apps.get_model("part", "PartParameterTemplate") - ParameterTemplate = apps.get_model("common", "ParameterTemplate") - PartParameter = apps.get_model("part", "PartParameter") - Parameter = apps.get_model("common", "Parameter") - - # First, create a ParameterTemplate instance for each PartParameterTemplate - templates = [] - - for template in PartParameterTemplate.objects.all(): - templates.append(ParameterTemplate( - name=template.name, - description=template.description, - units=template.units or '', - checkbox=template.checkbox, - choices=template.choices, - selectionlist=template.selectionlist, - model_type=None - )) - - if len(templates) > 0: - ParameterTemplate.objects.bulk_create(templates) - logger.info(f"\nMigrated {len(templates)} PartParameterTemplate instances.") - - assert ParameterTemplate.objects.all().count() == len(templates) - - # Next, copy PartParameter instances to Parameter instances - parameters = [] - - content_type, _created = ContentType.objects.get_or_create(app_label='part', model='part') - - for parameter in PartParameter.objects.all(): - # Find the corresponding ParameterTemplate - template = ParameterTemplate.objects.get(name=parameter.template.name) - - parameters.append(Parameter( - template=template, - model_type=content_type, - model_id=parameter.part.id, - data=parameter.data, - data_numeric=parameter.data_numeric, - note=parameter.note, - metadata=parameter.metadata, - updated=parameter.updated, - updated_by=parameter.updated_by - )) - - if len(parameters) > 0: - Parameter.objects.bulk_create(parameters) - logger.info(f"\nMigrated {len(parameters)} PartParameter instances.") - - assert Parameter.objects.filter(model_type=content_type).count() == len(parameters) - - -def copy_manufacturer_part_parameters(apps, schema_editor): - """Copy ManufacturerPartParameter to Parameter.""" - - ManufacturerPartParameter = apps.get_model("company", "ManufacturerPartParameter") - Parameter = apps.get_model("common", "Parameter") - ParameterTemplate = apps.get_model("common", "ParameterTemplate") - ContentType = apps.get_model("contenttypes", "ContentType") - parameters = [] - - content_type, _created = ContentType.objects.get_or_create(app_label='company', model='manufacturerpart') - - for parameter in ManufacturerPartParameter.objects.all(): - # Find the corresponding ParameterTemplate - template = ParameterTemplate.objects.filter(name=parameter.name).first() - - if not template: - # A matching template does not exist - let's create one - template = ParameterTemplate.objects.create( - name=parameter.name, - description='', - units=parameter.units or '', - model_type=None, - checkbox=False - ) - - parameters.append(Parameter( - template=template, - model_type=content_type, - model_id=parameter.manufacturer_part.id, - data=parameter.value, - data_numeric=convert_to_numeric_value(parameter.value, parameter.units), - )) - - if len(parameters) > 0: - Parameter.objects.bulk_create(parameters) - logger.info(f"\nMigrated {len(parameters)} ManufacturerPartParameter instances.") - - assert Parameter.objects.filter(model_type=content_type).count() == len(parameters) - - -def update_category_parameters(apps, schema_editor): - """Migration for PartCategoryParameterTemplate. - - Copies the contents of the 'parameter_template' field to the new 'template' field - """ - - PartCategoryParameterTemplate = apps.get_model("part", "partcategoryparametertemplate") - ParameterTemplate = apps.get_model("common", "parametertemplate") - - to_update = [] - - for item in PartCategoryParameterTemplate.objects.all(): - # Find a matching template - item.template = ParameterTemplate.objects.get( - name=item.parameter_template.name - ) - - to_update.append(item) - - if len(to_update) > 0: - PartCategoryParameterTemplate.objects.bulk_update(to_update, ['template']) - logger.info(f"Updated {len(to_update)} PartCategoryParameterTemplate instances.") - - -class Migration(migrations.Migration): - - dependencies = [ - ('contenttypes', '__latest__'), - ("part", "0144_partcategoryparametertemplate_template"), - ("company", "0076_alter_company_image"), - ("common", "0040_parametertemplate_parameter"), - ] - - operations = [ - migrations.RunPython( - update_global_setting, - reverse_code=migrations.RunPython.noop - ), - migrations.RunPython( - remove_existing_parameters, - reverse_code=migrations.RunPython.noop - ), - migrations.RunPython( - copy_part_parameters, - reverse_code=migrations.RunPython.noop - ), - migrations.RunPython( - copy_manufacturer_part_parameters, - reverse_code=migrations.RunPython.noop - ), - migrations.RunPython( - update_category_parameters, - reverse_code=migrations.RunPython.noop - ) - ] diff --git a/src/backend/InvenTree/common/migrations/0041_auto_20251203_1244.py b/src/backend/InvenTree/common/migrations/0041_auto_20251203_1244.py new file mode 100644 index 0000000000..31e4973b7e --- /dev/null +++ b/src/backend/InvenTree/common/migrations/0041_auto_20251203_1244.py @@ -0,0 +1,107 @@ +# Generated by Django 5.2.8 on 2025-12-03 12:44 + +from django.db import migrations + +def convert_to_numeric_value(value: str, units: str): + """Convert a value (with units) to a numeric value. + + Defaults to zero if the value cannot be converted. + """ + + import InvenTree.conversion + + # Default value is null + result = None + + if units: + try: + result = InvenTree.conversion.convert_physical_value(value, units) + result = float(result.magnitude) + except Exception: + pass + else: + try: + result = float(value) + except Exception: + pass + + return result + + +def copy_manufacturer_part_parameters(apps, schema_editor): + """Copy ManufacturerPartParameter to Parameter.""" + + ManufacturerPartParameter = apps.get_model("company", "ManufacturerPartParameter") + Parameter = apps.get_model("common", "Parameter") + ParameterTemplate = apps.get_model("common", "ParameterTemplate") + ContentType = apps.get_model("contenttypes", "ContentType") + parameters = [] + + content_type, _created = ContentType.objects.get_or_create(app_label='company', model='manufacturerpart') + + for parameter in ManufacturerPartParameter.objects.all(): + # Find the corresponding ParameterTemplate + template = ParameterTemplate.objects.filter(name=parameter.name).first() + + if not template: + # A matching template does not exist - let's create one + template = ParameterTemplate.objects.create( + name=parameter.name, + description='', + units=parameter.units or '', + model_type=None, + checkbox=False + ) + + parameters.append(Parameter( + template=template, + model_type=content_type, + model_id=parameter.manufacturer_part.id, + data=parameter.value, + data_numeric=convert_to_numeric_value(parameter.value, parameter.units), + )) + + if len(parameters) > 0: + Parameter.objects.bulk_create(parameters) + print(f"\nMigrated {len(parameters)} ManufacturerPartParameter instances.") + + assert Parameter.objects.filter(model_type=content_type).count() == len(parameters) + + +def update_global_setting(apps, schema_editor): + """Update global setting key from PART_PARAMETER_ENFORCE_UNITS to PARAMETER_ENFORCE_UNITS.""" + GlobalSetting = apps.get_model("common", "InvenTreeSetting") + + OLD_KEY = 'PART_PARAMETER_ENFORCE_UNITS' + NEW_KEY = 'PARAMETER_ENFORCE_UNITS' + + try: + setting = GlobalSetting.objects.get(key=OLD_KEY) + + if setting is not None: + # Remove any existing new key + GlobalSetting.objects.filter(key=NEW_KEY).delete() + setting.key = NEW_KEY + setting.save() + print(f"Updated global setting key from {OLD_KEY} to {NEW_KEY}.") + except GlobalSetting.DoesNotExist: + print(f"Global setting {OLD_KEY} does not exist; no update needed.") + + +class Migration(migrations.Migration): + """Perform data migration for the ManufacturerPartParameter model.""" + + dependencies = [ + ("common", "0040_parametertemplate_parameter"), + ] + + operations = [ + migrations.RunPython( + update_global_setting, + reverse_code=migrations.RunPython.noop + ), + migrations.RunPython( + copy_manufacturer_part_parameters, + reverse_code=migrations.RunPython.noop + ), + ] diff --git a/src/backend/InvenTree/common/models.py b/src/backend/InvenTree/common/models.py index 2aeeac49c3..6e1dbd2e5f 100644 --- a/src/backend/InvenTree/common/models.py +++ b/src/backend/InvenTree/common/models.py @@ -2387,6 +2387,11 @@ class ParameterTemplate( verbose_name = _('Parameter Template') verbose_name_plural = _('Parameter Templates') + # Note: Data was migrated from the existing 'part_partparametertemplate' table + # Ref: https://github.com/inventree/InvenTree/pull/10699 + # To avoid data loss, we retain the existing table name + db_table = 'part_partparametertemplate' + class ModelChoices(RenderChoices): """Model choices for parameters.""" @@ -2576,9 +2581,13 @@ class Parameter( verbose_name = _('Parameter') verbose_name_plural = _('Parameters') unique_together = [['model_type', 'model_id', 'template']] - indexes = [models.Index(fields=['model_type', 'model_id'])] + # Note: Data was migrated from the existing 'part_partparameter' table + # Ref: https://github.com/inventree/InvenTree/pull/10699 + # To avoid data loss, we retain the existing table name + db_table = 'part_partparameter' + class ModelChoices(RenderChoices): """Model choices for parameters.""" diff --git a/src/backend/InvenTree/company/migrations/0077_delete_manufacturerpartparameter.py b/src/backend/InvenTree/company/migrations/0077_delete_manufacturerpartparameter.py index 4af9571f6d..0b39842dc3 100644 --- a/src/backend/InvenTree/company/migrations/0077_delete_manufacturerpartparameter.py +++ b/src/backend/InvenTree/company/migrations/0077_delete_manufacturerpartparameter.py @@ -4,10 +4,15 @@ from django.db import migrations class Migration(migrations.Migration): + """Remove the ManufacturerPartParameter model. + + The data has been migrated to the common.Parameter model in a previous migration. + """ dependencies = [ ("company", "0076_alter_company_image"), - ("common", "0041_auto_20251028_1112"), + ("common", "0041_auto_20251203_1244"), + ("part", "0146_auto_20251203_1241") ] operations = [ 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 574d32563b..f7f13bb0a7 100644 --- a/src/backend/InvenTree/part/migrations/0144_auto_20251203_1045.py +++ b/src/backend/InvenTree/part/migrations/0144_auto_20251203_1045.py @@ -61,48 +61,6 @@ 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. @@ -168,49 +126,32 @@ 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 + # Update the "model_type" field on the PartParameter model to be non-nullable migrations.AlterField( - model_name="partcategoryparametertemplate", - name="template", + model_name="partparameter", + name="model_type", field=models.ForeignKey( on_delete=deletion.CASCADE, - related_name="part_categories", - to="common.parametertemplate", + to="contenttypes.contenttype", ), ), - # Update uniqueness constraint on PartCategoryParameterTemplate model - migrations.AddConstraint( - model_name="partcategoryparametertemplate", - constraint=models.UniqueConstraint( - fields=("category", "template"), - name="unique_category_parameter_pair", - ), + # Update the "model_id" field on the PartParameter model to be non-nullable + migrations.AlterField( + model_name="partparameter", + name="model_id", + field=models.PositiveIntegerField( + help_text="ID of the target model for this parameter", + verbose_name="Model ID", + ) + ), + # Remove the "unique_together" constraint on the PartParameter model + migrations.AlterUniqueTogether( + name="partparameter", + unique_together={('model_type', 'model_id', 'template')}, + ), + # Remove the obsolete "part" field from the PartParameter model + migrations.RemoveField( + model_name="partparameter", + name="part", ), ] diff --git a/src/backend/InvenTree/part/migrations/0144_partcategoryparametertemplate_template.py b/src/backend/InvenTree/part/migrations/0144_partcategoryparametertemplate_template.py deleted file mode 100644 index bc627b0dd7..0000000000 --- a/src/backend/InvenTree/part/migrations/0144_partcategoryparametertemplate_template.py +++ /dev/null @@ -1,30 +0,0 @@ -# Generated by Django 5.2.8 on 2025-11-25 06:19 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("part", "0143_alter_part_image"), - ("common", "0040_parametertemplate_parameter"), - ] - - operations = [ - migrations.AddField( - model_name="partcategoryparametertemplate", - name="template", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - related_name="part_categories", - to="common.parametertemplate", - ), - ), - migrations.RemoveConstraint( - model_name="partcategoryparametertemplate", - name="unique_category_parameter_template_pair", - ), - ] diff --git a/src/backend/InvenTree/part/migrations/0145_auto_20251203_1120.py b/src/backend/InvenTree/part/migrations/0145_auto_20251203_1120.py deleted file mode 100644 index 18dc1f6a80..0000000000 --- a/src/backend/InvenTree/part/migrations/0145_auto_20251203_1120.py +++ /dev/null @@ -1,42 +0,0 @@ -# Generated by Django 5.2.8 on 2025-12-03 11:20 - -from django.db import migrations, models -from django.db.models import deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ("part", "0144_auto_20251203_1045"), - ] - - operations = [ - # Update the "model_type" field on the PartParameter model to be non-nullable - migrations.AlterField( - model_name="partparameter", - name="model_type", - field=models.ForeignKey( - on_delete=deletion.CASCADE, - to="contenttypes.contenttype", - ), - ), - # Update the "model_id" field on the PartParameter model to be non-nullable - migrations.AlterField( - model_name="partparameter", - name="model_id", - field=models.PositiveIntegerField( - help_text="ID of the target model for this parameter", - verbose_name="Model ID", - ) - ), - # Remove the "unique_together" constraint on the PartParameter model - migrations.AlterUniqueTogether( - name="partparameter", - unique_together={('model_type', 'model_id', 'template')}, - ), - # Remove the obsolete "part" field from the PartParameter model - migrations.RemoveField( - model_name="partparameter", - name="part", - ), - ] diff --git a/src/backend/InvenTree/part/migrations/0145_auto_20251203_1238.py b/src/backend/InvenTree/part/migrations/0145_auto_20251203_1238.py new file mode 100644 index 0000000000..3b40ad5daf --- /dev/null +++ b/src/backend/InvenTree/part/migrations/0145_auto_20251203_1238.py @@ -0,0 +1,106 @@ +# Generated by Django 5.2.8 on 2025-12-03 12:38 + +from django.db import migrations, models +from django.db.models import deletion + + +def update_category_parameters(apps, schema_editor): + """Copy the 'parameter_template' field to the new 'template' field.""" + + PartCategoryParameterTemplate = apps.get_model("part", "PartCategoryParameterTemplate") + ParameterTemplate = apps.get_model("common", "ParameterTemplate") + + category_parameters_to_update = [] + + for cat_param in PartCategoryParameterTemplate.objects.all(): + template = ParameterTemplate.objects.get(pk=cat_param.parameter_template_id) + cat_param.template = 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 'parameter_template' field.""" + + PartParameterTemplate = apps.get_model("part", "PartParameterTemplate") + PartCategoryParameterTemplate = apps.get_model("part", "PartCategoryParameterTemplate") + + category_parameters_to_update = [] + + for cat_param in PartCategoryParameterTemplate.objects.all(): + template = PartParameterTemplate.objects.get(pk=cat_param.template_id) + cat_param.parameter_template = 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=["parameter_template"], + ) + + +class Migration(migrations.Migration): + + dependencies = [ + ("part", "0144_auto_20251203_1045"), + ("common", "0040_parametertemplate_parameter") + ] + + operations = [ + # 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/0145_remove_partparametertemplate_selectionlist_and_more.py b/src/backend/InvenTree/part/migrations/0145_remove_partparametertemplate_selectionlist_and_more.py deleted file mode 100644 index ca946691d4..0000000000 --- a/src/backend/InvenTree/part/migrations/0145_remove_partparametertemplate_selectionlist_and_more.py +++ /dev/null @@ -1,45 +0,0 @@ -# Generated by Django 5.2.8 on 2025-11-25 07:01 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("common", "0041_auto_20251028_1112"), - ("part", "0144_partcategoryparametertemplate_template"), - ] - - operations = [ - migrations.RemoveField( - model_name="partparametertemplate", - name="selectionlist", - ), - migrations.RemoveField( - model_name="partcategoryparametertemplate", - name="parameter_template", - ), - migrations.AlterField( - model_name="partcategoryparametertemplate", - name="template", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="part_categories", - to="common.parametertemplate", - ), - ), - migrations.AddConstraint( - model_name="partcategoryparametertemplate", - constraint=models.UniqueConstraint( - fields=("category", "template"), - name="unique_category_parameter_pair", - ), - ), - migrations.DeleteModel( - name="PartParameter", - ), - migrations.DeleteModel( - name="PartParameterTemplate", - ), - ] diff --git a/src/backend/InvenTree/part/migrations/0146_auto_20251203_1241.py b/src/backend/InvenTree/part/migrations/0146_auto_20251203_1241.py new file mode 100644 index 0000000000..bd6bdfbe31 --- /dev/null +++ b/src/backend/InvenTree/part/migrations/0146_auto_20251203_1241.py @@ -0,0 +1,23 @@ +# Generated by Django 5.2.8 on 2025-12-03 12:41 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("part", "0145_auto_20251203_1238"), + ] + + # Remove the PartParameterTemplate and PartParameter models + # Note: We *DO NOT* drop the underlying database tables, + # as these are now used by the common.ParameterTemplate and common.Parameter models + operations = [ + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.DeleteModel(name='PartParameterTemplate'), + migrations.DeleteModel(name='PartParameter'), + ], + database_operations=[] + ), + ] 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 deleted file mode 100644 index a435fd0b4c..0000000000 --- a/src/backend/InvenTree/part/migrations/0146_remove_partparametertemplate_model_type_and_more.py +++ /dev/null @@ -1,33 +0,0 @@ -# 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, - } - ), - ]