mirror of
https://github.com/inventree/InvenTree.git
synced 2026-09-27 14:16:02 +00:00
Fix validation of dependent serializer fields (#12871)
This commit is contained in:
@@ -693,7 +693,7 @@ class DependentField(serializers.Field):
|
||||
"""This method tries to convert the data to an internal representation based on the defined to_internal_value method on the child."""
|
||||
self.get_child()
|
||||
if self.child:
|
||||
return self.child.to_internal_value(data)
|
||||
return self.child.run_validation(data)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
@@ -3,21 +3,110 @@
|
||||
import sys
|
||||
import threading
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from types import SimpleNamespace
|
||||
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import SimpleTestCase
|
||||
from django.urls import path, reverse
|
||||
|
||||
from rest_framework import serializers
|
||||
from rest_framework.serializers import SerializerMethodField
|
||||
|
||||
import InvenTree.serializers
|
||||
from InvenTree.mixins import ListCreateAPI, OutputOptionsMixin
|
||||
from InvenTree.serializers import OptionalField
|
||||
from InvenTree.serializers import DependentField, OptionalField
|
||||
from InvenTree.unit_test import InvenTreeAPITestCase
|
||||
from InvenTree.urls import backendpatterns
|
||||
from part.models import Part
|
||||
|
||||
|
||||
class DependentFieldTests(SimpleTestCase):
|
||||
"""Test validation of dynamically selected child fields and serializers."""
|
||||
|
||||
def get_serializer(self, child, value):
|
||||
"""Bind a dependent child using the same data as the request."""
|
||||
|
||||
class ParentSerializer(serializers.Serializer):
|
||||
kind = serializers.CharField()
|
||||
options = DependentField(
|
||||
depends_on=['kind'], field_serializer='get_options'
|
||||
)
|
||||
|
||||
def get_options(self, fields):
|
||||
return child
|
||||
|
||||
data = {'kind': 'test', 'options': value}
|
||||
return ParentSerializer(
|
||||
data=data, context={'request': SimpleNamespace(data=data)}
|
||||
)
|
||||
|
||||
def test_child_field_validators(self):
|
||||
"""Run validators on the selected child field."""
|
||||
serializer = self.get_serializer(serializers.IntegerField(min_value=1), '0')
|
||||
|
||||
self.assertFalse(serializer.is_valid())
|
||||
self.assertEqual(serializer.errors['options'][0].code, 'min_value')
|
||||
|
||||
def test_child_serializer_validation(self):
|
||||
"""Propagate object-level validation errors under the dependent field."""
|
||||
|
||||
class OptionsSerializer(serializers.Serializer):
|
||||
copies = serializers.IntegerField()
|
||||
|
||||
def validate(self, attrs):
|
||||
raise serializers.ValidationError('Invalid options', code='options')
|
||||
|
||||
serializer = self.get_serializer(OptionsSerializer(), {'copies': 1})
|
||||
|
||||
with self.assertRaises(serializers.ValidationError) as error:
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
detail = error.exception.detail['options']['non_field_errors'][0]
|
||||
self.assertEqual(detail, 'Invalid options')
|
||||
self.assertEqual(detail.code, 'options')
|
||||
|
||||
def test_valid_child_field(self):
|
||||
"""Preserve conversion of valid child field input."""
|
||||
serializer = self.get_serializer(serializers.IntegerField(min_value=1), '2')
|
||||
|
||||
self.assertTrue(serializer.is_valid(), serializer.errors)
|
||||
self.assertEqual(serializer.validated_data['options'], 2)
|
||||
|
||||
def test_valid_child_serializer(self):
|
||||
"""Use the data returned by the child serializer's validate method."""
|
||||
|
||||
class OptionsSerializer(serializers.Serializer):
|
||||
copies = serializers.IntegerField()
|
||||
|
||||
def validate(self, attrs):
|
||||
attrs['copies'] *= 2
|
||||
return attrs
|
||||
|
||||
serializer = self.get_serializer(OptionsSerializer(), {'copies': '2'})
|
||||
|
||||
self.assertTrue(serializer.is_valid(), serializer.errors)
|
||||
self.assertEqual(serializer.validated_data['options'], {'copies': 4})
|
||||
|
||||
def test_child_conversion_error(self):
|
||||
"""Preserve ordinary child field conversion errors."""
|
||||
serializer = self.get_serializer(serializers.IntegerField(), 'invalid')
|
||||
|
||||
self.assertFalse(serializer.is_valid())
|
||||
self.assertEqual(serializer.errors['options'][0].code, 'invalid')
|
||||
|
||||
def test_child_serializer_field_error(self):
|
||||
"""Preserve nested field errors from a child serializer."""
|
||||
|
||||
class OptionsSerializer(serializers.Serializer):
|
||||
copies = serializers.IntegerField()
|
||||
|
||||
serializer = self.get_serializer(OptionsSerializer(), {'copies': 'invalid'})
|
||||
|
||||
self.assertFalse(serializer.is_valid())
|
||||
self.assertEqual(serializer.errors['options']['copies'][0].code, 'invalid')
|
||||
|
||||
|
||||
class SampleSerializer(
|
||||
InvenTree.serializers.FilterableSerializerMixin,
|
||||
InvenTree.serializers.InvenTreeModelSerializer,
|
||||
|
||||
@@ -9,9 +9,12 @@ from django.urls import reverse
|
||||
|
||||
from pdfminer.high_level import extract_text
|
||||
from PIL import Image
|
||||
from rest_framework import serializers
|
||||
|
||||
from InvenTree.config import get_testfolder_dir
|
||||
from InvenTree.unit_test import InvenTreeAPITestCase
|
||||
from machine import registry as machine_registry
|
||||
from machine.models import MachineConfig
|
||||
from part.models import Part
|
||||
from plugin import InvenTreePlugin, PluginMixinEnum, registry
|
||||
from plugin.base.label.mixins import LabelPrintingMixin
|
||||
@@ -269,6 +272,57 @@ class LabelMixinTests(PrintTestMixins, InvenTreeAPITestCase):
|
||||
print_label.call_args.kwargs['printing_options'], {'amount': 13}
|
||||
)
|
||||
|
||||
def test_machine_driver_options_validate_before_print_task(self):
|
||||
"""Test that machine driver option validation runs before the print task."""
|
||||
self.ensurePluginsLoaded()
|
||||
apps.get_app_config('report').create_default_labels()
|
||||
machine_registry.initialize()
|
||||
registry.set_plugin_state('label-printer-test-plugin', True)
|
||||
|
||||
machine_config = MachineConfig.objects.create(
|
||||
machine_type='label-printer',
|
||||
driver='test-label-printer-api',
|
||||
name='Test label printer',
|
||||
active=True,
|
||||
)
|
||||
machine = machine_registry.get_machine(str(machine_config.pk))
|
||||
self.assertIsNotNone(machine)
|
||||
|
||||
template = LabelTemplate.objects.filter(enabled=True, model_type='part').first()
|
||||
assert template
|
||||
part = Part.objects.first()
|
||||
assert part
|
||||
|
||||
class RejectingOptionsSerializer(serializers.Serializer):
|
||||
copies = serializers.IntegerField(required=False, default=1)
|
||||
|
||||
def validate(self, attrs):
|
||||
raise serializers.ValidationError('preflight failed')
|
||||
|
||||
driver = machine.driver
|
||||
with (
|
||||
mock.patch('InvenTree.tasks.offload_task') as offload_task,
|
||||
mock.patch.object(
|
||||
driver,
|
||||
'get_printing_options_serializer',
|
||||
side_effect=lambda *args, **kwargs: RejectingOptionsSerializer(),
|
||||
),
|
||||
):
|
||||
response = self.post(
|
||||
self.printing_url,
|
||||
{
|
||||
'plugin': 'inventreelabelmachine',
|
||||
'template': template.pk,
|
||||
'items': [part.pk],
|
||||
'machine': str(machine_config.pk),
|
||||
'driver_options': {'copies': 1},
|
||||
},
|
||||
expected_code=400,
|
||||
)
|
||||
|
||||
offload_task.assert_not_called()
|
||||
self.assertIn('preflight failed', str(response.data))
|
||||
|
||||
def test_printing_endpoints(self):
|
||||
"""Cover the endpoints not covered by `test_printing_process`."""
|
||||
# Activate the label components
|
||||
|
||||
Reference in New Issue
Block a user