2
0
mirror of https://github.com/inventree/InvenTree.git synced 2026-03-16 00:50:56 +00:00

Overhaul of migration strategy

- New models simply point to the old database tables
- Perform schema and data migrations on the old models first (in the part app)
- Swap model references in correct order
This commit is contained in:
Oliver Walters
2025-12-03 12:55:00 +00:00
parent d331015329
commit 447e18c1cb
13 changed files with 502 additions and 924 deletions

View File

@@ -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",
},
),
]

View File

@@ -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=[],
),
]

View File

@@ -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
)
]

View File

@@ -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
),
]

View File

@@ -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."""

View File

@@ -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 = [

View File

@@ -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",
),
]

View File

@@ -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",
),
]

View File

@@ -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",
),
]

View File

@@ -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",
),
),
]

View File

@@ -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",
),
]

View File

@@ -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=[]
),
]

View File

@@ -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,
}
),
]