2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-12-16 17:28:11 +00:00

Remove old models

This commit is contained in:
Oliver Walters
2025-11-25 07:23:09 +00:00
parent 211c98ed30
commit 72607110be
13 changed files with 223 additions and 546 deletions

View File

@@ -7,8 +7,9 @@ from typing import Any, Optional
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.fields import GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.db import models
from django.db import models, transaction
from django.db.models import QuerySet
from django.db.models.signals import post_save
from django.dispatch import receiver
@@ -550,6 +551,70 @@ class InvenTreeParameterMixin(InvenTreePermissionCheckMixin, models.Model):
self.parameters_list.all().delete()
super().delete(*args, **kwargs)
@transaction.atomic
def copy_parameters_from(self, other, clear=True, **kwargs):
"""Copy all parameters from another model instance.
Arguments:
other: The other model instance to copy parameters from
clear: If True, clear existing parameters before copying
**kwargs: Additional keyword arguments to pass to the Parameter constructor
"""
import common.models
if clear:
self.parameters_list.all().delete()
parameters = []
content_type = ContentType.objects.get_for_model(self.__class__)
template_ids = [parameter.template.pk for parameter in other.parameters.all()]
# Remove all conflicting parameters first
self.parameters_list.filter(template__pk__in=template_ids).delete()
for parameter in other.parameters.all():
parameter.pk = None
parameter.model_id = self.pk
parameter.model_type = content_type
parameters.append(parameter)
if len(parameters) > 0:
common.models.Parameter.objects.bulk_create(parameters)
def get_parameter(self, name: str):
"""Return a Parameter instance for the given parameter name.
Args:
name: Name of the parameter template
Returns:
Parameter instance if found, else None
"""
return self.parameters_list.filter(template__name=name).first()
def get_parameters(self):
"""Return all Parameter instances for this model."""
return self.parameters_list.all().order_by('template__name')
def parameters_map(self):
"""Return a map (dict) of parameter values associated with this Part instance, of the form.
Example:
{
"name_1": "value_1",
"name_2": "value_2",
}
"""
params = {}
for parameter in self.parameters.all().prefetch_related('template'):
params[parameter.template.name] = parameter.data
return params
class InvenTreeAttachmentMixin(InvenTreePermissionCheckMixin):
"""Provides an abstracted class for managing file attachments.

View File

@@ -167,11 +167,37 @@ def copy_manufacturer_part_parameters(apps, schema_editor):
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'])
print(f"Updated {len(to_update)} PartCategoryParameterTemplate instances.")
class Migration(migrations.Migration):
dependencies = [
("part", "0132_partparametertemplate_selectionlist"),
("common", "0040_parametertemplate_parameter"),
("part", "0144_partcategoryparametertemplate_template")
]
operations = [
@@ -190,6 +216,9 @@ class Migration(migrations.Migration):
migrations.RunPython(
copy_manufacturer_part_parameters,
reverse_code=migrations.RunPython.noop
),
migrations.RunPython(
update_category_parameters,
reverse_code=migrations.RunPython.noop
)
# TODO: Data migration for existing CategoryParameter objects
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 5.2.8 on 2025-11-25 07:03
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("company", "0076_alter_company_image"),
("common", "0041_auto_20251028_1112"),
]
operations = [
migrations.DeleteModel(
name="ManufacturerPartParameter",
),
]

View File

@@ -585,55 +585,6 @@ class ManufacturerPart(
return s
class ManufacturerPartParameter(InvenTree.models.InvenTreeModel):
"""A ManufacturerPartParameter represents a key:value parameter for a ManufacturerPart.
This is used to represent parameters / properties for a particular manufacturer part.
Each parameter is a simple string (text) value.
"""
class Meta:
"""Metaclass defines extra model options."""
verbose_name = _('Manufacturer Part Parameter')
unique_together = ('manufacturer_part', 'name')
@staticmethod
def get_api_url():
"""Return the API URL associated with the ManufacturerPartParameter model."""
return reverse('api-manufacturer-part-parameter-list')
manufacturer_part = models.ForeignKey(
ManufacturerPart,
on_delete=models.CASCADE,
related_name='parameters',
verbose_name=_('Manufacturer Part'),
)
name = models.CharField(
max_length=500,
blank=False,
verbose_name=_('Name'),
help_text=_('Parameter name'),
)
value = models.CharField(
max_length=500,
blank=False,
verbose_name=_('Value'),
help_text=_('Parameter value'),
)
units = models.CharField(
max_length=64,
blank=True,
null=True,
verbose_name=_('Units'),
help_text=_('Parameter units'),
)
class SupplierPartManager(models.Manager):
"""Define custom SupplierPart objects manager.

