Why does Django Rest Framework discourage model level validation? - python

Django Rest Framework serializers do not call the Model.clean when validating model serializers. The explanation given is that this leads to 'cleaner separation of concerns', from the Django Rest Framework 3.0 release notes:
Differences between ModelSerializer validation and ModelForm.
This change also means that we no longer use the .full_clean() method
on model instances, but instead perform all validation explicitly on
the serializer. This gives a cleaner separation, and ensures that
there's no automatic validation behavior on ModelSerializer classes
that can't also be easily replicated on regular Serializer classes.
But what concerns are the authors of Django Rest Framework attempting to separate?
My guess is that they're saying that a model instance should not be concerned about it's own validity. If that's the case I don't understand why.

There are two major issues with the model's "full_clean".
The first one is technical. There are a couple of cases where the full_clean isn't called at all. For example, you'll bypass it when you do a queryset.update().
The second one is that if you have a complex business logic - which is usually why you'll have a full_clean - it's likely that you should do the validation in the business logic, not go down to the models to validate.
Each layer should be responsible for its own consistency and the storage layer - ie models - shouldn't care about the business layer.
Another thing that I can think of is that full_clean will be called once you have a model that comes after the serializer has been doing its validation. At this point, things start getting messy because you have a two-step validation with an object created in between.
If you're using nested serializer, you might be stuck here because you won't be able to create nested models before the primary model has been saved which will make the full clean call even messier - some objects will be created, others will not. It's hard to figure out when and what object should be validated with their full_clean and you can be sure there'll be a lot of complaints from users when they'll override the update/clean and figure out the full_clean hasn't been called for every model.
This started becoming a total headache and we prefer to keep things simpler and more explicit.

Related

Is there any relationship between model's clean() method and form's clean() method?

I have overwritten clean() methods for some of my models to construct constraints to meet my DB schema requirements (Because it required runtime information for those validations).
Since now I have finished most of the back-end side components(models, signals, ..) now I'm trying to write ModelForms for my models.
What I'm wondering is that, is there any relationship between the clean() method of model and clean() implementation on the form side?
If so and form's clean() calls model's clean() I won't have to rewrite my model - side clean() implementation and be able to avoid code redundancy.
Yes, ModelForm cleaning involves model cleaning. That's the idea with a ModelForm: there are a lot of useful defaults that can be determined by auto building a form object from a model.
I've discovered this clean chaining through personal experience, but to back it up I can reference the source.
On 1.8, ModelForms call the model instance full_clean method. On 1.7, it calls the clean method directly.
Form.full_clean()
def full_clean(self):
# ..... snip
self._clean_fields()
self._clean_form()
self._post_clean()
ModelForm._post_clean for 1.8
Model full_clean() calls clean() amongst other validation: https://docs.djangoproject.com/en/1.8/ref/models/instances/
self.instance.full_clean(exclude=exclude, validate_unique=False)
ModelForm._post_clean for 1.7
self.instance.clean()
According to Model.clean and ModelForms clean, I don't think there is any relationship between them.
These two clean has the same name but they did a different job.
Model.clean is used to validate the data you are going to store into database, and make sure the data is ok and can be stored into database.
This method should be used to provide custom model validation, and to modify attributes on your model if desired.
ModelForms clean, by my understanding, is to validate what the user has entered, and make sure they are ok.
You can override the clean() method on a model form to provide additional validation in the same way you can on a normal form.
And on a normal form, it's:
Implement a clean() method on your Form when you must add custom validation for fields that are interdependent.
And I think this one is also what you wanted:
Notice that we are talking about the clean() method on the form here, whereas earlier we were writing a clean() method on a field. It’s important to keep the field and form difference clear when working out where to validate things. Fields are single data points, forms are a collection of fields.

What's wrong with putting all your custom validation in your model?

Django's ModelForms are cool, but I'm having trouble wrapping my head around why you'd ever want to put data integrity rules in your forms, when they could just be in the models themselves, per https://stackoverflow.com/a/18876223/1207253, who goes on to write "This isn't done by default, as explained here, because it interferes with certain features..."
I've read through the cited links and https://github.com/danielgatis/django-smart-save and still don't understand why this isn't the recommended approach. What are the features this approach interferes with? Excluded fields works. Default values works. What am I missing?
Only downside I can think of is that full_clean or clean (whichever you call in save) will be called twice if saving models through admin. There shouldn't be any side effects as such.
Form validation puts restriction on how the user enters data, Model validation specifies how that data must be processed / validated further before storing it in the db. So, unless your forms are very complicated, validating in the model works just fine.
Use whichever you are most comfortable with and adhere to it throughout the project as there is no strong convention here.

