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

4

7

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 = "[email protected]"
    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

Jerricajerrie answered 30/7, 2020 at 22:2 Comment(5)
There is no much sense of having 2 Country objects with same name, package you are using has unique on Country name hence error.Paternal
I'm not trying to create a new country -- I just want to create a new address using existing state and country objects.Jerricajerrie
@Jerricajerrie PS: The ._meta issue you encountered is related to your models.py on lines 95 and 101 attempting to access an instance method on an uninstantiated class.Kavanaugh
@Jerricajerrie the serializer_data in the coop_create test doesn't specify a name for state, adding one bypasses the initial validation error when running the test.Kavanaugh
@Jerricajerrie The code I specified will allow you to bypass validation, but you then need to handle the business logic in the create + update method yourself by popping the related field and creating/passing it to a subsequent serializer (if you're going with option 2). If you're going with the option 3 I specified you just need to follow these instructions (github.com/beda-software/drf-writable-nested).Kavanaugh
B
13

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']
Borlow answered 2/8, 2020 at 4:24 Comment(10)
You probably know this code better than me now. Per my comment to @pygeek, I'm not trying to create a Country object, I just want to create an address object and use an existing Country. Anyway, I tried your suggestion (edited my question with the code and output), but got the error, "AttributeError: 'NoneType' object has no attribute '_meta'". I also included the GitHub link, but it's the same one as before. This DRF is hard!Jerricajerrie
Lol. Let me try. I would like to see how did you run the tests (pytest is hard for me). apart from the all, a simple suggestion, adding a minimal reproducible example will have a higher chance of getting an accurate answer. personally, I don't think the OP is a mve.Borlow
K, just checked in new a bare bones test -- "test_address_create". The tests are run by running "python manage.py test --settings=directory.test_settings". I wasn't sure how to make it more bare bones but certainly can give it a shot if you let me know what might be useful there.Jerricajerrie
When I ran the tests, I found a few problems. 1. The mentioned error (AttributeError: 'NoneType' object has no attribute '_meta') is raised from test_coop_create test, not from test_address_create. 2. The test_address_create() uses the Modelfactory and I think it is supposed to create the model instance "directly", not from serializer. FYI: I don't have any prior experience using in PyTestBorlow
You're not getting the "AttributeError: 'NoneType' object has no attribute '_meta'" error for the "test_address_create" test? What error are you getting, or is it passing for you?Jerricajerrie
It is not passing, but the err you mentioned is raised from a different test function named, test_coop_createBorlow
Would you be so kind as to share the output of running that test? I am getting the "_meta" error from running that test as you can see here -- imgur.com/a/PvwHhG8 . So if you're not getting it, then this might be a simple problem and it's just a version thing or something like that.Jerricajerrie
Here is the pastebin linkBorlow
@Dave, "Method-1" is what I detailed in my answer as option 2, with references to GitHub discussion and tutorial on implementation (medium.com/django-rest-framework/…).Kavanaugh
@ArakkalAbu, I think you might have an old version of my code because "test_address_create" is actually not in your output. Even if you do get the latest version, I have a feeling you'll see the same error. I get this error using either method 1 or method 2 as you document.Jerricajerrie
B
2

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

Bade answered 7/8, 2020 at 9:4 Comment(5)
Thanks for this thorough answer. Yes, I NEVER want to create states or countries, I probably shoudl have stated that more clearly. I only want to re-use existing ones. Question -- you mention, "you can remove the nested custom serializer on StateSerializer" -- if I do that, when returning my Locality data, will I still be able to return info about the name of the state and the country in addition to their IDs?Jerricajerrie
@Jerricajerrie to achieve this in an API call, you can use a serializer to perform the creation and different one for returning the responseBade
My other question was regarding your proposed change of "setattr(State._meta, 'default_manager' ..." to "State.add_to_class('objects' ...", this breaks my ability to run my python command, "python manage.py docker_init_db_data", which allows me to import YAML data in which the state has the format, "state: ['IL', 'United States']". (Fixture is here -- github.com/chicommons/maps/blob/master/web/directory/fixtures/… )Jerricajerrie
@Jerricajerrie if you need the custom managers only to load the initial dump, you can 1) move setattr(State._meta, 'default_manager', StateCustomManager()) setattr(Locality._meta, 'default_manager', LocalityCustomManager()) inside the handle of the command, where this is enough to get the job done 2) have pks instead of natural keys in the dump (this should not cause any problem, because you're clearing the db before the import)Bade
Never thought of moving the "setattr" piece to the command module. Makes total sense. Did get me moving in the right direction.Jerricajerrie
K
1

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

Kavanaugh answered 1/8, 2020 at 22:23 Comment(7)
To address something, I don't want to remove the unique name constraint at the model level b/c I'm not trying to create a country object. I want to create a new address object and use an existing country object. If possible, I would like to remove this unique name check at the serializer level but I don't know how.Jerricajerrie
@Jerricajerrie you can remove the validation at the serializer level or get it working without need to remove any validations. See the second and third option that I detail in my answer.Kavanaugh
@Jerricajerrie see references section for tutorials on how to accomplish the other two options I detail.Kavanaugh
Hi @pygeek, (just keeping the threads separate). I did use the "validators []" strategy -- github.com/chicommons/maps/blob/master/web/directory/…, but I still get the error. With regards to the tutorial, my implementation mirrors theirs, which is why I'm confused about the error I'm receiving. I'm wondering if it has somethign to do with the Django address module.Jerricajerrie
@Jerricajerrie I’ll take a look at your repo shortly.Kavanaugh
@Jerricajerrie PS: The ._meta issue you encountered is related to your models.py on lines 95 and 101 attempting to access an instance method on an uninstantiated class.Kavanaugh
Line 95 and 101 of which file?Jerricajerrie
T
0

The current answer from @JPG (as of 2024-02-19) will work and is the simplest one. However, if you have custom validators in your model field name, those will not anymore be run. For example, if you are ensuring that the country name has no spaces and defined it in the model:

from django.core.exceptions import ValidationError
from django.db import models


def validate_no_spaces(country_name):
    if " " in country_name:
        raise ValidationError("Country name has spaces")


class Country(models.Model):
    name = models.CharField(max_length=50, unique=True, validators=[validate_no_spaces])

If you want to remove the unique field checker of the model field but still want to run the validate_no_spaces from the model during the validation process of the serializer, one approach is to override get_fields():

from rest_framework import fields, serializers, validators

from onesentry.configuration.models import Configuration


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

    def get_fields(self):
        fields = super().get_fields()
        if "name" in fields:
            fields["name"].validators = [
                validator
                for validator in fields["name"].validators
                if not isinstance(validator, validators.UniqueValidator)  # Removes the uniqueness checker
            ]
        return fields

Details

The method get_fields() is the one responsible for building each field of a serializer. Each field (which is of type rest_framework.fields.Field) has an attribute "validators" (if it is a serializers.ModelSerializer, then it is from the model field).

Flow:

  • BaseSerializer.is_valid()
  • Serializer.run_validation()
  • Serializer.to_internal_value()
  • Initializes all Serializer.fields via ModelSerializer.get_fields()
    • This is also the part where the values from CountrySerializer.Meta.extra_kwargs are used to overwrite the construction of the rest_framework.fields.Field objects.
  • Then within that Serializer.to_internal_value(), it will iterate through each field and call:
    • Field.run_validation()
    • Field.run_validators()
      • This is the part that actually runs each validator e.g. the uniqueness check and raises ValidationError.
Twosome answered 19/2 at 9:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.