mirror of
https://github.com/inventree/InvenTree.git
synced 2025-12-16 01:08:12 +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
|
||||
|
||||
# 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:
|
||||
|
||||
@@ -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."""
|
||||
|
||||
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."""
|
||||
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'))
|
||||
|
||||
@@ -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'))
|
||||
|
||||
@@ -161,6 +161,9 @@ export function ParameterTable({
|
||||
url: ApiEndpoints.parameter_list,
|
||||
title: t`Add Parameter`,
|
||||
fields: useParameterFields({ modelType, modelId }),
|
||||
initialData: {
|
||||
data: ''
|
||||
},
|
||||
table: table
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user