How to use full_clean() for data validation before saving in Django 1.5 gracefully?

I think Django's model validation is a little inconvenient for those models that don't use built-in ModelForm, though not knowing why.
Firstly, full_clean() needs called manually.
Note that full_clean() will not be called automatically when you call
your model’s save() method, nor as a result of ModelForm validation.In
the case of ModelForm validation, Model.clean_fields(), Model.clean(),
and Model.validate_unique() are all called individually.You’ll need to
call full_clean manually when you want to run one-step model
validation for your own manually created models.
Secondly, validators are used in built-in ModelForm.
Note that validators will not be run automatically when you save a
model, but if you are using a ModelForm, it will run your validators
on any fields that are included in your form.
There are great demands when you need to do data validation before saving data into databases. And obviously I'd prefer to make it in model, rather than views. So, are there any good ideas to implement this gracefully in Django 1.5?
Even though the idea of enforcing validation on Model level seems right, Django does not do this by default for various reasons. Except for some backward-compatibility problems, the authors probably don't want to support this because they fear this could create a false feeling of safety when in fact your data are not guaranteed to be always validated. Some ORM methods (e.g. bulk_create or update) don't call save() and thus are unable to validate your models.
In other words, it is hard to guarantee the validation, thus they've decided not to pretend it.
If you need this for multiple models, you can create a simple mixin that overrides the save() method and calls full_clean() before super. Do note that this might cause the validation to be run twice in some cases, like when using ModelForm. It might not be that of an issue though if your validation routines are side-effect free and cheap to run.
For more info, please see these answers:
https://stackoverflow.com/a/4441740/2263517
https://stackoverflow.com/a/12945692/2263517
https://stackoverflow.com/a/13039057/2263517

Django best practices - where should form processing logic (for creating / updating models) go

I am trying to make my Django app follow better practices than it currently does. I hear about fat models and thin controllers a lot (after putting much of my display logic in views.py initially).
Anyway, say I have a moderately complex form, for creating new objects, including foreign keys on the new objects. Assuming there are no validation errors, where should the logic for creating / linking models go? views.py after is_valid() seems the easiest place to put, but I am not sure if that is considered best practice.
In the question title you describe it as "form processing logic", but it sounds from your question text like you really mean inter-model business logic.
If it's form processing logic (cleaning, etc.), that should go on the form.
Since it sounds like you're talking about business logic, it should generally be added to the appropriate model as a model method (Django docs on model methods), then called from either the custom form logic (e.g., on save) or from the view.
Of course, where the code lives depends to a great degree on the structure of relationship between your models. Say you have an author model with a one-to-many relationship to a book model. The author model might have a method that helps you create a new book object, filling in the foreign key relation as it goes. A more complex relationship might require more view code, or at least more thought.
And yes, in general it's a good idea to try to keep your views slimmer and your models fatter.

How to define a Model with fields filled by other data sources than database in django?

Does anyone can tell me if it's possible to create a Model class, with some model fields and some other fields taking their data from external data sources. The point is that I would like this model to be exploited the same way as another model by ModelForm for instance. I mean if I redefine "objects" Manager of the model by specifying the actions to get the datas for special fields (those who may not be linked to datas from the database), would the modelForm link the input with the fields not attached to the database ? Similar question about related objects. If I have a Model that has a relation with that special Model, can I get this Model instances through the classic way to get related objects (with both the classic model fields and the non-database fields) ?
Please tell me if I'm not clear, I'll reformulate.
Thanks.
EDIT: I tried to make a Model with custom fields, and then override the default Manager and its functions: all, get, ... to get objects like it would be with classical Model and Manager, it works. However, I don't use QuerySet, and it seems that the only way to get ModelForm, related objects and the admin functionnalities, working with it, is to build the QuerySet properly and let it being returned by the manager. That's why now I'm wondering if it's possible to properly and manually build a QuerySet with data got from external sources, or tell django-admin, model forms and related objects to take care of another class than queryset on this Model.
Thanks
The way is to define custom methods:
Define custom methods on a model to add custom "row-level"
functionality to your objects. Whereas Manager methods are intended to
do "table-wide" things, model methods should act on a particular model
instance.
This is a valuable technique for keeping business logic in one place
-- the model.
I have now a partial solution. I override the Manager and in particular its all() and get() functions (because I only need those functions for now). all() returns a queryset in which I added the result of some logics that give me objects build from external datas (taken through xmlrpc in my case). I added those objects to the qs through _result_cache attribute.
I think it's not clean and in fact my Model is now a custom Model and I don't have any database field. I may use it to fill database Models... However I can use it the same way as classic models: MyModel.objects.all() for example.
If anyone has another idea I'd really appreciate.
Regards

Categories

Resources