Esempio n. 1
0
    def test_retrieve(self):
        """
        Confirm that existing instances of models w/ ArrayModelFields can
        still be retrieved and serialized correctly
        """

        # Set up the initial data
        class TestSerializer(rmd_ser.DjongoModelSerializer):
            class Meta:
                model = ArrayContainerModel
                fields = '__all__'

        embed_data_1 = {'int_field': 1234, 'char_field': 'foo'}

        embed_data_2 = {'int_field': 4321, 'char_field': 'bar'}

        embed_list = [EmbedModel(**embed_data_1), EmbedModel(**embed_data_2)]

        # Attempt to serialize an instance of the model using the data above
        instance = ArrayContainerModel.objects.create(embed_list=embed_list)
        serializer = TestSerializer(instance)

        expected_data = {
            '_id': str(instance._id),
            'embed_list':
            [OrderedDict(embed_data_1),
             OrderedDict(embed_data_2)]
        }

        self.assertDictEqual(expected_data, serializer.data)
    def test_null_retrieve_filled(self):
        """
        Test whether nullable lists are possible
        """

        # Set up the initial data
        class TestSerializer(rmd_ser.DjongoModelSerializer):
            class Meta:
                model = NullArrayContainerModel
                fields = '__all__'

        embed_data_1 = {'int_field': 1234, 'char_field': 'foo'}

        embed_data_2 = {'int_field': 4321, 'char_field': 'bar'}

        embed_list = [EmbedModel(**embed_data_1), EmbedModel(**embed_data_2)]

        # Attempt to serialize an instance of the model using the data above
        instance = NullArrayContainerModel.objects.create(
            nullable_list=embed_list)
        serializer = TestSerializer(instance)

        expected_data = {
            '_id': str(instance._id),
            'nullable_list': [embed_data_1, embed_data_2]
        }

        expected_str = format_dict(expected_data)
        observed_str = format_dict(serializer.data)

        assert observed_str == expected_str
    def embed_instance(self):
        embed_data = {
            'int_field': 1234,
            'char_field': 'Embed'
        }

        embed_instance = EmbedModel(**embed_data)

        return embed_instance
    def test_null_update_filled(self):
        """
        Confirm that existing instances of models w/ ArrayModelFields
        can still be updated when provided with new raw data
        """
        # Set up the initial data
        embed_data_1 = {'int_field': 1234, 'char_field': 'foo'}

        embed_data_2 = {'int_field': 4321, 'char_field': 'bar'}

        embed_list = [EmbedModel(**embed_data_1), EmbedModel(**embed_data_2)]

        initial_data = {'embed_list': embed_list}

        instance = ArrayContainerModel.objects.create(**initial_data)

        initial_data.update({'pk': instance.pk})

        # Attempt to update the instance above
        class TestSerializer(rmd_ser.DjongoModelSerializer):
            class Meta:
                model = NullArrayContainerModel
                fields = '__all__'

        embed_data_1.update({'char_field': 'baz'})

        new_data = {
            'nullable_list': [embed_data_1, embed_data_2, embed_data_1]
        }

        serializer = TestSerializer(instance, data=new_data)

        # Confirm that the update is valid
        assert serializer.is_valid(), serializer.errors

        # Confirm that the serializer saves the updated instance correctly
        serializer.save()
        assert instance.pk == initial_data['pk']
        assert instance.nullable_list[0].int_field == embed_data_1['int_field']
        assert instance.nullable_list[0].char_field == embed_data_1[
            'char_field']
        assert instance.nullable_list[1].int_field == embed_data_2['int_field']
        assert instance.nullable_list[2].char_field == embed_data_1[
            'char_field']
    def test_null_update_empty(self):
        """
        Confirm that existing instances of models w/ ArrayModelFields
        can still be updated when provided with new raw data
        """
        # Set up the initial data
        embed_data_1 = {'int_field': 1234, 'char_field': 'foo'}

        embed_data_2 = {'int_field': 4321, 'char_field': 'bar'}

        embed_list = [EmbedModel(**embed_data_1), EmbedModel(**embed_data_2)]

        initial_data = {'embed_list': embed_list}

        instance = ArrayContainerModel.objects.create(**initial_data)

        initial_data.update({'pk': instance.pk})

        # Attempt to update the instance above
        class TestSerializer(rmd_ser.DjongoModelSerializer):
            class Meta:
                model = NullArrayContainerModel
                fields = '__all__'

        embed_data_1.update({'char_field': 'baz'})

        new_data = {'nullable_list': None}

        serializer = TestSerializer(instance, data=new_data)

        # Confirm that the update is valid
        assert serializer.is_valid(), serializer.errors

        # Confirm that the serializer saves the updated instance correctly
        serializer.save()

        expected_data = {'_id': str(instance.pk), 'nullable_list': None}

        expected_str = format_dict(expected_data)
        observed_str = format_dict(serializer.data)

        assert observed_str == expected_str
