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:
@@ -44,6 +44,7 @@ from InvenTree.mixins import (
|
||||
CreateAPI,
|
||||
ListAPI,
|
||||
ListCreateAPI,
|
||||
OutputOptionsMixin,
|
||||
RetrieveAPI,
|
||||
RetrieveDestroyAPI,
|
||||
RetrieveUpdateAPI,
|
||||
@@ -708,13 +709,17 @@ class AttachmentFilter(FilterSet):
|
||||
return queryset.filter(Q(attachment=None) | Q(attachment='')).distinct()
|
||||
|
||||
|
||||
class AttachmentList(BulkDeleteMixin, ListCreateAPI):
|
||||
"""List API endpoint for Attachment objects."""
|
||||
class AttachmentMixin:
|
||||
"""Mixin class for Attachment views."""
|
||||
|
||||
queryset = common.models.Attachment.objects.all()
|
||||
serializer_class = common.serializers.AttachmentSerializer
|
||||
permission_classes = [IsAuthenticatedOrReadScope]
|
||||
|
||||
|
||||
class AttachmentList(AttachmentMixin, BulkDeleteMixin, ListCreateAPI):
|
||||
"""List API endpoint for Attachment objects."""
|
||||
|
||||
filter_backends = SEARCH_ORDER_FILTER
|
||||
filterset_class = AttachmentFilter
|
||||
|
||||
@@ -746,13 +751,9 @@ class AttachmentList(BulkDeleteMixin, ListCreateAPI):
|
||||
)
|
||||
|
||||
|
||||
class AttachmentDetail(RetrieveUpdateDestroyAPI):
|
||||
class AttachmentDetail(AttachmentMixin, RetrieveUpdateDestroyAPI):
|
||||
"""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):
|
||||
"""Check user permissions before deleting an attachment."""
|
||||
attachment = self.get_object()
|
||||
@@ -827,6 +828,58 @@ class ParameterTemplateDetail(ParameterTemplateMixin, RetrieveUpdateDestroyAPI):
|
||||
"""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')
|
||||
class IconList(ListAPI):
|
||||
"""List view for available icon packages."""
|
||||
@@ -1082,7 +1135,14 @@ common_api_urls = [
|
||||
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(
|
||||
|
||||
@@ -2621,6 +2621,19 @@ class Parameter(
|
||||
if math.isnan(self.data_numeric) or math.isinf(self.data_numeric):
|
||||
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(
|
||||
max_length=100,
|
||||
default='',
|
||||
|
||||
@@ -22,9 +22,11 @@ from InvenTree.helpers import get_objectreference
|
||||
from InvenTree.helpers_model import construct_absolute_url
|
||||
from InvenTree.mixins import DataImportExportSerializerMixin
|
||||
from InvenTree.serializers import (
|
||||
FilterableSerializerMixin,
|
||||
InvenTreeAttachmentSerializerField,
|
||||
InvenTreeImageSerializerField,
|
||||
InvenTreeModelSerializer,
|
||||
enable_filter,
|
||||
)
|
||||
from plugin import registry as plugin_registry
|
||||
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
|
||||
if not target_model_class.check_related_permission('change', user):
|
||||
raise PermissionDenied(_(permission_error_msg))
|
||||
raise PermissionDenied(permission_error_msg)
|
||||
|
||||
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):
|
||||
"""Serializer for an icon."""
|
||||
|
||||
|
||||
@@ -37,6 +37,18 @@ def parameter_template_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):
|
||||
"""Ensure that the provided parameter model is valid."""
|
||||
model_names = [el[0] for el in parameter_model_options()]
|
||||
|
||||
Reference in New Issue
Block a user