Using latest version of Django and DRF.
I have a rather complex requirement I can't find a solution for. I'll try to simplify it.
Let's say I have a model that has two fields. field_a and field_b
I have a ModelSerializer for it. I POST a request with its fields. The fields get validated with the model and then against my two serializer functions validate_field_a and validate_field_b. All is well.
Now I'd like my POST request to include a third field that is not a member of that model. let's call it field_c. I have a custom def create(self, validated_data): in my serializer which saves everything to the database.
with regards to field_c I would like to:
Custom Validate it. just like I do with the other two fields.
Require that it is mandatory for the whole request to succeed and if it's not, issue a "Field is required" error just like if I forgot to POST one of my required model fields.
Have the chance to take field_c and save it onto a totally different unrelated Model's row in the db.
I can't seem to get around that. If I add field_c to the fields meta - it throws an exception saying justifiably that field_c is not in my model. If I don't include it in fields, the validate_field_c which I really want to put there doesn't even get called.
What can I do?
You can add the custom field in your serializer as a write_only field and override the create method so that you can handle the custom field's value.
Something like this:
class MySerializer(serializers.ModelSerializer):
field_c = serializers.CharField(write_only=True)
class Meta:
model = MyModel
fields = ('field_a', 'field_b', 'field_c')
def validate_field_c(self, value):
if value is 'test':
raise ValidationError('Invalid')
return value
def create(self, validated_data, **kwargs):
field_c = validated_data.pop('field_c')
return MyModel.objects.create(**validated_data)
Don't use ModelSerializer for this - use a serializer that recreates the same fields as your model & include field_c as you would.
I understand that you want your model to do some of the work in the validation process but the design of DRF is such that it isolates these responsibilities. You can read more about it here. Basically, the serializer should be the one doing all the validation heavy-lifting.
Of course, this means that you'll have to explicitly define the validation methods in the serializer.
In your custom create() method you can create the model instance or do whatever you want in it as required.
Related
The Problem
I am using a model class Event that contains an optional ManyToManyField to another model class, User (different events can have different users), with a factory class EventFactory (using the Factory Boy library) with a serializer EventSerializer. I believe I have followed the docs for factory-making and serializing, but am receiving the error:
ValueError: "< Event: Test Event >" needs to have a value for field "id"
before this many-to-many relationship can be used.
I know that both model instances must be created in a ManyToMany before linking them, but I do not see where the adding is even happening!
The Question
Can someone clarify how to properly use a ManyToManyField using models, factory boy, and serializers in a way I am not already doing?
The Set-Up
Here is my code:
models.py
#python_2_unicode_compatible
class Event(CommonInfoModel):
users = models.ManyToManyField(User, blank=True, related_name='events')
# other basic fields...
factories.py
class EventFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.Event
#factory.post_generation
def users(self, create, extracted, **kwargs):
if not create:
# Simple build, do nothing.
return
if extracted:
# A list of users were passed in, use them
# NOTE: This does not seem to be the problem. Setting a breakpoint
# here, this part never even fires
for users in extracted:
self.users.add(users)
serializers.py
class EventSerializer(BaseModelSerializer):
serialization_title = "Event"
# UserSerializer is a very basic serializer, with no nested
# serializers
users = UserSerializer(required=False, many=True)
class Meta:
model = Event
exclude = ('id',)
test.py
class EventTest(APITestCase):
#classmethod
def setUpTestData(cls):
cls.user = User.objects.create_user(email='test#gmail.com',
password='password')
def test_post_create_event(self):
factory = factories.EventFactory.build()
serializer = serializers.EventSerializer(factory)
# IMPORTANT: Calling 'serializer.data' is the exact place the error occurs!
# This error does not occur when I remove the ManyToManyField
res = self.post_api_call('/event/', serializer.data)
Version Info
Django 1.11
Python 2.7.10
Thank you for any help you can give!
Regarding the error:
It seems like the missing id is due to the use of .build() instead of .create() (or just EventFactory()). The former does not save the model, and therefore it does not get an id value, whereas the latter does (refer to factory docs and model docs).
I suspect the serializer still expects the object to have an id, even though the many-to-many relationship is optional, because it cannot enforce a potential relationship without an id.
However, there might be a simpler solution to the actual task. The above method is a way of generating the POST data passed to post_api_call(). If this data was instead created manually, then both the factory and serializer become unnecessary. The explicit data method might even be better from a test-perspective, because you can now see the exact data which has to produce an expected outcome. Whereas with the factory and serializer method it is much more implicit in what is actually used in the test.
I have a model with some fields and a unique together:
....
class Meta(object):
unique_together = ('device_identifier', 'device_platform',)
Obviously, in this way, about Django rest framework serializer, I obtain an error when I try to make a PUT with the same device_identifier and device_platform (if already exist an entry with this data).
{
"non_field_errors": [
"The fields device_identifier, device_platform must make a unique set."
]
}
Is possible to disable this validation in my model serializer?
Because I need to manage this situation during save model step (for me, in serializer validation this is not an error)
Django REST framework applies the UniqueTogetherValidator on the serializer. You can remove this by override the validators field in the serializer definition.
class ExampleSerializer(serializers.ModelSerializer):
class Meta:
validators = []
Note that this also removes the other unique-check validators that are applied on the model, which might not be the best idea. To avoid that, just override the get_unique_together_validators method on the serializer, to ensure only unique-together check is removed.
class ExampleSerializer(serializers.ModelSerializer):
def get_unique_together_validators(self):
"""Overriding method to disable unique together checks"""
return []
You need to remove the validator from the serializer's list.
Although not exactly the same, the steps are explained here
I have a database schema that has each object of a certain type being stored across two separate tables (one row in each table, different data in each, with a foreign key from one to the other.)
Unfortunately, Django Rest Framework tends to assume that there is a one to one correspondence between serializers and models, which is not true of my case. How should I be approaching this? It seems like the serializer should return the representation of the object which will be the actual HTTP response of the ajax requests, so using two serializers doesn't seem right. I've looked at extending BaseSerializer (which is how I currently plan to implement this if I don't find better solutions), but certain methods take in an instance, which should contain all the data needed to serialize the object, whereas I have two instances relevant.
Any advice would be super appreciated! Thank you.
Writable nested representations section might help you.
You have 2 models ModelA and ModelB. Create your first model's serializer
class ModelASerializer(serializers.ModelSerializer):
class Meta:
model = ModelA
fields = ('fields',..) #
Then in other model's serializer add the first serializer and override the required methods (like create, update). Something like this:
class ModelBSerializer(serializers.ModelSerializer):
# add the serializer for the foreignkey model
model_a = ModelASerializer()
class Meta:
model = ModelB
fields = ('fields',..) #
def create(self, validated_data):
modela_data = validated_data.pop('model_a')
model_b = ModelB.objects.create(**validated_data)
ModelA.objects.create(model_b=model_b, **modela_data)
return model_b
# override update too ..
I have a DRF 3.3+ API to create/update/retrieve users. I use the super-convenient write_only serializer field argument on my password field so that it's used to create/update a user, but is not returned when serializing a user. However, I want to make password required to create a user, but optional to update a user. Instead of write_only, it would be great to have something like create_only and update_only for finer-grained control. Since that's not available, I have two serializers that are exactly the same except for the password field, which doesn't seem clean.
I'm aware of this answer for DRF 2: Disable field update after object is created, but I was hoping there's a better way to handle this use case in DRF 3.3+. Thanks in advance for any insight.
There's no create_only or update_only like options.
You could override __init__ to see if the instance parameter was passed and adjust fields accordingly.
I'd have two serialisers, just as you do. So as not to repeat too much, I have one subclass the other, with the subclass adjusting only the fields that differed.
I think there is no inbuilt functionality for that.
But you could do something like this to define the fields depening on the action:
class FooSerializer(serializers.ModelSerializer):
# define fields depending on action
_action_fields = {'update': ['name'],
'create': ['name', 'password'],
'default': ['name', 'password']}
class Meta:
model = Foo
fields = ['name', 'password'] # define max fields you want serialize
def __init__(self, *args, **kwargs):
serializers.ModelSerializer.__init__(self, *args, **kwargs)
action = kwargs['context']['view'].get('action', 'default') # I'm not 100% sure if action is defined here. But something similar
allowed = set(self._action_fields[action])
existing = set(self.fields.keys())
for field_name in existing - allowed:
self.fields.pop(field_name)
I have a simple Model that stores the user that created it with a ForeignKey. The model has a corresponding ModelSerializer and ModelViewSet.
The problem is that when the user submits a POST to create a new record, the user should be set by the backend. I tried overriding perform_create on the ModelViewSet to set the user, but it actually still fails during the validation step (which makes sense). It comes back saying the user field is required.
I'm thinking about overriding the user field on the ModelSerializer to be optional, but I feel like there's probably a cleaner and more efficient way to do this. Any ideas?
I came across this answer while looking for a way to update values before the control goes to the validator.
This might be useful for someone else - here's how I finally did it (DRF 3) without rewriting the whole validator.
class MyModelSerializer(serializers.ModelSerializer):
def to_internal_value(self, data):
data['user'] = '<Set Value Here>'
return super(MyModelSerializer, self).to_internal_value(data)
For those who're curious, I used this to round decimal values to precision defined in the model so that the validator doesn't throw errors.
You can make the user field as read_only.
This will ensure that the field is used when serializing a representation, but is not used when creating or updating an instance during deserialization.
In your serializers, you can do something like:
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
extra_kwargs = {
'user' : {'read_only' : True} # define the 'user' field as 'read-only'
}
You can then override the perform_create() and set the user as per your requirements.
Old topic but it could be useful for someone.
If you want to alter your data before validation of serializer:
serializer.initial_data["your_key"] = "your data"
serializer.is_valid(raise_exception=True)