In Django's rest framework, how do I turn off validation for certain fields in my serializer?

Viewed 5956

I'm using Django 3, Python 3.8, and the Django Rest framework. Additionally, I'm using the address module from here -- https://github.com/furious-luke/django-address. I have built the following serializers for the address objects as well as one of my own taht depends on them ...

class CountrySerializer(serializers.ModelSerializer):
    class Meta:
        model = Country
        fields = ['id', 'name', 'code']

    def to_representation(self, instance):
        rep = super().to_representation(instance)
        return rep


class StateSerializer(serializers.ModelSerializer):
    country = CountrySerializer()
    class Meta:
        model = State
        fields = ['id', 'code', 'country']

    def to_representation(self, instance):
        rep = super().to_representation(instance)
        rep['country'] = CountrySerializer(instance.country).data
        return rep


class LocalitySerializer(serializers.ModelSerializer):
    state = StateSerializer()
    class Meta:
        model = Locality
        fields = ['id', 'name', 'postal_code', 'state']

    def to_representation(self, instance):
        rep = super().to_representation(instance)
        rep['state'] = StateSerializer(instance.state).data
        return rep

    def create(self, validated_data):
        """
        Create and return a new `Locality` instance, given the validated data.
        """
        validated_data['state'] = validated_data['state'].id
        print("\n\n\n\n****####\n\n", validated_data, "\n\n\n\n")
        return "{bogus}"
        #return Locality.objects.create(**validated_data)

class AddressSerializer(serializers.ModelSerializer):
    locality = LocalitySerializer()   #LocalityTypeField()

    class Meta:
        model = Address
        fields = ['id', 'street_number', 'route', 'raw', 'formatted', 'latitude', 'longitude', 'locality']

    def to_representation(self, instance):
        rep = super().to_representation(instance)
        rep['locality'] = LocalitySerializer(instance.locality).data
        return rep

    def create(self, validated_data):
        """
        Create and return a new `AddressField` instance, given the validated data.
        """
        address = AddressTypeField.objects.create(**validated_data)
        return address

class CoopSerializer(serializers.ModelSerializer):
    types = CoopTypeSerializer(many=True, allow_empty=False)
    addresses = AddressSerializer(many=True)   # AddressTypeField(many=True)
    phone = ContactMethodPhoneSerializer()
    email = ContactMethodEmailSerializer()

    class Meta:
        model = Coop
        fields = '__all__'

    def to_representation(self, instance):
        rep = super().to_representation(instance)
        rep['types'] = CoopTypeSerializer(instance.types.all(), many=True).data
        rep['addresses'] = AddressSerializer(instance.addresses.all(), many=True).data
        return rep

    def create(self, validated_data):
        """
        Create and return a new `Snippet` instance, given the validated data.
        """

        coop_types = validated_data.pop('types', {})
        phone = validated_data.pop('phone', {})
        email = validated_data.pop('email', {})
        instance = super().create(validated_data)
        for item in coop_types:
            coop_type, _ = CoopType.objects.get_or_create(name=item['name'])
            instance.types.add(coop_type)
        instance.phone = ContactMethod.objects.create(type=ContactMethod.ContactTypes.PHONE, **phone)
        instance.email = ContactMethod.objects.create(type=ContactMethod.ContactTypes.EMAIL, **email)
        instance.save()
        return instance

I'm trying to pass data to my serializer to be saved ...

@pytest.mark.django_db
def test_coop_create(self):
    """ Test coop serizlizer model """
    name = "Test 8899"
    coop_type_name = "Library"
    street = "222 W. Merchandise Mart Plaza, Suite 1212"
    city = "Chicago"
    postal_code = "60654"
    enabled = True
    postal_code = "60654"
    email = "test@example.com"
    phone = "7732441468"
    web_site = "http://www.1871.com"
    state = StateFactory()
    serializer_data = {
        "name": name,
        "types": [
            {"name": coop_type_name}
        ],
        "addresses": [{
            "raw": street,
            "formatted": street,
            "locality": {
                "name": city,
                "postal_code": postal_code,
                "state": {
                  "id": state.id,
                  "country": {
                    "id": state.country.id,
                    "name": state.country.name
                  }
                }
            }
        }],
        "enabled": enabled,
        "phone": {
          "phone": phone
        },
        "email": {
          "email": email
        },
        "web_site": web_site
    }

    serializer = CoopSerializer(data=serializer_data)
    assert serializer.is_valid(), serializer.errors

but I keep getting validation errors ...

    assert serializer.is_valid(), serializer.errors
AssertionError: {'addresses': [{'locality': {'state': {'country': {'name': [ErrorDetail(string='country with this name already exists.', code='unique')]}}}}]}