Esempio n. 6
0
class TestDataParsing(object):
    obj_data = {'int_field': 123, 'char_field': "Hello"}

    instance = EmbedModel(**obj_data)
    djm_embed = get_model_meta(ContainerModel).get_field('embed_field')
    rmd_embed = EmbeddedModelField(model_field=djm_embed)

    @fixture
    def errors(self, build_tuple):
        from rest_framework.exceptions import ValidationError

        err_dict = {'ValidationError': ValidationError, 'TypeError': TypeError}

        return build_tuple('Errors', err_dict)

    def test_to_internal_val(self):
        new_instance = self.rmd_embed.to_internal_value(self.obj_data)

        assert str(self.instance) == str(new_instance)

    def test_to_representation(self):
        new_data = self.rmd_embed.to_representation(self.instance)

        assert self.obj_data == new_data

    def test_conversion_equivalence(self):
        data = self.rmd_embed.to_representation(self.instance)
        new_instance = self.rmd_embed.to_internal_value(data)

        assert str(self.instance) == str(new_instance)

    @mark.error
    def test_invalid_rejection(self, error_raised):
        # Non-dictionary values are rejected
        not_a_dict = 1234

        with error_raised:
            self.rmd_embed.run_validation(not_a_dict)

        # Dictionaries denoting fields which do not exist are rejected
        wrong_dict = {'bool_field': True, 'char_field': 'error'}

        with error_raised:
            self.rmd_embed.run_validation(wrong_dict)
class TestDataParsing(object):

    embed_data = [
        {'int_field': 34, 'char_field': "Hello"},
        {'int_field': 431, 'char_field': "Bye!"}
    ]

    embed_list = [EmbedModel(**val) for val in embed_data]

    instance = ArrayContainerModel(embed_list=embed_list)

    array_field = ArrayModelField(
        model_field=get_model_meta(instance).get_field('embed_list')
    )

    def test_to_internal_val(self):
        new_list = self.array_field.to_internal_value(self.embed_data)

        assert self.embed_list == new_list

    def test_to_representation(self):
        new_list = self.array_field.to_representation(self.embed_list)

        assert self.embed_data == new_list

    def test_conversion_equivalence(self):
        rep_list = self.array_field.to_representation(self.embed_list)
        new_list = self.array_field.to_internal_value(rep_list)

        assert self.embed_list == new_list

    @mark.error
    def test_invalid_rejection(self, error_raised):
        # Non-list values are caught
        not_a_list = 1234
        with error_raised:
            self.array_field.run_validation(not_a_list)

        # List contents with invalid fields are caught
        invalid_list_field = self.embed_data.copy()
        invalid_list_field.append({'int_field': 34, 'bool_field': True})
        with error_raised:
            self.array_field.run_validation(invalid_list_field)
    def test_non_list_field_caught(self):
        """
        Check that single values passed into list fields are caught
        """

        # Set up data to use for creation
        class TestSerializer(rmd_ser.DjongoModelSerializer):
            class Meta:
                model = ArrayContainerModel
                fields = '__all__'

        embed_data_1 = {'int_field': 123, 'char_field': 'foo'}

        embed_obj = EmbedModel(**embed_data_1)

        data = {'embed_list': embed_obj}

        # Serializer should NOT validate correctly
        serializer = TestSerializer(data=data)
        assert not serializer.is_valid()

        # Confirm that the errors caught are correct
        print(serializer.errors)
