Django under the hood: validation - Loïc Bistuer

Tags: django, djangocon

(One of my summaries of a talk at the 2016 django under the hood conference).

Loïc has mostly worked on forms and the ORM.

The main concerns with validation are:

  • Enforcement.

  • User experience

  • Performance

  • Convenience

Some items go well together. Enforcement and user experience like each other. You don’t want wrong data. And you want good feedback. Validation helps with that.

But “user experience” and “performance” are harder to combine. Checks do cost time.

Similarly “user experience” and “developer convenience”. Why do you have to check anything on the backend when you already checked it on the front end? Extra work.

Where to validate data?

You can do it in the front end: javascript, html5/browser or in native code like phone apps. The nice thing is that it is fast and provides direct feedback. The drawback is that you have to do the same thing on the backend again, as you cannot trust anything coming in from the front end.

You can also use forms and the django rest framework serializer. Designed for the task, but it is easy to circumvent. Similarly django views.

You could do validation directly on the model. Only problem is that it isn’t run by default. But…. you could call .full_clean() in the model’s .save() method. That makes it harder to circumvent. Though…. bulk creating objects bypasses the save method…

Some validation can be done in the database. It is designed for the task and impossible to circumvent. And fast. But it will be backend-specific, harder to write, harder to audit and harder to maintain.

Field validation

Field validation is the bread-and-butter of django’s validation. Presence validation, for instance. required=True on a field. Similarly choice validation and range validation (max_length=100, choices=...). Uniqueness validators. Those are all set on the fields.

You can also write specific ones:

def validate_even(value):
    ....
    if ...
        raise ValidationError(...)

myfield = Field(... validators=[validate_even])

A validator is a simple function. You can also write them as a class.

You can customize the error messages raised by validations with a dict. If a ValidationError is raised, the validation mechanism looks for a code attribute on the error message. This is taken as a key that’s looked up in the dict of error messages.

ValidationErrors accept several things when instantiated. Just a string, a list (of multiple errors) or a dict, mapping field names to errors.

Example:

raise ValidationError("Invalid value %s" % 42)

Better: add translation:

raise ValidationError(_("Invalid value %s") % 42,

Even better: add a code:

raise ValidationError(_("Invalid value %s") % 42,
                      code='invalid')

Tip: a code attribute also makes it easier to test!

And to help translators, use a dict (“params”) which will be fed to the error string:

raise ValidationError(_("Invalid value %(value)s"),
                      code='invalid',
                      params={'value': 42})

But… If you don’t write a reusable app and just want to display a error string in your own views, just use the simple version.

There are some handy utilities. Form.add_error(), for instance. you pass it a field and an error. If the field is None, it will be a whole-form-level error that’s typically displayed at the top.

.add_error() was added in django 1.7 and it replaced 400 lines of documentation on how to do it yourself :-)

The errors end up in an ErrorDict. Historically you have the .as_ul() and .as_text() methods to return the errors for html or plain text. In 1.7, .as_data() and .as_json() were added to make it easier to work with. You can add a .get_json_data() method if you want to provide extra data to .as_json().

Like forms, models have a .full_clean().

If you use ModelForms, the ._post_clean() method is the glue that ties the form to the model. It creaes a model instance, but does some special handling around uniqueness.

Closing words

Where to validate? His tips:

  • Validate on your front end when practical.

  • Add mission-critical checks in the database itself.

  • And all the rest of the validation in django itself? Pick the spot where it is handiest for you. Forms, model forms, field validation, etc.

Look at django rest framework, also when thinking about validation. Django rest framework did many things right. For instance by making database models an implementation details: the externally exposed objects don’t need to correspond to a single specific data model.

geographical layers exposed

Photo explanation: geographical layers exposed in a small excavation in the German vulcanic “Eifel” region. By looking at the alternating red/black ground layers they were able to validate that the vulcano and a nearby exploding lake (“Booser maar”) were active at the same time.

water-gerelateerd Python en Django in het hartje van Utrecht!
 
vanrees.org logo

Reinout van Rees

My name is Reinout van Rees and I program in Python, I live in the Netherlands, I cycle recumbent bikes and I have a model railway.

Weblog feeds

Most of my website content is in my weblog. You can keep up to date by subscribing to the automatic feeds (for instance with Google reader):