Django REST Framework API ignores any unknown parameters. This has led to several issues. For example, when a model filter was missing, a client received all records rather than the single one they were expecting. How can I force DRF to return 400 Bad Request whenever an API call includes an unknown parameter?
(An unknown parameter is one which is not in [SerializerClass].Meta.fields if this is a list, or not in [SerializerClass].Meta.model fields if it is __all__.)
One of the easy and basic solution may be this,
# serializer.py
class FooSerializer(serializers.ModelSerializer):
class Meta:
model = Foo
fields = '__all__'
# views.py
def foo_view(request):
serializer = FooSerializer(data=request.data)
if set(request.data.keys()) - set(serializer.fields.keys()):
raise Exception
Note: Assuming request.data is a dict object
Disclaimer: I'm not sure about the cases while we use source argument in serializer
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.
Using Django Rest Framework 3, Function Based Views, and the ModelSerializer (more specifically the HyperlinkedModelSerializer).
When a user submits a form from the client, I have a view that takes the request data, uses it to call to an external API, then uses the data from the external API to populate data for a model serializer.
I believe I have this part working properly, and from what I read, you are supposed to use context and validate()
In my model serializer, I have so far just this one overidden function:
from django.core.validators import URLValidator
def validate(self, data):
if 'foo_url' in self.context:
data['foo_url'] = self.context['foo_url']
URLValidator(data['foo_url'])
if 'bar_url' in self.context:
data['bar_url'] = self.context['bar_url']
URLValidator(data['bar_url'])
return super(SomeSerializer, self).validate(data)
Just in case, the relevant view code is like so:
context = {'request': request}
...
context['foo_url'] = foo_url
context['bar_url'] = bar_url
s = SomeSerializer(data=request.data, context=context)
if s.is_valid():
s.save(user=request.user)
return Response(s.data, status=status.HTTP_201_CREATED)
Now assuming I have the right idea going (my model does populate its foo_url and bar_url fields from the corresponding context data), where I get confused is how the validation is not working. If I give it bad data, the model serializer does not reject it.
I assumed that in validate(), by adding the context data to the data, the data would be checked for validity when is_valid() was called. Maybe not the case, especially when I print out s (after using the serializer but before calling is_valid()) there is no indication that the request object's data has been populated with the context data from validate() (I don't know if it should be).
So I tried calling the URLValidators directly in the validate() method, but still doesn't seem to be working. No errors despite giving it invalid data like 'asdf' or an empty python dict ({}). My test assertions show that the field indeed contains invalid data like '{}'.
What would be the proper way to do this?
You're not calling the validator.
By doing URLValidator(data['bar_url']) you're actually building an url validator with custom schemes (see the docs) and that's it. The proper code should be:
URLValidator()(data['bar_url'])
Where you build a default url validator and then validate the value.
But anyway I would not use this approach, what I would do instead is directly add the extra data (not using the context) and let DRF do the validation by declaring the right fields:
# Somewhere in your view
request.data['bar_url'] = 'some_url'
# In serializer:
class MySerializer(serializers.ModelSerializer):
bar_url = serializers.URLField()
class Meta:
fields = ('bar_url', ...)
To answer your comment
I also don't understand how this also manages to make it past the
Django's model validation
See this answer:
Why doesn't django's model.save() call full_clean()?
By default Django does not automatically call the .full_clean method so you can save a model instance with invalid values (unless the constraints are on the database level).
This is more of a conceptual question. I am not looking for code sample answers. Simply an insight into validation when working with Django and DRF.
Consider the following the model:
class Store(models.Model):
id = models.CharField()
products = JsonField(default='[]')
regexp = models.CharField(max_length=255)
I am using Django REST Framework and I have a serializer which serializes this model for a StoreView.
I have some validation I would like to enforce. For example, I want products to take the form: {"id":x, "optional-title":y} and I would like to enforce some regex validation for regexp.
How would I enforce validation for this model in one single place and still get correct error returns. By 'correct error returns', I mean that I should return a 400 BAD REQUEST when I receive some bad payload in from an API client but I should also return a normal Django ValidationError if I create an object on the model level.
I can't see the advantage of serializer level validation. It appears to me that I would just need to duplicate my validations in the model level if I want to guarantee that a bad object never gets into the DB.
You can define validate_<field> method within serializer class
def validate_regexp(obj,regex):
#your regex validation goes here
#valid_regex = .....
if not valid_regex:
raise serializers.ValidationError("Regex invalid")
return regex
I am very new to django and python in general, and I was trying to learn rest_framework to create RESTful APIs.
So i have a model like this:
class Listing(models.Model):
listingid = models.BigIntegerField(primary_key=True)
sellerid = models.IntegerField()
createdon = models.DateTimeField(auto_now_add=True, editable=False)
expirydate = models.DateTimeField(null=True)
validationstatus = models.SmallIntegerField(default=0)
listingstatus = models.SmallIntegerField(
choices=((0, 'Active'),
(1, 'Hidden'),
(2, 'Suspended'),
(4, 'Expired'),
(5, 'Deleted'),
),
default=0)
Now i need to validate that the expirydate is always greater than the createdon date.
I know i can do this in the views, I guess that would not be a good idea, since now the validation only exists in the views.
So that leaves me with the serializers and the model.
I know I can override the save method to do check this like so:
class MasterListing(models.Model):
# fields here..
def save(self, *args, **kwargs):
if self.expirydate > self.createdon:
super().save(*args, **kwargs)
return ValidationError("Expiry date cannot be greater than created date ("++")")
but I dont know if this would be a good idea, since now I am raising an error which the programmer may forget to catch. I am also not sure if the fields would be populated when this method would run.
Another way I read about in the docs is the clean method which i couldn't really understand so well.
Can anyone guide me on how to handle situations like this when you are working with the rest_framework?
Some of the things I have read about validation till now:
Serializer Validation
Field level validation
Validators
Model Validation
override clean method
override save method
Just do it manually in the views
There seem to be so many options, and I might have even left a few, I could not clearly get an idea of when to use where.
I am sorry if this is a little on the beginner level, but i am new to frameworks and django seems to be very different from what i was doing in PHP. Any advice is welcome!
Edit: I will be using django for the rest_framework only and nothing else, since we only want to build RESTful APIs.
Django REST framework used to call Model.clean, which was previously the recommended place for putting validation logic that needed to be used in Django forms and DRF serializers. As of DRF 3.0, this is no longer the case and Model.clean will no longer be called during the validation cycle. With that change, there are now two possible places to put in custom validation logic that works on multiple fields.
If you are only using Django REST framework for validation, and you don't have any other areas where data needs to be manually validated (like a ModelForm, or in the Django admin), then you should look into Django REST framework's validation framework.
class MySerializer(serializers.ModelSerializer):
# ...
def validate(self, data):
# The keys can be missing in partial updates
if "expirydate" in data and "createdon" in data:
if data["expirydate"] < data["createdon"]:
raise serializers.ValidationError({
"expirydata": "Expiry date cannot be greater than created date",
})
return super(MySerializer, self).validate(data)
If you need to use Django REST framework in combination with a Django component that uses model-level validation (like the Django admin), you have two options.
Duplicate your logic in both Model.clean and Serializer.validate, violating the DRY principle and opening yourself up to future issues.
Do your validation in Model.save and hope that nothing strange happens later.
but I dont know if this would be a good idea, since now I am raising an error which the programmer may forget to catch.
I would venture to say that it would be better for the error to be raised than for the saved data to possibly become invalid on purpose. Once you start allowing invalid data, you have to put in checks anywhere the data is used to fix it. If you don't allow it to go into an invalid state, you don't run into that issue.
I am also not sure if the fields would be populated when this method would run.
You should be able to assume that if an object is going to be saved, the fields have already been populated with their values.
If you would like to both Model Validation and Serializer validation using Django REST Framework 3.0, you can force your serializer to use the Model validation like this (so you don't repeat yourself):
import rest_framework, django
from rest_framework import serializers
class MySerializer(serializers.ModelSerializer):
def validate(self, data):
for key, val in data.iteritems():
setattr(self.instance, key, val)
try:
self.instance.clean()
except django.core.exceptions.ValidationError as e:
raise rest_framework.exceptions.ValidationError(e.message_dict)
return data
I thought about generating a new function from my model's clean() function's code, and have it either spit out django.core.exceptions.ValidationError or rest_framework.exceptions.ValidationError, based on a parameter source (or something) to the function. Then I would call it from the model, and from the serializer. But that hardly seemed better to me.
If you want to make sure that your data is valid on the lowest level, use Model Validation (it should be run by the serializer class as well as by (model)form classes (eg. admin)).
If you want the validation to happen only in your API/forms put it in a serializer/form class. So the best place to put your validation should be Model.clean().
Validation should never actually happen in views, as they shouldn't get too bloated and the real business logic should be encapsulated in either models or forms.
I'm trying to partially update an instance with partial=True, but no matter which of the attribute is missing I'd get an error saying This field cannot be blank.. I thought partial=True enables partial updating. Am I missing something here?
class MySerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ('meta_name', 'meta_num_files', 'meta_total_length')
...
class MyViewClass(APIView):
def post(self, request):
instance = get_object_or_404(MyModel, foo='foo')
...
serializer = MySerializer(instance, data, partial=True)
...
self.client.post(reverse('api_meta', data={'meta_name': '',
'meta_total_length': 1000000,
'meta_num_files': 10
}
)
There is a difference between partial updates, where you only update a subset of the fields on the serializer, and non-required fields, where the value can be blank.
When using partial updates, the client can send in only a subset of fields on the serializer, and the serializer will not require the ones that are not present. This has the side-effect of skipping some validation, but it expects that the fields not included in the request are already present on the model. Only the fields that are passed in with the request are validated, and only those fields are updated on the model.
You are passing in a blank value instead of not passing in the key at all. This is equivalent to doing an update that tries to clear the value of the field, which is perfectly normal in partial update situations, so Django REST Framework will run validation on the blank field. This does not appear to be documented anywhere, but that is how Django REST Framework determines what fields to run the validation on.
Because your field does not have empty=True specified, Django REST Framework will realize that it is a required field and cannot be empty. That is why you are getting an error that says This field cannot be blank, you are telling it to clear out the value of the field.