Skip to content

Commit 6312a1b

Browse files
committed
Move validation from reformat function to serializer
1 parent bdeef88 commit 6312a1b

2 files changed

Lines changed: 102 additions & 121 deletions

File tree

app/grandchallenge/components/serializers.py

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -29,41 +29,19 @@
2929

3030
def reformat_serialized_civ_data(*, serialized_civ_data):
3131
"""Takes serialized CIV data and returns list of CIVData objects."""
32-
33-
possible_keys = [
34-
"image",
35-
"value",
36-
"file",
37-
"user_upload",
38-
"upload_session",
39-
("image_name", "user_uploads"),
40-
]
41-
4232
civ_data_objects = []
4333
for civ in serialized_civ_data:
4434
interface = civ["interface"]
4535

4636
keys = set(civ.keys()) - {"interface"}
4737

48-
if not keys:
49-
raise serializers.ValidationError(
50-
f"You must provide at least one of {possible_keys}."
51-
)
52-
elif keys == {"image_name", "user_uploads"}:
38+
if keys == {"image_name", "user_uploads"}:
5339
value = DICOMUploadWithName(
5440
name=civ["image_name"],
5541
user_uploads=civ["user_uploads"],
5642
)
57-
elif len(keys) > 1:
58-
raise serializers.ValidationError(
59-
f"You can only provide one of {possible_keys} for each socket."
60-
)
61-
elif (key := keys.pop()) in ("image_name", "user_uploads"):
62-
raise serializers.ValidationError(
63-
"You must provide 'image_name' and 'user_uploads' together."
64-
)
6543
else:
66-
value = civ[key]
44+
value = civ[keys.pop()]
6745

6846
try:
6947
civ_data_objects.append(
@@ -193,16 +171,15 @@ def __init__(self, *args, **kwargs):
193171
)
194172

195173
def validate(self, attrs):
196-
if "interface" not in attrs:
197-
raise serializers.ValidationError("An interface must be specified")
174+
self._validate_provided_fields(attrs=attrs)
198175

199176
interface = attrs["interface"]
200177

