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

API endpoints for Parameter instances

This commit is contained in:
Oliver Walters
2025-11-10 11:57:33 +00:00
parent c1cd327fc2
commit 5f067e2c50
4 changed files with 185 additions and 9 deletions

View File

@@ -44,6 +44,7 @@ from InvenTree.mixins import (
CreateAPI, CreateAPI,
ListAPI, ListAPI,
ListCreateAPI, ListCreateAPI,
OutputOptionsMixin,
RetrieveAPI, RetrieveAPI,
RetrieveDestroyAPI, RetrieveDestroyAPI,
RetrieveUpdateAPI, RetrieveUpdateAPI,
@@ -708,13 +709,17 @@ class AttachmentFilter(FilterSet):
return queryset.filter(Q(attachment=None) | Q(attachment='')).distinct() return queryset.filter(Q(attachment=None) | Q(attachment='')).distinct()
class AttachmentList(BulkDeleteMixin, ListCreateAPI): class AttachmentMixin:
"""List API endpoint for Attachment objects.""" """Mixin class for Attachment views."""
queryset = common.models.Attachment.objects.all() queryset = common.models.Attachment.objects.all()
serializer_class = common.serializers.AttachmentSerializer serializer_class = common.serializers.AttachmentSerializer
permission_classes = [IsAuthenticatedOrReadScope] permission_classes = [IsAuthenticatedOrReadScope]
class AttachmentList(AttachmentMixin, BulkDeleteMixin, ListCreateAPI):
"""List API endpoint for Attachment objects."""
filter_backends = SEARCH_ORDER_FILTER filter_backends = SEARCH_ORDER_FILTER
filterset_class = AttachmentFilter filterset_class = AttachmentFilter
@@ -746,13 +751,9 @@ class AttachmentList(BulkDeleteMixin, ListCreateAPI):
) )
class AttachmentDetail(RetrieveUpdateDestroyAPI): class AttachmentDetail(AttachmentMixin, RetrieveUpdateDestroyAPI):
"""Detail API endpoint for Attachment objects.""" """Detail API endpoint for Attachment objects."""
queryset = common.models.Attachment.objects.all()
serializer_class = common.serializers.AttachmentSerializer
permission_classes = [IsAuthenticatedOrReadScope]
def destroy(self, request, *args, **kwargs): def destroy(self, request, *args, **kwargs):
"""Check user permissions before deleting an attachment.""" """Check user permissions before deleting an attachment."""
attachment = self.get_object() attachment = self.get_object()
@@ -827,6 +828,58 @@ class ParameterTemplateDetail(ParameterTemplateMixin, RetrieveUpdateDestroyAPI):
"""Detail view for a ParameterTemplate object.""" """Detail view for a ParameterTemplate object."""
class ParameterFilter(FilterSet):
"""Custom filters for the ParameterList API endpoint."""
class Meta:
"""Metaclass options for the filterset."""
model = common.models.Parameter
fields = ['model_type', 'model_id', 'template', 'updated_by']
class ParameterMixin:
"""Mixin class for Parameter views."""
queryset = common.models.Parameter.objects.all()
serializer_class = common.serializers.ParameterSerializer
permission_classes = [IsAuthenticatedOrReadScope]
class ParameterList(
OutputOptionsMixin,
ParameterMixin,
BulkDeleteMixin,
DataExportViewMixin,
ListCreateAPI,
):
"""List API endpoint for Parameter objects."""
filterset_class = ParameterFilter
filter_backends = SEARCH_ORDER_FILTER
ordering_fields = ['name', 'data', 'units', 'template', 'updated', 'updated_by']
ordering_field_aliases = {
'name': 'template__name',
'units': 'template__units',
'data': ['data_numeric', 'data'],
}
search_fields = [
'data',
'template__name',
'template__description',
'template__units',
]
unique_create_fields = ['model_type', 'model_id', 'template']
class ParameterDetail(ParameterMixin, RetrieveUpdateDestroyAPI):
"""Detail API endpoint for Parameter objects."""
@method_decorator(cache_control(public=True, max_age=86400), name='dispatch') @method_decorator(cache_control(public=True, max_age=86400), name='dispatch')
class IconList(ListAPI): class IconList(ListAPI):
"""List view for available icon packages.""" """List view for available icon packages."""
@@ -1082,7 +1135,14 @@ common_api_urls = [
name='api-parameter-template-list', name='api-parameter-template-list',
), ),
]), ]),
) ),
path(
'<int:pk>/',
include([
path('', ParameterDetail.as_view(), name='api-parameter-detail')
]),
),
path('', ParameterList.as_view(), name='api-parameter-list'),
]), ]),
), ),
path( path(

View File

@@ -2621,6 +2621,19 @@ class Parameter(
if math.isnan(self.data_numeric) or math.isinf(self.data_numeric): if math.isnan(self.data_numeric) or math.isinf(self.data_numeric):
self.data_numeric = None self.data_numeric = None
def check_permission(self, permission, user):
"""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
)
if not issubclass(model_class, InvenTreeParameterMixin):
raise ValidationError(_('Invalid model type specified for parameter'))
return model_class.check_related_permission(permission, user)
model_type = models.CharField( model_type = models.CharField(
max_length=100, max_length=100,
default='', default='',

View File

@@ -22,9 +22,11 @@ from InvenTree.helpers import get_objectreference
from InvenTree.helpers_model import construct_absolute_url from InvenTree.helpers_model import construct_absolute_url
from InvenTree.mixins import DataImportExportSerializerMixin from InvenTree.mixins import DataImportExportSerializerMixin
from InvenTree.serializers import ( from InvenTree.serializers import (
FilterableSerializerMixin,
InvenTreeAttachmentSerializerField, InvenTreeAttachmentSerializerField,
InvenTreeImageSerializerField, InvenTreeImageSerializerField,
InvenTreeModelSerializer, InvenTreeModelSerializer,
enable_filter,
) )
from plugin import registry as plugin_registry from plugin import registry as plugin_registry
from users.serializers import OwnerSerializer, UserSerializer from users.serializers import OwnerSerializer, UserSerializer
@@ -692,7 +694,7 @@ class AttachmentSerializer(InvenTreeModelSerializer):
# Check that the user has the required permissions to attach files to the target model # Check that the user has the required permissions to attach files to the target model
if not target_model_class.check_related_permission('change', user): if not target_model_class.check_related_permission('change', user):
raise PermissionDenied(_(permission_error_msg)) raise PermissionDenied(permission_error_msg)
return super().save(**kwargs) return super().save(**kwargs)
@@ -737,6 +739,95 @@ class ParameterTemplateSerializer(
) )
class ParameterSerializer(
FilterableSerializerMixin, DataImportExportSerializerMixin, InvenTreeModelSerializer
):
"""Serializer for the Parameter model."""
class Meta:
"""Meta options for ParameterSerializer."""
model = common_models.Parameter
fields = [
'pk',
'template',
'model_type',
'model_id',
'data',
'data_numeric',
'note',
'updated',
'updated_by',
'template_detail',
'updated_by_detail',
]
read_only_fields = ['updated', 'updated_by']
def __init__(self, *args, **kwargs):
"""Override the model_type field to provide dynamic choices."""
super().__init__(*args, **kwargs)
if len(self.fields['model_type'].choices) == 0:
self.fields[
'model_type'
].choices = common.validators.parameter_model_options()
def save(self, **kwargs):
"""Save the Parameter instance."""
from InvenTree.models import InvenTreeParameterMixin
from users.permissions import check_user_permission
model_type = self.validated_data.get('model_type', None)
if model_type is None and self.instance:
model_type = self.instance.model_type
# 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
)
if not issubclass(target_model_class, InvenTreeParameterMixin):
raise PermissionDenied(_('Invalid model type specified for parameter'))
permission_error_msg = _(
'User does not have permission to create or edit parameters for this model'
)
if not check_user_permission(user, target_model_class, 'change'):
raise PermissionDenied(permission_error_msg)
if not target_model_class.check_related_permission('change', user):
raise PermissionDenied(permission_error_msg)
instance = super().save(**kwargs)
instance.updated_by = user
instance.save()
return instance
# Note: The choices are overridden at run-time on class initialization
model_type = serializers.ChoiceField(
label=_('Model Type'),
default='',
choices=common.validators.parameter_model_options(),
required=False,
allow_blank=True,
allow_null=True,
)
updated_by_detail = enable_filter(
UserSerializer(source='updated_by', read_only=True, many=False), True
)
template_detail = enable_filter(
ParameterTemplateSerializer(source='template', read_only=True, many=False), True
)
class IconSerializer(serializers.Serializer): class IconSerializer(serializers.Serializer):
"""Serializer for an icon.""" """Serializer for an icon."""

View File

@@ -37,6 +37,18 @@ def parameter_template_model_options():
return [('', _('Any model type')), *parameter_model_options()] return [('', _('Any model type')), *parameter_model_options()]
def parameter_model_class_from_label(label: str):
"""Return the model class for the given label."""
if not label:
raise ValidationError(_('No parameter model type provided'))
for model in parameter_model_types():
if model.__name__.lower() == label.lower():
return model
raise ValidationError(_('Invalid parameter model type') + f": '{label}'")
def validate_parameter_model_type(value: str): def validate_parameter_model_type(value: str):
"""Ensure that the provided parameter model is valid.""" """Ensure that the provided parameter model is valid."""
model_names = [el[0] for el in parameter_model_options()] model_names = [el[0] for el in parameter_model_options()]