class TestEmbeddingIntegration(object):
    # -- DB Setup fixtures -- #
    @fixture
    def embed_instance(self):
        embed_data = {
            'int_field': 1234,
            'char_field': 'Embed'
        }

        embed_instance = EmbedModel(**embed_data)

        return embed_instance

    @fixture
    def container_instance(self, embed_instance):
        """Prepares a default ContainerModel instance in the DB"""
        from collections import namedtuple
        container_data = {
            'embed_field': embed_instance
        }

        container_instance = ContainerModel.objects.create(**container_data)

        data_tuple = namedtuple('ModelData', ['embedded', 'container'])

        data = {
            'embedded': embed_instance,
            'container': container_instance,
        }

        return data_tuple(**data)

    @fixture
    def deep_container_instance(self, container_instance):
        from collections import namedtuple
        deep_data = {
            'str_id': 'identifier',
            'deep_embed': container_instance.container
        }

        deep_instance = DeepContainerModel.objects.create(**deep_data)

        data_tuple = namedtuple(
            'ModelData', ['embedded', 'container', 'deep_container']
        )

        data = {
            'embedded': container_instance.embedded,
            'container': container_instance.container,
            'deep_container': deep_instance
        }

        return data_tuple(**data)

    # -- Actual Test Code -- #
    @mark.parametrize(
        ["serializer", "expected", "missing"],
        [
            param(
                # Generic test
                {'target': ContainerModel},
                {'control_val': "CONTROL",
                 'embed_field': OrderedDict({
                    'int_field': 1234,
                    'char_field': 'Embed'
                 })},
                None,
                id='basic'
            ),
            param(
                # Fields meta, in the root model, is respected
                {'target': ContainerModel,
                 'meta_fields': ['embed_field']},
                {'embed_field': OrderedDict({
                    'int_field': 1234,
                    'char_field': 'Embed'
                 })},
                {'control_val': 'CONTROL'},
                id='respects_fields'
            ),
            param(
                # Exclude meta, in the root model, is respected
                {'target': ContainerModel,
                 'meta_exclude': ['embed_field']},
                {'control_val': 'CONTROL'},
                {'embed_field': OrderedDict({
                    'int_field': 1234, 'char_field': 'Embed'
                })},
                id='respects_exclude'
            ),
            param(
                # Fields meta, in the contained model, is respected
                {'target': ContainerModel, 'custom_fields': {
                    'embed_field': {
                        'target': EmbedModel,
                        'base_class': EmbeddedModelSerializer,
                        'meta_fields': ['int_field']
                    }
                }},
                {'control_val': "CONTROL",
                 'embed_field': OrderedDict({'int_field': 1234})},
                {'embed_field': OrderedDict({'char_field': 'Embed'})},
                id='respects_nested_fields'
            ),
            param(
                # Exclude meta, in the contained model, is respected
                {'target': ContainerModel, 'custom_fields': {
                    'embed_field': {
                        'target': EmbedModel,
                        'base_class': EmbeddedModelSerializer,
                        'meta_exclude': ['int_field']
                    }
                }},
                {'control_val': "CONTROL",
                 'embed_field': OrderedDict({'char_field': 'Embed'})},
                {'embed_field': OrderedDict({'int_field': 1234})},
                id='respects_nested_exclude'
            )
        ])
    def test_basic_retrieve(self, build_serializer, does_a_subset_b,
                            container_instance, serializer, expected, missing):
        # Prepare the test environment
        TestSerializer, _ = build_serializer(**serializer)
        serializer = TestSerializer(container_instance.container)

        # Make sure fields which should exist do
        if expected:
            does_a_subset_b(expected, serializer.data)

        # Make sure fields which should be ignored are
        if missing:
            with raises(AssertionError):
                does_a_subset_b(missing, serializer.data)

    @mark.parametrize(
        ["serializer", "expected", "missing"],
        [
            param(
                # Generic test
                {'target': DeepContainerModel},
                {'control_val': "CONTROL",
                 'deep_embed': OrderedDict({
                    'control_val': "CONTROL",
                    'embed_field': OrderedDict({
                        'int_field': 1234,
                        'char_field': "Embed"
                    })
                 })},
                None,
                id='basic'
            ),
            param(
                # Fields meta, in the topmost model, is respected
                {'target': DeepContainerModel,
                 'meta_fields': ['deep_embed']},
                {'deep_embed': OrderedDict({
                    'control_val': "CONTROL",
                    'embed_field': OrderedDict({
                        'int_field': 1234,
                        'char_field': "Embed"
                    })
                })},
                {'control_val': "CONTROL"},
                id='respects_root_fields'
            ),
            param(
                # Exclude meta, in the topmost model, is respected
                {'target': DeepContainerModel,
                 'meta_exclude': ['deep_embed']},
                {'control_val': "CONTROL"},
                {'deep_embed': OrderedDict({
                    'control_val': "CONTROL",
                    'embed_field': OrderedDict({
                        'int_field': 1234,
                        'char_field': "Embed"
                    })
                })},
                id='respects_root_exclude'
            ),
            param(
                # Fields meta, in the intermediary model, is respected
                {'target': DeepContainerModel,
                 'custom_fields': {
                     'deep_embed': {
                         'target': ContainerModel,
                         'meta_fields': ['embed_field']
                     },
                 }},
                {'control_val': "CONTROL",
                 'deep_embed': OrderedDict({
                     'embed_field': OrderedDict({
                         'int_field': 1234,
                         'char_field': "Embed"
                     })
                 })},
                {'deep_embed': OrderedDict({
                     'control_val': "CONTROL"
                 })},
                id='respects_intermediary_fields'
            ),
            param(
                # Exclude meta, in the intermediary model, is respected
                {'target': DeepContainerModel,
                 'custom_fields': {
                     'deep_embed': {
                         'target': ContainerModel,
                         'meta_exclude': ['embed_field']
                     },
                 }},
                {'deep_embed': OrderedDict({
                    'control_val': "CONTROL"
                })},
                {'control_val': "CONTROL",
                 'deep_embed': OrderedDict({
                     'embed_field': OrderedDict({
                         'int_field': 1234,
                         'char_field': "Embed"
                     })
                 })},
                id='respects_intermediary_exclude'
            ),
            param(
                # Field meta, in the deepest model, is respected
                {'target': DeepContainerModel,
                 'custom_fields': {
                     'deep_embed': {
                         'target': ContainerModel,
                         'custom_fields': {
                             'embed_field': {
                                 'target': EmbedModel,
                                 'meta_fields': ['char_field']
                             }
                         }
                     },
                 }},
                {'control_val': "CONTROL",
                 'deep_embed': OrderedDict({
                     'control_val': "CONTROL",
                     'embed_field': OrderedDict({
                         'char_field': "Embed"
                     })
                 })},
                {'deep_embed': OrderedDict({
                     'embed_field': OrderedDict({
                         'int_field': 1234,
                     })
                })},
                id='respects_deep_fields'
            ),
            param(
                # Field meta, in the deepest model, is respected
                {'target': DeepContainerModel,
                 'custom_fields': {
                     'deep_embed': {
                         'target': ContainerModel,
                         'custom_fields': {
                             'embed_field': {
                                 'target': EmbedModel,
                                 'meta_exclude': ['char_field']
                             }
                         }
                     },
                 }},
                {'control_val': "CONTROL",
                 'deep_embed': OrderedDict({
                     'control_val': "CONTROL",
                     'embed_field': OrderedDict({
                         'int_field': 1234,
                     })
                 })},
                {'deep_embed': OrderedDict({
                    'embed_field': OrderedDict({
                        'char_field': "Embed",
                    })
                })},
                id='respects_deep_exclude'
            )
        ])
    def test_deep_retrieve(self, build_serializer, does_a_subset_b,
                           deep_container_instance, serializer, expected,
                           missing):
        # Prepare the test environment
        TestSerializer, _ = build_serializer(**serializer)
        serializer = TestSerializer(deep_container_instance.deep_container)

        # Make sure fields which should exist do
        if expected:
            does_a_subset_b(expected, serializer.data)

        # Make sure fields which should be ignored are
        if missing:
            with raises(AssertionError):
                does_a_subset_b(missing, serializer.data)

    @mark.parametrize(
        ["initial", "serializer", "expected"],
        [
            param(
                # Basic test (Shallowly nested models)
                {'embed_field': {
                    'int_field': 1357,
                    'char_field': "Bar"
                }},
                {'target': ContainerModel},
                {'control_val': "CONTROL",
                 'embed_field': EmbedModel(
                    int_field=1357,
                    char_field="Bar"
                 )},
                id='basic_root'
            ),
            param(
                # Basic test (deeply nested models)
                {'str_id': "identifier",
                 'deep_embed': {
                    'embed_field': {
                        'int_field': 1357,
                        'char_field': "Bar"
                    }},
                 },
                {'target': DeepContainerModel},
                {'str_id': "identifier",
                 'control_val': "CONTROL",
                 'deep_embed': ContainerModel(
                    control_val="CONTROL",
                    embed_field=EmbedModel(
                        int_field=1357,
                        char_field="Bar"
                    ),
                 )},
                id='basic_deep'
            ),
            param(
                # Custom fields are valid in the root model
                {},
                {'target': ContainerModel,
                 'custom_fields': {
                     'embed_field': {
                         'target': EmbedModel,
                         'required': False,
                     }
                 }},
                {'control_val': "CONTROL"},
                id='custom_field_root'
            ),
            param(
                # Custom fields are valid (intermediary field)
                {'str_id': "identifier"},
                {'target': DeepContainerModel,
                 'custom_fields': {
                     'deep_embed': {
                         'target': ContainerModel,
                         'required': False,
                         'default': {
                             'embed_field': {
                                 'int_field': 1357,
                                 'char_field': "Bar"
                             }
                         }
                     }
                 }},
                {'control_val': "CONTROL",
                 'deep_embed': ContainerModel(
                     control_val="CONTROL",
                     embed_field=EmbedModel(
                         int_field=1357,
                         char_field="Bar"
                     )
                 )},
                id='custom_field_intermediate'
            ),
            param(
                # Custom fields are valid (deeply nested field)
                {'str_id': 'identifier',
                 'deep_embed': {
                     'embed_field': {
                         'int_field': 1357
                     }},
                 },
                {'target': DeepContainerModel,
                 'custom_fields': {
                     'deep_embed': {
                         'target': ContainerModel,
                         'custom_fields': {
                             'embed_field': {
                                 'target': EmbedModel,
                                 'custom_fields': {
                                     'char_field': CharField(default="Foo")
                                 }
                             }
                         }
                     }
                 }},
                {'str_id': 'identifier',
                 'deep_embed': ContainerModel(
                     control_val="CONTROL",
                     embed_field=EmbedModel(
                         int_field=1357,
                         char_field="Foo"
                     ))
                 },
                id='custom_field_deep'
            ),
        ])
    def test_valid_create(self, build_serializer, instance_matches_data,
                          initial, serializer, expected):
        # Test environment preparation
        TestSerializer, _ = build_serializer(**serializer)
        serializer = TestSerializer(data=initial)

        # Confirm that input data is valid
        assert serializer.is_valid(), serializer.errors

        # Make sure the serializer can save the data
        instance = serializer.save()

        # Confirm that the data was saved correctly
        instance_matches_data(instance, expected)

    @mark.parametrize(
        ["initial", "serializer", "error"],
        [
            param(
                # Invalid values in the root model are caught
                {'control_val': "WAY_TOO_LONG"},
                {'target': ContainerModel},
                AssertionError,
                id='root_validation'
            ),
            param(
                # Invalid values in the intermediary model are caught
                {'deep_embed': {
                    'control_val': "WAY_TOO_LONG"
                }},
                {'target': DeepContainerModel},
                AssertionError,
                id='intermediate_validation'
            ),
            param(
                # Invalid values in the deepest model are caught
                {'deep_embed': {
                    'control_val': {
                        'int_val': 1357,
                        'char_val': "TOO_LONG"
                    }
                }},
                {'target': DeepContainerModel},
                AssertionError,
                id='deep_validation'
            ),
            param(
                # Missing values in the deepest model are caught
                {'deep_embed': {
                    'control_val': {
                        'int_val': 1357,
                    }
                }},
                {'target': DeepContainerModel},
                AssertionError,
                id='deep_validation'
            ),
        ])
    def test_invalid_create(self, build_serializer, instance_matches_data,
                            initial, serializer, error):
        # Prepare the test environment
        TestSerializer, _ = build_serializer(**serializer)
        serializer = TestSerializer(data=initial)

        # Confirm that the serializer throws the designated error
        with raises(error):
            assert serializer.is_valid(), serializer.errors

            serializer.save()

    @mark.parametrize(
        ["update", "serializer", "expected"],
        [
            param(
                # Generic test
                {'control_val': "NEW_VAL",
                 'embed_field': {
                    'int_field': 2468,
                    'char_field': "Baz"
                 }},
                {'target': ContainerModel},
                {'control_val': "NEW_VAL",
                 'embed_field': EmbedModel(
                    int_field=2468,
                    char_field="Baz"
                 )},
                id='basic'
            ),
            param(
                # Values can be set to null after submission
                {'control_val': "NEW_VAL",
                 'embed_field': None},
                {'target': ContainerModel},
                {'control_val': "NEW_VAL",
                 'embed_field': None},
                id='null_set'
            ),
            param(
                # Meta `fields` functions in root
                {'control_val': "NEW_VAL"},
                {'target': ContainerModel,
                 'meta_fields': ['control_val']},
                {'control_val': "NEW_VAL",
                 'embed_field': EmbedModel(
                     int_field=1234,
                     char_field="Embed"
                 )},
                id='respects_root_fields'
            ),
            param(
                # Meta `fields` functions in a nested model
                {'control_val': "NEW_VAL",
                 'embed_field': {
                     'int_field': 1470
                 }},
                {'target': ContainerModel,
                 'custom_fields': {
                     'embed_field': {
                         'target': EmbedModel,
                         'meta_fields': ['int_field']
                     }
                 }},
                {'control_val': "NEW_VAL",
                 'embed_field': EmbedModel(
                     int_field=1470,
                     char_field="Embed"
                 )},
                id='respects_deep_fields'
            ),
            param(
                # Meta `exclude` functions in root model
                {'embed_field': {
                    'int_field': 1369,
                    'char_field': "Baz",
                }},
                {'target': ContainerModel,
                 'meta_exclude': ['control_val']},
                {'control_val': "CONTROL",
                 'embed_field': EmbedModel(
                     int_field=1369,
                     char_field="Baz"
                 )},
                id='respects_root_exclude'
            ),
            param(
                # Meta `exclude` functions in a nested model
                {'control_val': "NEW_VAL",
                 'embed_field': {
                     'char_field': "Baz"
                 }},
                {'target': ContainerModel,
                 'custom_fields': {
                     'embed_field': {
                         'target': EmbedModel,
                         'meta_exclude': ['int_field']
                     }
                 }},
                {'control_val': "NEW_VAL",
                 'embed_field': EmbedModel(
                     int_field=1234,
                     char_field="Baz"
                 )},
                id='respects_deep_exclude'
            ),
        ])
    def test_valid_basic_update(self, build_serializer, instance_matches_data,
                                container_instance, update, serializer, expected):
        # Prepare the test environment
        TestSerializer, _ = build_serializer(**serializer)
        serializer = TestSerializer(container_instance.container, data=update)

        # Confirm the serializer is valid
        assert serializer.is_valid(), serializer.errors

        # Confirm that the serializer can save the new data
        instance = serializer.save()

        # Confirm that the update went as planned
        instance_matches_data(instance, expected)

    @mark.parametrize(
        ['update', 'serializer', 'error'],
        [
            param(
                # Missing value caught in root model
                {'embed_field': {
                    'int_field': 1357,
                    'char_field': "Bar"
                 }},
                {'target': ContainerModel,
                 'custom_fields': {
                     'control_val': CharField(required=True)
                 }},
                AssertionError,
                id='missing_root_value'
            ),
            param(
                # Missing value caught in deep model
                {'embed_field': {
                    'char_field': "Bar"
                }},
                {'target': ContainerModel},
                AssertionError,
                id='missing_deep_value'
            ),
            param(
                # Invalid values in the root model are caught
                {'control_val': "WAY_TOO_LONG",
                 'embed_field': {
                    'int_field': 1357,
                    'char_field': "Bar"
                 }},
                {'target': ContainerModel},
                AssertionError,
                id='invalid_root_value'
            ),
            param(
                # Invalid values in nested models are caught
                {'embed_field': {
                     'int_field': "Not_An_Int",
                     'char_field': "Bar"
                 }},
                {'target': ContainerModel},
                AssertionError,
                id='invalid_deep_value'
            ),
        ])
    def test_invalid_basic_update(self, build_serializer, instance_matches_data,
                                  container_instance, update, serializer, error):
        TestSerializer, _ = build_serializer(**serializer)
        serializer = TestSerializer(container_instance.container, data=update)

        # Confirm that the serializer throws the designated error
        with raises(error):
            assert serializer.is_valid(), serializer.errors

            serializer.save()

    @mark.parametrize(
        ["update", "serializer", "expected"],
        [
            param(
                # Generic test
                {'str_id': "new_id",
                 'deep_embed': {
                    'embed_field': {
                        'int_field': 1357,
                        'char_field': "Bar"
                    }
                 }},
                {'target': DeepContainerModel},
                {'str_id': "new_id",
                 'deep_embed': ContainerModel(
                    control_val="CONTROL",
                    embed_field=EmbedModel(
                        int_field=1357,
                        char_field="Bar"
                    )
                 )},
                id='basic'
            ),
            param(
                # Intermediate `fields` respected
                {'str_id': "new_id",
                 'deep_embed': {
                     'control_val': "NEW_VAL"
                 }},
                {'target': DeepContainerModel,
                 'custom_fields': {
                     'deep_embed': {
                         'target': ContainerModel,
                         'meta_fields': ['control_val']
                     }
                 }},
                {'str_id': "new_id",
                 'deep_embed': ContainerModel(
                     control_val="NEW_VAL",
                     embed_field=EmbedModel(
                         int_field=1234,
                         char_field="Embed"
                     )
                 )},
                id='respects_intermediate_fields'
            ),
            param(
                # Intermediate `exclude` respected
                {'str_id': "new_id",
                 'deep_embed': {
                     'control_val': "NEW_VAL"
                 }},
                {'target': DeepContainerModel,
                 'custom_fields': {
                     'deep_embed': {
                         'target': ContainerModel,
                         'meta_exclude': ['embed_field']
                     }
                 }},
                {'str_id': "new_id",
                 'deep_embed': ContainerModel(
                     control_val="NEW_VAL",
                     embed_field=EmbedModel(
                         int_field=1234,
                         char_field="Embed"
                     )
                 )},
                id='respects_intermediate_exclude'
            ),
        ])
    def test_valid_deep_update(self, build_serializer, instance_matches_data,
                               deep_container_instance, update, serializer,
                               expected):
        # Prepare the test environment
        TestSerializer, _ = build_serializer(**serializer)
        serializer = TestSerializer(deep_container_instance.deep_container,
                                    data=update)

        # Confirm that serializer data is valid
        assert serializer.is_valid(), serializer.errors

        # Confirm that the serializer can save the data
        instance = serializer.save()

        # Confirm that the update went as planned
        instance_matches_data(instance, expected)

    @mark.parametrize(
        ["update", "serializer", "error"],
        [
            param(
                # Missing fields are caught (root field)
                {'deep_embed': {
                    'embed_field': {
                        'int_field': 1357,
                        'char_field': "Bar"
                    }
                }},
                {'target': DeepContainerModel},
                AssertionError,
                id="root_missing"
            ),
            param(
                # Missing fields are caught (intermediate field)
                {'str_id': "identifier",
                 'deep_embed': {
                     'embed_field': {
                         'int_field': 1357,
                         'char_field': "Baz"
                     }
                 }},
                {'target': DeepContainerModel,
                 'custom_fields': {
                     'control_val': CharField(required=True)
                 }},
                AssertionError,
                id="intermediate_missing"
            ),
            param(
                # Missing fields are caught (deep field)
                {'str_id': "identifier",
                 'deep_embed': {
                     'embed_field': {
                         'char_field': "Baz"
                     }
                 }},
                {'target': DeepContainerModel},
                AssertionError,
                id="deep_missing"
            ),
            param(
                # Invalid fields are caught (root field)
                {'str_id': "very_very_very_long",
                 'deep_embed': {
                     'embed_field': {
                         'int_field': 1324,
                         'char_field': "Baz"
                     }
                 }},
                {'target': DeepContainerModel},
                AssertionError,
                id="root_invalid"
            ),
            param(
                # Invalid fields are caught (intermediate field)
                {'str_id': "identifier",
                 'deep_embed': {
                     'embed_field': 1324
                 }},
                {'target': DeepContainerModel},
                AssertionError,
                id="intermediate_invalid"
            ),
            param(
                # Invalid fields are caught (deep field)
                {'str_id': "intermediate",
                 'deep_embed': {
                     'embed_field': {
                         'int_field': "Wrong",
                         'char_field': "Baz"
                     }
                 }},
                {'target': DeepContainerModel},
                AssertionError,
                id="deep_invalid"
            ),
        ])
    def test_invalid_update(self, build_serializer, instance_matches_data,
                            deep_container_instance, update, serializer, error):
        # Prepare the test environment
        TestSerializer, _ = build_serializer(**serializer)
        serializer = TestSerializer(deep_container_instance.deep_container,
                                    data=update)

        with raises(error):
            # Confirm that serializer data is valid
            assert serializer.is_valid(), serializer.errors

            # Confirm that the serializer can save the data
            serializer.save()

    @mark.parametrize(
        ["update", "serializer", "expected"],
        [
            param(
                # Generic test (root fields)
                {'embed_field': {
                    'char_field': "Baz",
                    'int_field': 1324
                }},
                {'target': ContainerModel},
                {'control_val': "CONTROL",
                 'embed_field': EmbedModel(
                     int_field=1324,
                     char_field="Baz"
                 )},
                id="basic_root"
            ),
            param(
                # Generic test (deep fields)
                {'embed_field': {
                    'int_field': 1324
                }},
                {'target': ContainerModel},
                {'control_val': "CONTROL",
                 'embed_field': EmbedModel(
                     int_field=1324,
                     char_field='Embed'
                 )},
                id="basic_root"
            ),
        ])
    def test_valid_basic_partial_update(self, build_serializer,
                                        instance_matches_data,
                                        container_instance,
                                        update, serializer, expected):
        # Prepare the test environment
        TestSerializer, _ = build_serializer(**serializer)
        serializer = TestSerializer(container_instance.container,
                                    data=update, partial=True)

        # Confirm that serializer data is valid
        assert serializer.is_valid(), serializer.errors

        # Confirm that the serializer can save the data
        instance = serializer.save()

        # Confirm that the update went as planned
        instance_matches_data(instance, expected)

    @mark.parametrize(
        ["update", "serializer", "expected"],
        [
            param(
                # Generic test (root fields)
                {'deep_embed': {
                    'embed_field': {
                        'char_field': "Baz",
                        'int_field': 1324
                    }
                }},
                {'target': DeepContainerModel},
                {'control_val': "CONTROL",
                 'deep_embed': ContainerModel(
                     control_val="CONTROL",
                     embed_field=EmbedModel(
                         int_field=1324,
                         char_field="Baz"
                     )
                 )},
                id="basic_root"
            ),
            param(
                # Generic test (intermediate fields)
                {'deep_embed': {
                    'control_val': "NEW_VAL"
                }},
                {'target': DeepContainerModel},
                {'control_val': "CONTROL",
                 'deep_embed': ContainerModel(
                     control_val="NEW_VAL",
                     embed_field=EmbedModel(
                         int_field=1234,
                         char_field='Embed'
                     )
                 )},
                id="basic_intermediate"
            ),
            param(
                # Generic test (intermediate fields)
                {'deep_embed': {
                    'embed_field': {
                        'int_field': 1324
                    }
                }},
                {'target': DeepContainerModel},
                {'control_val': "CONTROL",
                 'deep_embed': ContainerModel(
                     control_val="CONTROL",
                     embed_field=EmbedModel(
                         int_field=1324,
                         char_field='Embed'
                     )
                 )},
                id="basic_deep"
            ),
        ])
    def test_valid_deep_partial_update(self, build_serializer,
                                        instance_matches_data,
                                        deep_container_instance,
                                        update, serializer, expected):
        # Prepare the test environment
        TestSerializer, _ = build_serializer(**serializer)
        serializer = TestSerializer(deep_container_instance.deep_container,
                                    data=update, partial=True)

        # Confirm that serializer data is valid
        assert serializer.is_valid(), serializer.errors

        # Confirm that the serializer can save the data
        instance = serializer.save()

        # Confirm that the update went as planned
        instance_matches_data(instance, expected)

    @mark.parametrize(
        ['update', 'serializer', 'error'],
        [
            param(
                # Invalid fields are caught (root field)
                {'str_id': "very_very_very_long"},
                {'target': DeepContainerModel},
                AssertionError,
                id="root_invalid"
            ),
            param(
                # Invalid fields are caught (intermediate field)
                {'deep_embed': {
                    'control_field': "NEW_VAL",
                    'embed_field': 1324,
                }},
                {'target': DeepContainerModel},
                AssertionError,
                id="intermediate_invalid"
            ),
            param(
                # Invalid fields are caught (deep field)
                {'deep_embed': {
                    'control_field': "NEW_VAL",
                    'embed_field': {
                        'int_field': "NOT_AN_INT",
                        'char_field': "Foo"
                    },
                }},
                {'target': DeepContainerModel},
                AssertionError,
                id="deep_invalid"
            ),
        ]
    )
    def test_invalid_partial_update(self, build_serializer,
                                    instance_matches_data,
                                    deep_container_instance,
                                    update, serializer, error):
        # Prepare the test environment
        TestSerializer, _ = build_serializer(**serializer)
        serializer = TestSerializer(deep_container_instance.deep_container,
                                    data=update, partial=True)

        with raises(error):
            assert serializer.is_valid(), serializer.errors

            # Confirm that the serializer can save the data
            serializer.save()
 def embedded(self):
     from tests.models import EmbedModel
     return EmbedModel()