2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-12-16 09:18:10 +00:00

Refactor API filtering for endpoints

- Specify ContentType by ID, model or app label
This commit is contained in:
Oliver Walters
2025-11-24 23:03:31 +00:00
parent ccc3997518
commit b6c38b91e1
6 changed files with 98 additions and 18 deletions

View File

@@ -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:
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:

View File

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

View File

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

View File

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

View File

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

View File

@@ -161,6 +161,9 @@ export function ParameterTable({
url: ApiEndpoints.parameter_list,
title: t`Add Parameter`,
fields: useParameterFields({ modelType, modelId }),
initialData: {
data: ''
},
table: table
});