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:
@@ -768,20 +768,31 @@ class ContentTypeField(serializers.ChoiceField):
|
|||||||
|
|
||||||
content_type = None
|
content_type = None
|
||||||
|
|
||||||
|
# First, try to resolve the content type via direct pk value
|
||||||
try:
|
try:
|
||||||
app_label, model = data.split('.')
|
content_type_id = int(data)
|
||||||
content_types = ContentType.objects.filter(app_label=app_label, model=model)
|
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:
|
||||||
# Try exact match first
|
if len(data.split('.')) == 2:
|
||||||
content_type = content_types.first()
|
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:
|
else:
|
||||||
# Try lookup just on model name
|
# 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:
|
if content_types.exists() and content_types.count() == 1:
|
||||||
content_type = content_types.first()
|
content_type = content_types.first()
|
||||||
|
|
||||||
except Exception:
|
except Exception as e:
|
||||||
|
print('e:', e)
|
||||||
raise ValidationError(_('Invalid content type format'))
|
raise ValidationError(_('Invalid content type format'))
|
||||||
|
|
||||||
if content_type is None:
|
if content_type is None:
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ from rest_framework.permissions import IsAdminUser, IsAuthenticated
|
|||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
|
import common.filters
|
||||||
import common.models
|
import common.models
|
||||||
import common.serializers
|
import common.serializers
|
||||||
import InvenTree.conversion
|
import InvenTree.conversion
|
||||||
@@ -777,7 +778,7 @@ class ParameterTemplateFilter(FilterSet):
|
|||||||
"""Metaclass options."""
|
"""Metaclass options."""
|
||||||
|
|
||||||
model = common.models.ParameterTemplate
|
model = common.models.ParameterTemplate
|
||||||
fields = ['model_type', 'name', 'units', 'checkbox', 'enabled']
|
fields = ['name', 'units', 'checkbox', 'enabled']
|
||||||
|
|
||||||
has_choices = rest_filters.BooleanFilter(
|
has_choices = rest_filters.BooleanFilter(
|
||||||
method='filter_has_choices', label='Has Choice'
|
method='filter_has_choices', label='Has Choice'
|
||||||
@@ -799,6 +800,14 @@ class ParameterTemplateFilter(FilterSet):
|
|||||||
|
|
||||||
return queryset.filter(Q(units=None) | Q(units='')).distinct()
|
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')
|
for_model = rest_filters.CharFilter(method='filter_for_model', label='For Model')
|
||||||
|
|
||||||
def filter_for_model(self, queryset, name, value):
|
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
|
Note that this varies from the 'model_type' filter, in that ParameterTemplates
|
||||||
with a blank 'model_type' are considered to apply to all models.
|
with a blank 'model_type' are considered to apply to all models.
|
||||||
"""
|
"""
|
||||||
return queryset.filter(
|
return common.filters.filter_content_type(
|
||||||
Q(model_type__iexact=value) | Q(model_type__isnull=True) | Q(model_type='')
|
queryset, 'model_type', value, allow_null=True
|
||||||
).distinct()
|
)
|
||||||
|
|
||||||
|
|
||||||
class ParameterTemplateMixin:
|
class ParameterTemplateMixin:
|
||||||
@@ -839,12 +848,20 @@ class ParameterFilter(FilterSet):
|
|||||||
"""Metaclass options for the filterset."""
|
"""Metaclass options for the filterset."""
|
||||||
|
|
||||||
model = common.models.Parameter
|
model = common.models.Parameter
|
||||||
fields = ['model_type', 'model_id', 'template', 'updated_by']
|
fields = ['model_id', 'template', 'updated_by']
|
||||||
|
|
||||||
enabled = rest_filters.BooleanFilter(
|
enabled = rest_filters.BooleanFilter(
|
||||||
label='Template Enabled', field_name='template__enabled'
|
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:
|
class ParameterMixin:
|
||||||
"""Mixin class for Parameter views."""
|
"""Mixin class for Parameter views."""
|
||||||
|
|||||||
53
src/backend/InvenTree/common/filters.py
Normal file
53
src/backend/InvenTree/common/filters.py
Normal 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)
|
||||||
@@ -2643,9 +2643,7 @@ class Parameter(
|
|||||||
"""Check if the user has the required permission for this parameter."""
|
"""Check if the user has the required permission for this parameter."""
|
||||||
from InvenTree.models import InvenTreeParameterMixin
|
from InvenTree.models import InvenTreeParameterMixin
|
||||||
|
|
||||||
model_class = common.validators.parameter_model_class_from_label(
|
model_class = self.model_type.model_class()
|
||||||
self.model_type
|
|
||||||
)
|
|
||||||
|
|
||||||
if not issubclass(model_class, InvenTreeParameterMixin):
|
if not issubclass(model_class, InvenTreeParameterMixin):
|
||||||
raise ValidationError(_('Invalid model type specified for parameter'))
|
raise ValidationError(_('Invalid model type specified for parameter'))
|
||||||
|
|||||||
@@ -772,9 +772,7 @@ class ParameterSerializer(
|
|||||||
# Ensure that the user has permission to modify parameters for the specified model
|
# Ensure that the user has permission to modify parameters for the specified model
|
||||||
user = self.context.get('request').user
|
user = self.context.get('request').user
|
||||||
|
|
||||||
target_model_class = common.validators.parameter_model_class_from_label(
|
target_model_class = model_type.model_class()
|
||||||
model_type
|
|
||||||
)
|
|
||||||
|
|
||||||
if not issubclass(target_model_class, InvenTreeParameterMixin):
|
if not issubclass(target_model_class, InvenTreeParameterMixin):
|
||||||
raise PermissionDenied(_('Invalid model type specified for parameter'))
|
raise PermissionDenied(_('Invalid model type specified for parameter'))
|
||||||
|
|||||||
@@ -161,6 +161,9 @@ export function ParameterTable({
|
|||||||
url: ApiEndpoints.parameter_list,
|
url: ApiEndpoints.parameter_list,
|
||||||
title: t`Add Parameter`,
|
title: t`Add Parameter`,
|
||||||
fields: useParameterFields({ modelType, modelId }),
|
fields: useParameterFields({ modelType, modelId }),
|
||||||
|
initialData: {
|
||||||
|
data: ''
|
||||||
|
},
|
||||||
table: table
|
table: table
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user