201178
if interface.super_kind == interface.SuperKind.IMAGE:
202179
if interface.is_dicom_image_kind:
203-
self._validate_dicom_image(attrs, interface)
180+
self._validate_dicom_image(attrs=attrs, interface=interface)
204181
else:
205-
self._validate_panimg_image(attrs, interface)
182+
self._validate_panimg_image(attrs=attrs, interface=interface)
206183
elif interface.super_kind == interface.SuperKind.VALUE:
207184
if (
208185
attrs.get("value") is None
@@ -240,7 +217,33 @@ def validate(self, attrs):
240217

241218
return attrs
242219

243-
def _validate_panimg_image(self, attrs, interface):
220+
@staticmethod
221+
def _validate_provided_fields(*, attrs):
222+
if "interface" not in attrs:
223+
raise serializers.ValidationError("An interface must be specified")
224+
225+
possible_keys = [
226+
"image",
227+
"value",
228+
"file",
229+
"user_upload",
230+
"upload_session",
231+
("image_name", "user_uploads"),
232+
]
233+
234+
keys = set(attrs.keys()) - {"interface"}
235+
236+
if not keys:
237+
raise serializers.ValidationError(
238+
f"You must provide at least one of {possible_keys}."
239+
)
240+
elif len(keys) > 1 and keys != {"image_name", "user_uploads"}:
241+
raise serializers.ValidationError(
242+
f"You can only provide one of {possible_keys} for each socket."
243+
)
244+
245+
@staticmethod
246+
def _validate_panimg_image(*, attrs, interface):
244247
if not any(
245248
[
246249
attrs.get("image"),
@@ -252,7 +255,8 @@ def _validate_panimg_image(self, attrs, interface):
252255
f"kind {interface.kind}"
253256
)
254257

255-
def _validate_dicom_image(self, attrs, interface):
258+
@staticmethod
259+
def _validate_dicom_image(*, attrs, interface):
256260
if not (
257261
attrs.get("image")
258262
or (attrs.get("user_uploads") and attrs.get("image_name"))

app/tests/components_tests/test_serializers.py

Lines changed: 68 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
1-
from contextlib import nullcontext
2-
31
import pytest
42
from guardian.shortcuts import assign_perm
5-
from rest_framework import serializers
63

74
from grandchallenge.cases.models import RawImageUploadSession
85
from grandchallenge.components.models import (
@@ -312,34 +309,55 @@ def test_civ_post_value_validation(kind):
312309

313310
@pytest.mark.django_db
314311
@pytest.mark.parametrize("kind", InterfaceKinds.json)
315-
@pytest.mark.parametrize(
316-
"store_in_database, expected_error",
317-
(
318-
(True, "value is required for interface kind {kind}"),
319-
(False, "user_upload or file is required for interface kind {kind}"),
320-
),
321-
)
322-
def test_civ_post_value_or_user_upload_required_validation(
323-
kind, store_in_database, expected_error
324-
):
325-
# setup
312+
def test_civ_post_value_required_validation(kind):
326313
interface = ComponentInterfaceFactory(
327314
kind=kind,
328-
store_in_database=store_in_database,
315+
store_in_database=True,
329316
)
330317

331-
payload = {"interface": interface.slug}
318+
for kwargs in [
319+
{"image": ""},
320+
{"user_upload": ""},
321+
{"upload_session": ""},
322+
{"image_name": "foo", "user_uploads": [""]},
323+
]:
324+
payload = {"interface": interface.slug, **kwargs}
332325

333-
# test
334-
serializer = ComponentInterfaceValuePostSerializer(data=payload)
326+
serializer = ComponentInterfaceValuePostSerializer(data=payload)
335327

336-
# verify
337-
assert not serializer.is_valid()
338-
assert (
339-
expected_error.format(kind=kind)
340-
in serializer.errors["non_field_errors"]
328+
assert not serializer.is_valid()
329+
assert (
330+
f"value is required for interface kind {kind}"
331+
in serializer.errors["non_field_errors"]
332+
)
333+
334+
335+
@pytest.mark.django_db
336+
@pytest.mark.parametrize("kind", InterfaceKinds.json)
337+
def test_civ_post_file_or_user_upload_required_validation(kind):
338+
interface = ComponentInterfaceFactory(
339+
kind=kind,
340+
store_in_database=False,
341341
)
342342

343+
for kwargs in [
344+
{"image": ""},
345+
{"value": "foo"},
346+
{"upload_session": ""},
347+
{"image_name": "foo", "user_uploads": [""]},
348+
]:
349+
payload = {"interface": interface.slug, **kwargs}
350+
351+
# test
352+
serializer = ComponentInterfaceValuePostSerializer(data=payload)
353+
354+
# verify
355+
assert not serializer.is_valid()
356+
assert (
357+
f"user_upload or file is required for interface kind {kind}"
358+
in serializer.errors["non_field_errors"]
359+
)
360+
343361

344362
@pytest.mark.django_db
345363
@pytest.mark.parametrize("kind", InterfaceKinds.json)
@@ -398,32 +416,43 @@ def test_civ_post_image_or_upload_required_validation(kind):
398416
# setup
399417
interface = ComponentInterfaceFactory(kind=kind)
400418

401-
payload = {"interface": interface.slug}
419+
for kwargs in [
420+
{"value": "foo"},
421+
{"upload_session": ""},
422+
{"image_name": "foo", "user_uploads": [""]},
423+
]:
424+
payload = {"interface": interface.slug, **kwargs}
402425

403-
# test
404-
serializer = ComponentInterfaceValuePostSerializer(data=payload)
426+
# test
427+
serializer = ComponentInterfaceValuePostSerializer(data=payload)
405428

406-
# verify
407-
assert not serializer.is_valid()
408-
assert (
409-
f"upload_session or image are required for interface kind {kind}"
410-
in serializer.errors["non_field_errors"]
411-
)
429+
# verify
430+
assert not serializer.is_valid()
431+
assert (
432+
f"upload_session or image are required for interface kind {kind}"
433+
in serializer.errors["non_field_errors"]
434+
)
412435

413436

414437
@pytest.mark.django_db
415438
@pytest.mark.parametrize("kind,", InterfaceKinds.dicom)
416439
def test_civ_post_dicom_image_or_upload_required_validation(kind, rf):
417440
interface = ComponentInterfaceFactory(kind=kind)
418-
payload = {"interface": interface.slug}
419441

420-
serializer = ComponentInterfaceValuePostSerializer(data=payload)
442+
for kwargs in [
443+
{"value": "foo"},
444+
{"user_upload": ""},
445+
{"upload_session": ""},
446+
]:
447+
payload = {"interface": interface.slug, **kwargs}
421448

422-
assert not serializer.is_valid()
423-
assert (
424-
f"either user_uploads with image_name, or image are required for interface kind {kind}"
425-
in serializer.errors["non_field_errors"]
426-
)
449+
serializer = ComponentInterfaceValuePostSerializer(data=payload)
450+
451+
assert not serializer.is_valid()
452+
assert (
453+
f"either user_uploads with image_name, or image are required for interface kind {kind}"
454+
in serializer.errors["non_field_errors"]
455+
)
427456

428457
payload = {"interface": interface.slug, "image_name": "foobar"}
429458

@@ -733,55 +762,3 @@ def test_reformat_serialized_civ_data():
733762
assert civ_data_objects[3].dicom_upload_with_name.name == "a dcm"
734763
assert civ_data_objects[3].dicom_upload_with_name.user_uploads == uploads
735764
assert civ_data_objects[4].user_upload == uploads[0]
736-
737-
738-
def test_reformat_serialized_civ_data_invalid_no_keys():
739-
data = [{"interface": ""}]
740-
741-
with pytest.raises(serializers.ValidationError) as e:
742-
reformat_serialized_civ_data(serialized_civ_data=data)
743-
744-
assert (
745-
"You must provide at least one of ['image', 'value', 'file', "
746-
"'user_upload', 'upload_session', ('image_name', 'user_uploads')]."
747-
) in str(e.value)
748-
749-
750-
@pytest.mark.django_db
751-
def test_reformat_serialized_civ_data_invalid_multiple_keys():
752-
data = [{"interface": "some_socket_slug", "value": "foo", "image": "foo"}]
753-
754-
with pytest.raises(serializers.ValidationError) as e:
755-
reformat_serialized_civ_data(serialized_civ_data=data)
756-
757-
assert (
758-
"You can only provide one of ['image', 'value', 'file', "
759-
"'user_upload', 'upload_session', ('image_name', 'user_uploads')] for each socket."
760-
) in str(e.value)
761-
762-
# Using multiple keys (image_name and user_uploads) is necessary for DICOM
763-
ci_dcm = ComponentInterfaceFactory(
764-
kind=InterfaceKindChoices.DICOM_IMAGE_SET
765-
)
766-
data = [
767-
{"interface": ci_dcm, "image_name": "foo", "user_uploads": ["foo"]}
768-
]
769-
770-
with nullcontext():
771-
reformat_serialized_civ_data(serialized_civ_data=data)
772-
773-
774-
@pytest.mark.django_db
775-
def test_reformat_serialized_civ_data_invalid_dicom_keys():
776-
ci_dcm = ComponentInterfaceFactory(
777-
kind=InterfaceKindChoices.DICOM_IMAGE_SET
778-
)
779-
for key in ("image_name", "user_uploads"):
780-
data = [{"interface": ci_dcm, key: "foo"}]
781-
782-
with pytest.raises(serializers.ValidationError) as e:
783-
reformat_serialized_civ_data(serialized_civ_data=data)
784-
785-
assert (
786-
"You must provide 'image_name' and 'user_uploads' together."
787-
) in str(e.value)

0 commit comments

Comments
 (0)