How do I turn off validation for certain fields that I don't want validated? Specifically, I dno't want the country name field validated.

Edit: In response to the answer tiven, changed my CountrySerializer to

class CountrySerializer(serializers.ModelSerializer):
    class Meta:
        model = Country
        fields = ['id', 'name', 'code']
        extra_kwargs = {
            'name': {
                'validators': []
            }
        }

    def to_representation(self, instance):
        rep = super().to_representation(instance)
        return rep

but got the error

======================================================================
ERROR: test_coop_create (tests.test_serializers.SerializerTests)
Test coop serizlizer model
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/davea/Documents/workspace/chicommons/maps/web/tests/test_serializers.py", line 95, in test_coop_create
    assert serializer.is_valid(), serializer.errors
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 234, in is_valid
    self._validated_data = self.run_validation(self.initial_data)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 433, in run_validation
    value = self.to_internal_value(data)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 490, in to_internal_value
    validated_value = field.run_validation(primitive_value)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 621, in run_validation
    value = self.to_internal_value(data)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 657, in to_internal_value
    validated = self.child.run_validation(item)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 433, in run_validation
    value = self.to_internal_value(data)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 490, in to_internal_value
    validated_value = field.run_validation(primitive_value)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 435, in run_validation
    self.run_validators(value)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 468, in run_validators
    super().run_validators(to_validate)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/rest_framework/fields.py", line 588, in run_validators
    validator(value, self)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/rest_framework/validators.py", line 150, in __call__
    queryset = self.filter_queryset(attrs, queryset, serializer)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/rest_framework/validators.py", line 136, in filter_queryset
    return qs_filter(queryset, **filter_kwargs)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/rest_framework/validators.py", line 28, in qs_filter
    return queryset.filter(**kwargs)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/django/db/models/manager.py", line 82, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/django/db/models/query.py", line 904, in filter
    return self._filter_or_exclude(False, *args, **kwargs)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/django/db/models/query.py", line 923, in _filter_or_exclude
    clone.query.add_q(Q(*args, **kwargs))
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/django/db/models/sql/query.py", line 1338, in add_q
    clause, _ = self._add_q(q_object, self.used_aliases)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/django/db/models/sql/query.py", line 1363, in _add_q
    child_clause, needed_inner = self.build_filter(
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/django/db/models/sql/query.py", line 1240, in build_filter
    lookups, parts, reffed_expression = self.solve_lookup_type(arg)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/django/db/models/sql/query.py", line 1078, in solve_lookup_type
    _, field, _, lookup_parts = self.names_to_path(lookup_splitted, self.get_meta())
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/django/db/models/sql/query.py", line 292, in get_meta
    return self.model._meta
AttributeError: 'NoneType' object has no attribute '_meta'

GitHub is at https://github.com/chicommons/maps/tree/master/web/directory

3 Answers

Disclaimer

It seems the name - (source code) field of Country model has been set to unique - (Django doc) condition, there are chances that the Database will raise django.db.utils.IntegrityError: UNIQUE constraint failed exceptions if you remove the validation

So you can remove the validation in (at least) two ways,

Method-1

Remove validations by extra_kwargs - (DRF doc) Meta class attribute

class CountrySerializer(serializers.ModelSerializer):
    class Meta:
        model = Country
        fields = ['id', 'name', 'code']
        extra_kwargs = {
            'name': {
                'validators': []
            }
        }

Method-2

Explicitly specify a name field in the serializer

class CountrySerializer(serializers.ModelSerializer):
    name = serializers.CharField()

    class Meta:
        model = Country
        fields = ['id', 'name', 'code']

Your original question

To answer your question this validation is raised because of the unique key on your model, so even if you "silence" it, you'll not be able to create your instance successfully.

If you don't need to create the country, you can remove the nested custom serializer on StateSerializer, in this way passing "country": <existing_country_id> in your post data will link the existing country to a new state (it's better to create only a country, not a state because there is another unique for the state, `unique_together = ('name', 'country')

tests.py

country = CountryFactory()
serializer_data = {
   ....
   "state": {
      "name": "a state",
       "country":country.id
   }
  ...
}

serializers.py

class StateSerializer(serializers.ModelSerializer):
    # removed country = CountrySerializer()
    class Meta:
        model = State
        fields = ['id', 'code', 'country']

If you want to handle a get_or_create functionality you can write your logic in the serializer or make a separate API call to create the country before the coop and only have the "existing case" for the CoopSerializer.

Without any other information, it makes more sense to me that the database should be prepopulated with all the states and countries, so I will remove the creation of them from API and pass existing ids, keeping only addresses and maybe localities as creatable from API

Your last error

For the last error that you reported, have looked your complete code in your repo https://github.com/chicommons/maps/tree/master/web

This error

======================================================================
ERROR: test_coop_create (tests.test_serializers.SerializerTests)
Test coop serizlizer model
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/davea/Documents/workspace/chicommons/maps/web/tests/test_serializers.py", line 95, in test_coop_create
    assert serializer.is_valid(), serializer.errors
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 234, in is_valid
    self._validated_data = self.run_validation(self.initial_data)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 433, in run_validation
    value = self.to_internal_value(data)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 490, in to_internal_value
    validated_value = field.run_validation(primitive_value)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 621, in run_validation
    value = self.to_internal_value(data)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 657, in to_internal_value
    validated = self.child.run_validation(item)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 433, in run_validation
    value = self.to_internal_value(data)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 490, in to_internal_value
    validated_value = field.run_validation(primitive_value)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 435, in run_validation
    self.run_validators(value)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 468, in run_validators
    super().run_validators(to_validate)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/rest_framework/fields.py", line 588, in run_validators
    validator(value, self)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/rest_framework/validators.py", line 150, in __call__
    queryset = self.filter_queryset(attrs, queryset, serializer)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/rest_framework/validators.py", line 136, in filter_queryset
    return qs_filter(queryset, **filter_kwargs)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/rest_framework/validators.py", line 28, in qs_filter
    return queryset.filter(**kwargs)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/django/db/models/manager.py", line 82, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/django/db/models/query.py", line 904, in filter
    return self._filter_or_exclude(False, *args, **kwargs)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/django/db/models/query.py", line 923, in _filter_or_exclude
    clone.query.add_q(Q(*args, **kwargs))
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/django/db/models/sql/query.py", line 1338, in add_q
    clause, _ = self._add_q(q_object, self.used_aliases)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/django/db/models/sql/query.py", line 1363, in _add_q
    child_clause, needed_inner = self.build_filter(
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/django/db/models/sql/query.py", line 1240, in build_filter
    lookups, parts, reffed_expression = self.solve_lookup_type(arg)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/django/db/models/sql/query.py", line 1078, in solve_lookup_type
    _, field, _, lookup_parts = self.names_to_path(lookup_splitted, self.get_meta())
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.8/site-packages/django/db/models/sql/query.py", line 292, in get_meta
    return self.model._meta
AttributeError: 'NoneType' object has no attribute '_meta'

is due to the way you're setting the custom manager on the two models State and Locality

setattr(State._meta, 'default_manager', StateCustomManager())
setattr(Locality._meta, 'default_manager', LocalityCustomManager())

because having set them in this way, both the model and the manager, miss some attributes. In fact, if you set the objects property at class definition the add_to_class method of the BaseModel class is called.

So, first of all, I suggest you to change the two setattr with

State.add_to_class('objects', StateCustomManager())
Locality.add_to_class('objects', LocalityCustomManager())

In this way, the error is resolved, and you get this one:


Failure
Traceback (most recent call last):
  File "./fixme/web/tests/test_serializers.py", line 127, in test_coop_create
    coop_saved = serializer.save()
  File ".virtualenvs/fix_me_web/lib/python3.7/site-packages/rest_framework/serializers.py", line 212, in save
    self.instance = self.create(validated_data)
  File "/Users/francescazorzi/PycharmProjects/fixme/web/directory/serializers.py", line 238, in create
    instance = super().create(validated_data)
  File ".virtualenvs/fix_me_web/lib/python3.7/site-packages/rest_framework/serializers.py", line 934, in create
    raise_errors_on_nested_writes('create', self, validated_data)
  File ".virtualenvs/fix_me_web/lib/python3.7/site-packages/rest_framework/serializers.py", line 818, in raise_errors_on_nested_writes
    class_name=serializer.__class__.__name__
AssertionError: The `.create()` method does not support writable nested fields by default.
Write an explicit `.create()` method for serializer `directory.serializers.CoopSerializer`, or set `read_only=True` on nested serializer fields.

as mentioned in the error, if you want to have nested writable serializer you need to override the create method on all your nested serializer in the chain

Problem

You're not alone, this has been a running issue (and not entirely obvious in documentation) for a few years now.

Your model serializer is set to use the same constraints found on the model for validation. A unique constraint on country model's name column seems to be causing this issue.

Solution

There are 3 ways that I know of that resolve this issue:

  1. Consider removing unique constraint on the country field in the model.
  2. Use a custom validator to bypass validation.
  3. Use DRF Writable Nested, which adds support for this functionality.

References:

Unique constraint prevents nested serializers from updating: https://github.com/encode/django-rest-framework/issues/2996

Dealing with unique constraints in nested serializers: https://medium.com/django-rest-framework/dealing-with-unique-constraints-in-nested-serializers-dade33b831d9

DRF Writable Nested: https://github.com/beda-software/drf-writable-nested

Related