3

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:

  1. Serializer Validation
    • Field level validation
    • Validators
  2. Model Validation
    • override clean method
    • override save method
  3. 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.

kunl
  • 1,177
  • 1
  • 15
  • 25

3 Answers3

6

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.

  1. Duplicate your logic in both Model.clean and Serializer.validate, violating the DRY principle and opening yourself up to future issues.
  2. 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.

Community
  • 1
  • 1
Kevin Brown-Silva
  • 40,873
  • 40
  • 203
  • 237
  • so i guess it would be better to save it to the models `save()` method, as the model will be used by different serializers, thus maintaining DRY? – kunl Jan 22 '15 at 08:28
2

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.

Kevin Brown-Silva
  • 40,873
  • 40
  • 203
  • 237
Terje Andersen
  • 429
  • 2
  • 17
  • I don't understand... Is this an answer or a comment ? – Lorenz Meyer Mar 17 '15 at 15:57
  • I guess this should have been a comment to Kevin Brown, yes. But I didn't see the possibility to add the code block there. Anyway, I hope you'll excuse me, as this is my first post at stackoverflow :) Thank you for the links Andy. – Terje Andersen Mar 17 '15 at 21:04
  • I think the occasional 'comment' with this much code should be allowed as an 'answer' as there is no other means to post. But this is and should be rare. – Terry Jan Reedy Mar 18 '15 at 05:39
0

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.

Bernhard Vallant
  • 49,468
  • 20
  • 120
  • 148
  • Allright.. But what if my serializers are not called by other people who are not working on my module? What would be the best way to mange that?.. – kunl Jan 21 '15 at 11:33
  • Guess the only solution to make sure the validation is always executed (also when calling `save()` directly) is to put it in the `save()` method as you mentioned... – Bernhard Vallant Jan 21 '15 at 14:45
  • 1
    Model level validation (`clean`/`full_clean`) is **only called by the Django forms and admin interface**. Django REST framework no longer calls it in 3.0. – Kevin Brown-Silva Jan 21 '15 at 19:28
  • @KevinBrown that saved me in the nick of time. I was about to experiment with `clean`, and I would probably not have even detected the bug :D – kunl Jan 22 '15 at 08:26
  • Thanks at @KevinBrown, was still on an older DRF version. Here's the relevant part in the documentation: https://github.com/tomchristie/django-rest-framework/blob/d3b2302588f333b22d5e4aa2be6eca0e944e9494/docs/topics/3.0-announcement.md#differences-between-modelserializer-validation-and-modelform – Bernhard Vallant Jan 22 '15 at 14:06