View File

@@ -18,7 +18,6 @@ from django.db import models
from django.db.models import (
Case,
DecimalField,
Exists,
ExpressionWrapper,
F,
FloatField,
@@ -517,59 +516,3 @@ def annotate_bom_item_can_build(queryset: QuerySet, reference: str = '') -> Quer
)
return queryset
def order_by_parameter(
queryset: QuerySet, template_id: int, ascending: bool = True
) -> QuerySet:
"""Order the given queryset by a given template parameter.
Parts which do not have a value for the given parameter are ordered last.
Arguments:
queryset: A queryset of Part objects
template_id (int): The ID of the template parameter to order by
ascending (bool): Order by ascending or descending (default = True)
Returns:
A queryset of Part objects ordered by the given parameter
"""
template_filter = part.models.PartParameter.objects.filter(
template__id=template_id, part_id=OuterRef('id')
)
# Annotate the queryset with the parameter value, and whether it exists
queryset = queryset.annotate(parameter_exists=Exists(template_filter))
# Annotate the text data value
queryset = queryset.annotate(
parameter_value=Case(
When(
parameter_exists=True,
then=Subquery(
template_filter.values('data')[:1], output_field=models.CharField()
),
),
default=Value('', output_field=models.CharField()),
),
parameter_value_numeric=Case(
When(
parameter_exists=True,
then=Subquery(
template_filter.values('data_numeric')[:1],
output_field=models.FloatField(),
),
),
default=Value(0, output_field=models.FloatField()),
),
)
prefix = '' if ascending else '-'
# Return filtered queryset
return queryset.order_by(
'-parameter_exists',
f'{prefix}parameter_value_numeric',
f'{prefix}parameter_value',
)

View File

@@ -14,6 +14,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='partparametertemplate',
name='name',
field=models.CharField(help_text='Parameter Name', max_length=100, unique=True, validators=[part.models.validate_template_name], verbose_name='Name'),
field=models.CharField(help_text='Parameter Name', max_length=100, unique=True, validators=[], verbose_name='Name'),
),
]

View File

@@ -0,0 +1,30 @@
# 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 = [
("common", "0040_parametertemplate_parameter"),
("part", "0143_alter_part_image"),
]
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

@@ -0,0 +1,45 @@
# 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

