diff --git a/src/backend/InvenTree/InvenTree/serializers.py b/src/backend/InvenTree/InvenTree/serializers.py index 6218f0c458..bd348c9b07 100644 --- a/src/backend/InvenTree/InvenTree/serializers.py +++ b/src/backend/InvenTree/InvenTree/serializers.py @@ -768,20 +768,31 @@ class ContentTypeField(serializers.ChoiceField): content_type = None + # First, try to resolve the content type via direct pk value try: - app_label, model = data.split('.') - content_types = ContentType.objects.filter(app_label=app_label, model=model) + content_type_id = int(data) + content_type = ContentType.objects.get_for_id(content_type_id) + except (ValueError, ContentType.DoesNotExist): + content_type = None - if content_types.exists() and content_types.count() == 1: - # Try exact match first - content_type = content_types.first() + try: + if len(data.split('.')) == 2: + app_label, model = data.split('.') + content_types = ContentType.objects.filter( + app_label=app_label, model=model + ) + + if content_types.count() == 1: + # Try exact match first + content_type = content_types.first() else: # Try lookup just on model name - content_types = ContentType.objects.filter(model=model) + content_types = ContentType.objects.filter(model=data) if content_types.exists() and content_types.count() == 1: content_type = content_types.first() - except Exception: + except Exception as e: + print('e:', e) raise ValidationError(_('Invalid content type format')) if content_type is None: diff --git a/src/backend/InvenTree/common/api.py b/src/backend/InvenTree/common/api.py index cc4d3164df..14894d8681 100644 --- a/src/backend/InvenTree/common/api.py +++ b/src/backend/InvenTree/common/api.py @@ -28,6 +28,7 @@ from rest_framework.permissions import IsAdminUser, IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView +import common.filters import common.models import common.serializers import InvenTree.conversion @@ -777,7 +778,7 @@ class ParameterTemplateFilter(FilterSet): """Metaclass options.""" model = common.models.ParameterTemplate - fields = ['model_type', 'name', 'units', 'checkbox', 'enabled'] + fields = ['name', 'units', 'checkbox', 'enabled'] has_choices = rest_filters.BooleanFilter( method='filter_has_choices', label='Has Choice' @@ -799,6 +800,14 @@ class ParameterTemplateFilter(FilterSet): return queryset.filter(Q(units=None) | Q(units='')).distinct() + model_type = rest_filters.CharFilter(method='filter_model_type', label='Model Type') + + def filter_model_type(self, queryset, name, value): + """Filter queryset to include only ParameterTemplates of the given model type.""" + return common.filters.filter_content_type( + queryset, 'model_type', value, allow_null=False + ) + for_model = rest_filters.CharFilter(method='filter_for_model', label='For Model') def filter_for_model(self, queryset, name, value): @@ -807,9 +816,9 @@ class ParameterTemplateFilter(FilterSet): Note that this varies from the 'model_type' filter, in that ParameterTemplates with a blank 'model_type' are considered to apply to all models. """ - return queryset.filter( - Q(model_type__iexact=value) | Q(model_type__isnull=True) | Q(model_type='') - ).distinct() + return common.filters.filter_content_type( + queryset, 'model_type', value, allow_null=True + ) class ParameterTemplateMixin: @@ -839,12 +848,20 @@ class ParameterFilter(FilterSet): """Metaclass options for the filterset.""" model = common.models.Parameter - fields = ['model_type', 'model_id', 'template', 'updated_by'] + fields = ['model_id', 'template', 'updated_by'] enabled = rest_filters.BooleanFilter( label='Template Enabled', field_name='template__enabled' ) + model_type = rest_filters.CharFilter(method='filter_model_type', label='Model Type') + + def filter_model_type(self, queryset, name, value): + """Filter queryset to include only Parameters of the given model type.""" + return common.filters.filter_content_type( + queryset, 'model_type', value, allow_null=False + ) + class ParameterMixin: """Mixin class for Parameter views.""" diff --git a/src/backend/InvenTree/common/filters.py b/src/backend/InvenTree/common/filters.py new file mode 100644 index 0000000000..d83f282d4c --- /dev/null +++ b/src/backend/InvenTree/common/filters.py @@ -0,0 +1,53 @@ +"""Custom API filters for InvenTree.""" + + +def filter_content_type( + queryset, field_name: str, content_type: str | int | None, allow_null: bool = True +): + """Filter a queryset by content type. + + Arguments: + queryset: The queryset to filter. + field_name: The name of the content type field within the current model context. + content_type: The content type to filter by (name or ID). + allow_null: If True, include entries with null content type. + + Returns: + Filtered queryset. + """ + if content_type is None: + return queryset + + from django.contrib.contenttypes.models import ContentType + from django.db.models import Q + + ct = None + + # First, try to resolve the content type via a PK value + try: + content_type_id = int(content_type) + ct = ContentType.objects.get_for_id(content_type_id) + except (ValueError, ContentType.DoesNotExist): + ct = None + + if len(content_type.split('.')) == 2: + # Next, try to resolve the content type via app_label.model_name + try: + app_label, model = content_type.split('.') + ct = ContentType.objects.get(app_label=app_label, model=model) + except ContentType.DoesNotExist: + ct = None + + else: + # Next, try to resolve the content type via a model name + ct = ContentType.objects.filter(model__iexact=content_type).first() + + if ct is None: + raise ValueError(f'Invalid content type: {content_type}') + + q = Q(**{f'{field_name}': ct}) + + if allow_null: + q |= Q(**{f'{field_name}__isnull': True}) + + return queryset.filter(q) diff --git a/src/backend/InvenTree/common/models.py b/src/backend/InvenTree/common/models.py index d050efb9ac..301aeb8612 100644 --- a/src/backend/InvenTree/common/models.py +++ b/src/backend/InvenTree/common/models.py @@ -2643,9 +2643,7 @@ class Parameter( """Check if the user has the required permission for this parameter.""" from InvenTree.models import InvenTreeParameterMixin - model_class = common.validators.parameter_model_class_from_label( - self.model_type - ) + model_class = self.model_type.model_class() if not issubclass(model_class, InvenTreeParameterMixin): raise ValidationError(_('Invalid model type specified for parameter')) diff --git a/src/backend/InvenTree/common/serializers.py b/src/backend/InvenTree/common/serializers.py index d218283e0a..43532206e0 100644 --- a/src/backend/InvenTree/common/serializers.py +++ b/src/backend/InvenTree/common/serializers.py @@ -772,9 +772,7 @@ class ParameterSerializer( # Ensure that the user has permission to modify parameters for the specified model user = self.context.get('request').user - target_model_class = common.validators.parameter_model_class_from_label( - model_type - ) + target_model_class = model_type.model_class() if not issubclass(target_model_class, InvenTreeParameterMixin): raise PermissionDenied(_('Invalid model type specified for parameter')) diff --git a/src/frontend/src/tables/general/ParameterTable.tsx b/src/frontend/src/tables/general/ParameterTable.tsx index 0f043c4071..eb995c5a4f 100644 --- a/src/frontend/src/tables/general/ParameterTable.tsx +++ b/src/frontend/src/tables/general/ParameterTable.tsx @@ -161,6 +161,9 @@ export function ParameterTable({ url: ApiEndpoints.parameter_list, title: t`Add Parameter`, fields: useParameterFields({ modelType, modelId }), + initialData: { + data: '' + }, table: table });