Forms

The fundamental pattern for a view that handles a form is covered fully in the Django form docs, so I don’t have much to add, except a few notes:

  • You don’t need to use FormView, and I recommend you don’t.

  • You don’t actually need Form either. It’s an API that provides a very helpful set of behaviours (validation etc.), but it’s entirely possible to build forms in Django without it. You need to know how forms work at the HTML level, and you need to process request.GET or request.POST yourself to get the submitted data and do something with it.

    Normally, this would be very tedious compared to using Form, but in some cases it will be better. For example, if you have a page with dynamically generated controls (e.g. lots of buttons or input boxes) it can be easiest to build them up and process them without using Form.

  • If you need multiple buttons on the same form, that do different things, you need to understand how this works at the HTML level. The button that is pressed becomes a “successful” control, which means the request.POST (or request.GET) dictionary will get an entry with that control’s name attribute.

    So it looks like this:

    Template:

    <form action="" method="POST">
        {% csrf_token %}
        {{ form }}
        <input type="submit" name="save" value="Save">
        <input type="submit" name="preview" value="Preview">
    </form>
    

    View:

    def my_view(request):
        if request.method == 'POST':
            if 'preview' in request.POST:
                # Do preview thing...
    

    You may have to do something similar for multiple forms on one page.

That’s it! Next up: Preconditions.

Discussion: Complex form cases

Why not FormView? Of all the CBVs, it is perhaps the most tempting, due to the control flow boilerplate that it eliminates. But overall, I still feel it is not worth it.

First, it requires you to know and use a second API (get_form_class, form_valid, get_initial etc.). All of these are more awkward to use than just using Form directly.

It also makes some relatively common things much harder to do, and provides a very bad starting point for most customisations.

For example, if you find you have a page that has two forms on it (perhaps alternative flows that the user can choose between), FormView will cause you lots of pain.

Or if you have form handling as well as something else (such as a list of items), you will be in confusion if you are trying to use FormView, even more so if you’ve forgotten how to use the Form API directly.

Another example that comes up quite frequently, and described above, is when you need multiple different submit buttons which take different actions . This is an easy thing in HTML/HTTP, and easy if you are using Form directly and in charge of the control flow yourself, but horrible if you are trying to fit it into FormView.

Finally, the way that FormView obscures the flow control can be disastrous:

In 2016 some Django core developers took on the task of refactoring a function based form view (the password reset views) to use FormView view. In the process, the checking of the “magic link” token was accidentally moved to a branch such that all security was effectively disabled — a trivial curl command enabled you to reset anyone’s password to anything you liked.

Such a mistake would have been painfully obvious in the FBV, but in the CBV version, despite being authored by one core developer and reviewed by another, it went unnoticed and was committed to the master branch. It was thankfully noticed before the next release, but it highlights just how badly the use of mixins for flow control obscures your code and makes reasoning about it a nightmare.