@@ -15,11 +15,7 @@ from typing import cast
from django.conf import settings
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from django.core.validators import (
MaxValueValidator,
MinLengthValidator,
MinValueValidator,
)
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models, transaction
from django.db.models import F, Q, QuerySet, Sum, UniqueConstraint
from django.db.models.functions import Coalesce
@@ -59,7 +55,7 @@ from company.models import SupplierPart
from InvenTree import helpers, validators
from InvenTree.exceptions import log_error
from InvenTree.fields import InvenTreeURLField
from InvenTree.helpers import decimal2money, decimal2string, normalize, str2bool
from InvenTree.helpers import decimal2money, decimal2string, normalize
from order import models as OrderModels
from order.status_codes import (
PurchaseOrderStatus,
@@ -287,7 +283,7 @@ class PartCategory(
def get_parameter_templates(self):
"""Return parameter templates associated to category."""
prefetch = PartCategoryParameterTemplate.objects.prefetch_related(
'category', 'parameter_template'
'category', 'parameter'
)
return prefetch.filter(category=self.id)
@@ -1028,7 +1024,7 @@ class Part(
self.ensure_trackable()
def ensure_trackable(self):
"""Ensure that trackable is set correctly downline."""
"""Ensure that trackable is set correctly downstream."""
if self.trackable:
for part in self.get_used_in():
if not part.trackable:
@@ -2388,36 +2384,6 @@ class Part(
sub.bom_item = bom_item
sub.save()
@transaction.atomic
def copy_parameters_from(self, other: Part, **kwargs) -> None:
"""Copy all parameter values from another Part instance."""
clear = kwargs.get('clear', True)
if clear:
self.get_parameters().delete()
parameters = []
for parameter in other.get_parameters():
# If this part already has a parameter pointing to the same template,
# delete that parameter from this part first!
try:
existing = PartParameter.objects.get(
part=self, template=parameter.template
)
existing.delete()
except PartParameter.DoesNotExist:
pass
parameter.part = self
parameter.pk = None
parameters.append(parameter)
if len(parameters) > 0:
PartParameter.objects.bulk_create(parameters)
@transaction.atomic
def copy_tests_from(self, other: Part, **kwargs) -> None:
"""Copy all test templates from another Part instance.
@@ -2571,36 +2537,6 @@ class Part(
return quantity
def get_parameter(self, name):
"""Return the parameter with the given name.
If no matching parameter is found, return None.
"""
try:
return self.parameters.get(template__name=name)
except PartParameter.DoesNotExist:
return None
def get_parameters(self):
"""Return all parameters for this part, ordered by name."""
return self.parameters.order_by('template__name')
def parameters_map(self):
"""Return a map (dict) of parameter values associated with this Part instance, of the form.
Example:
{
"name_1": "value_1",
"name_2": "value_2",
}
"""
params = {}
for parameter in self.parameters.all():
params[parameter.template.name] = parameter.data
return params
@property
def has_variants(self):
"""Check if this Part object has variants underneath it."""
@@ -3768,357 +3704,15 @@ class PartTestTemplate(InvenTree.models.InvenTreeMetadataModel):
return [x.strip() for x in self.choices.split(',') if x.strip()]
def validate_template_name(name):
"""Placeholder for legacy function used in migrations."""
class PartParameterTemplate(InvenTree.models.InvenTreeMetadataModel):
"""A PartParameterTemplate provides a template for key:value pairs for extra parameters fields/values to be added to a Part.
This allows users to arbitrarily assign data fields to a Part beyond the built-in attributes.
Attributes:
name: The name (key) of the Parameter [string]
units: The units of the Parameter [string]
description: Description of the parameter [string]
checkbox: Boolean flag to indicate whether the parameter is a checkbox [bool]
choices: List of valid choices for the parameter [string]
selectionlist: SelectionList that should be used for choices [selectionlist]
"""
class Meta:
"""Metaclass options for the PartParameterTemplate model."""
verbose_name = _('Part Parameter Template')
@staticmethod
def get_api_url():
"""Return the list API endpoint URL associated with the PartParameterTemplate model."""
return reverse('api-part-parameter-template-list')
def __str__(self):
"""Return a string representation of a PartParameterTemplate instance."""
s = str(self.name)
if self.units:
s += f' ({self.units})'
return s
def clean(self):
"""Custom cleaning step for this model.
Checks:
- A 'checkbox' field cannot have 'choices' set
- A 'checkbox' field cannot have 'units' set
"""
super().clean()
# Check that checkbox parameters do not have units or choices
if self.checkbox:
if self.units:
raise ValidationError({
'units': _('Checkbox parameters cannot have units')
})
if self.choices:
raise ValidationError({
'choices': _('Checkbox parameters cannot have choices')
})
# Check that 'choices' are in fact valid
if self.choices is None:
self.choices = ''
else:
self.choices = str(self.choices).strip()
if self.choices:
choice_set = set()
for choice in self.choices.split(','):
choice = choice.strip()
# Ignore empty choices
if not choice:
continue
if choice in choice_set:
raise ValidationError({'choices': _('Choices must be unique')})
choice_set.add(choice)
def validate_unique(self, exclude=None):
"""Ensure that PartParameterTemplates cannot be created with the same name.
This test should be case-insensitive (which the unique caveat does not cover).
"""
super().validate_unique(exclude)
try:
others = PartParameterTemplate.objects.filter(
name__iexact=self.name
).exclude(pk=self.pk)
if others.exists():
msg = _('Parameter template name must be unique')
raise ValidationError({'name': msg})
except PartParameterTemplate.DoesNotExist:
pass
def get_choices(self):
"""Return a list of choices for this parameter template."""
if self.selectionlist:
return self.selectionlist.get_choices()
if not self.choices:
return []
return [x.strip() for x in self.choices.split(',') if x.strip()]
name = models.CharField(
max_length=100,
verbose_name=_('Name'),
help_text=_('Parameter Name'),
unique=True,
)
units = models.CharField(
max_length=25,
verbose_name=_('Units'),
help_text=_('Physical units for this parameter'),
blank=True,
validators=[validators.validate_physical_units],
)
description = models.CharField(
max_length=250,
verbose_name=_('Description'),
help_text=_('Parameter description'),
blank=True,
)
checkbox = models.BooleanField(
default=False,
verbose_name=_('Checkbox'),
help_text=_('Is this parameter a checkbox?'),
)
choices = models.CharField(
max_length=5000,
verbose_name=_('Choices'),
help_text=_('Valid choices for this parameter (comma-separated)'),
blank=True,
)
selectionlist = models.ForeignKey(
common.models.SelectionList,
blank=True,
null=True,
on_delete=models.SET_NULL,
related_name='parameter_templates',
verbose_name=_('Selection List'),
help_text=_('Selection list for this parameter'),
)
@receiver(
post_save,
sender=PartParameterTemplate,
dispatch_uid='post_save_part_parameter_template',
)
def post_save_part_parameter_template(sender, instance, created, **kwargs):
"""Callback function when a PartParameterTemplate is created or saved."""
import part.tasks as part_tasks
if InvenTree.ready.canAppAccessDatabase() and not InvenTree.ready.isImportingData():
if not created:
# Schedule a background task to rebuild the parameters against this template
InvenTree.tasks.offload_task(
part_tasks.rebuild_parameters,
instance.pk,
force_async=True,
group='part',
)
class PartParameter(
common.models.UpdatedUserMixin, InvenTree.models.InvenTreeMetadataModel
):
"""A PartParameter is a specific instance of a PartParameterTemplate. It assigns a particular parameter <key:value> pair to a part.
Attributes:
part: Reference to a single Part object
template: Reference to a single PartParameterTemplate object
data: The data (value) of the Parameter [string]
data_numeric: Numeric value of the parameter (if applicable) [float]
note: Optional note field for the parameter [string]
updated: Timestamp of when the parameter was last updated [datetime]
updated_by: Reference to the User who last updated the parameter [User]
"""
class Meta:
"""Metaclass providing extra model definition."""
verbose_name = _('Part Parameter')
# Prevent multiple instances of a parameter for a single part
unique_together = ('part', 'template')
@staticmethod
def get_api_url():
"""Return the list API endpoint URL associated with the PartParameter model."""
return reverse('api-part-parameter-list')
def __str__(self):
"""String representation of a PartParameter (used in the admin interface)."""
return f'{self.part.full_name} : {self.template.name} = {self.data} ({self.template.units})'
def delete(self):
"""Custom delete handler for the PartParameter model.
- Check if the parameter can be deleted
"""
self.check_part_lock()
super().delete()
def check_part_lock(self):
"""Check if the referenced part is locked."""
# TODO: Potentially control this behaviour via a global setting
if self.part.locked:
raise ValidationError(_('Parameter cannot be modified - part is locked'))
def save(self, *args, **kwargs):
"""Custom save method for the PartParameter model."""
# Validate the PartParameter before saving
self.calculate_numeric_value()
# Check if the part is locked
self.check_part_lock()
# Convert 'boolean' values to 'True' / 'False'
if self.template.checkbox:
self.data = str2bool(self.data)
self.data_numeric = 1 if self.data else 0
super().save(*args, **kwargs)
def clean(self):
"""Validate the PartParameter before saving to the database."""
super().clean()
# Validate the parameter data against the template choices
if choices := self.template.get_choices():
if self.data not in choices:
raise ValidationError({'data': _('Invalid choice for parameter value')})
self.calculate_numeric_value()
# Run custom validation checks (via plugins)
from plugin import PluginMixinEnum, registry
for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION):
# Note: The validate_part_parameter function may raise a ValidationError
try:
result = plugin.validate_part_parameter(self, self.data)
if result:
break
except ValidationError as exc:
# Re-throw the ValidationError against the 'data' field
raise ValidationError({'data': exc.message})
except Exception:
log_error('validate_part_parameter', plugin=plugin.slug)
def calculate_numeric_value(self):
"""Calculate a numeric value for the parameter data.
- If a 'units' field is provided, then the data will be converted to the base SI unit.
- Otherwise, we'll try to do a simple float cast
"""
if self.template.units:
try:
self.data_numeric = InvenTree.conversion.convert_physical_value(
self.data, self.template.units
)
except (ValidationError, ValueError):
self.data_numeric = None
# No units provided, so try to cast to a float
else:
try:
self.data_numeric = float(self.data)
except ValueError:
self.data_numeric = None
if self.data_numeric is not None and type(self.data_numeric) is float:
# Prevent out of range numbers, etc
# Ref: https://github.com/inventree/InvenTree/issues/7593
if math.isnan(self.data_numeric) or math.isinf(self.data_numeric):
self.data_numeric = None
part = models.ForeignKey(
Part,
on_delete=models.CASCADE,
related_name='parameters',
verbose_name=_('Part'),
help_text=_('Parent Part'),
)
template = models.ForeignKey(
PartParameterTemplate,
on_delete=models.CASCADE,
related_name='instances',
verbose_name=_('Template'),
help_text=_('Parameter Template'),
)
data = models.CharField(
max_length=500,
verbose_name=_('Data'),
help_text=_('Parameter Value'),
validators=[MinLengthValidator(1)],
)
data_numeric = models.FloatField(default=None, null=True, blank=True)
note = models.CharField(
max_length=500,
blank=True,
verbose_name=_('Note'),
help_text=_('Optional note field'),
)
@property
def units(self):
"""Return the units associated with the template."""
return self.template.units
@property
def name(self):
"""Return the name of the template."""
return self.template.name
@property
def description(self):
"""Return the description of the template."""
return self.template.description
@classmethod
def create(cls, part, template, data, save=False):
"""Custom save method for the PartParameter class."""
part_parameter = cls(part=part, template=template, data=data)
if save:
part_parameter.save()
return part_parameter
class PartCategoryParameterTemplate(InvenTree.models.InvenTreeMetadataModel):
"""A PartCategoryParameterTemplate creates a unique relationship between a PartCategory and a PartParameterTemplate.
"""A PartCategoryParameterTemplate creates a unique relationship between a PartCategory and a ParameterTemplate.
Multiple PartParameterTemplate instances can be associated to a PartCategory to drive a default list of parameter templates attached to a Part instance upon creation.
Multiple ParameterTemplate instances can be associated to a PartCategory to drive a default list of parameter templates attached to a Part instance upon creation.
Attributes:
category: Reference to a single PartCategory object
parameter_template: Reference to a single PartParameterTemplate object
default_value: The default value for the parameter in the context of the selected
category
template: Reference to a single ParameterTemplate object
default_value: The default value for the parameter in the context of the selected category
"""
@staticmethod
@@ -4133,8 +3727,7 @@ class PartCategoryParameterTemplate(InvenTree.models.InvenTreeMetadataModel):
constraints = [
UniqueConstraint(
fields=['category', 'parameter_template'],
name='unique_category_parameter_template_pair',
fields=['category', 'template'], name='unique_category_parameter_pair'
)
]
@@ -4177,12 +3770,10 @@ class PartCategoryParameterTemplate(InvenTree.models.InvenTreeMetadataModel):
help_text=_('Part Category'),
)
parameter_template = models.ForeignKey(
PartParameterTemplate,
template = models.ForeignKey(
common.models.ParameterTemplate,
on_delete=models.CASCADE,
related_name='part_categories',
verbose_name=_('Parameter Template'),
help_text=_('Parameter Template'),
)
default_value = models.CharField(

View File

@@ -3,6 +3,7 @@
from dataclasses import dataclass
from typing import Optional
import common.models
import part.models as part_models
@@ -61,22 +62,22 @@ class ImportParameter:
name (str): The name of the parameter.
value (str): The value of the parameter.
on_category (Optional[bool]): Indicates if the parameter is associated with a category. This will be automatically set by InvenTree
parameter_template (Optional[PartParameterTemplate]): The associated parameter template, if any.
parameter_template (Optional[ParameterTemplate]): The associated parameter template, if any.
"""
name: str
value: str
on_category: Optional[bool] = False
parameter_template: Optional[part_models.PartParameterTemplate] = None
parameter_template: Optional[common.models.ParameterTemplate] = None
def __post_init__(self):
"""Post-initialization to fetch the parameter template if not provided."""
if not self.parameter_template:
try:
self.parameter_template = part_models.PartParameterTemplate.objects.get(
self.parameter_template = common.models.ParameterTemplate.objects.get(
name__iexact=self.name
)
except part_models.PartParameterTemplate.DoesNotExist:
except common.models.ParameterTemplate.DoesNotExist:
pass

View File

@@ -23,7 +23,7 @@ class PartParameterExportOptionsSerializer(serializers.Serializer):
class PartParameterExporter(DataExportMixin, InvenTreePlugin):
"""Builtin plugin for exporting PartParameter data.
"""Builtin plugin for exporting Part Parameter data.
Extends the "part" export process, to include all associated PartParameter data.
"""
@@ -93,7 +93,9 @@ class PartParameterExporter(DataExportMixin, InvenTreePlugin):
def prefetch_queryset(self, queryset):
"""Ensure that the part parameters are prefetched."""
queryset = queryset.prefetch_related('parameters', 'parameters__template')
queryset = queryset.prefetch_related(
'parameters_list', 'parameters_list__template'
)
return queryset
@@ -118,7 +120,7 @@ class PartParameterExporter(DataExportMixin, InvenTreePlugin):
for part in parts:
# Extract the part parameters from the serialized data
for parameter in part.get('parameters', []):
for parameter in part.get('parameters_list', []):
if template := parameter.get('template_detail', None):
template_id = template['pk']

View File

@@ -11,6 +11,7 @@ from django import template
from django.apps.registry import apps
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db.models import Model
from django.db.models.query import QuerySet
from django.utils.safestring import SafeString, mark_safe
from django.utils.translation import gettext_lazy as _
@@ -22,6 +23,7 @@ from PIL import Image
import common.currency
import common.icons
import common.models
import InvenTree.helpers
import InvenTree.helpers_model
import report.helpers
@@ -329,19 +331,23 @@ def part_image(part: Part, preview: bool = False, thumbnail: bool = False, **kwa
@register.simple_tag()
def part_parameter(part: Part, parameter_name: str) -> Optional[str]:
"""Return a PartParameter object for the given part and parameter name.
def parameter(
instance: Model, parameter_name: str
) -> Optional[common.models.Parameter]:
"""Return a Parameter object for the given part and parameter name.
Arguments:
part: A Part object
instance: A Model object
parameter_name: The name of the parameter to retrieve
Returns:
A PartParameter object, or None if not found
A Parameter object, or None if not found
"""
if type(part) is Part:
return part.get_parameter(parameter_name)
return None
return (
instance.parameters.prefetch_related('template')
.filter(template__name=parameter_name)
.first()
)
@register.simple_tag()

View File

@@ -106,15 +106,12 @@ def get_ruleset_models() -> dict:
'part_partsellpricebreak',
'part_partinternalpricebreak',
'part_parttesttemplate',
'part_partparametertemplate',
'part_partparameter',
'part_partrelated',
'part_partstar',
'part_partstocktake',
'part_partcategorystar',
'company_supplierpart',
'company_manufacturerpart',
'company_manufacturerpartparameter',
],
RuleSetEnum.STOCK_LOCATION: ['stock_stocklocation', 'stock_stocklocationtype'],
RuleSetEnum.STOCK: [