Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django: Giving validation error feedback on a form after a post redirect get

I have a good bit of experience in non-Django web development, but I am struggling to find a good practice for handling what to do when the user enters invalid data in their form and I want to re-display the form with their submitted data and the form errors displayed as well. I have a simple form with three fields and this is how I finally made it work.

def get(self, request) :
    # Check if we have been redirected...
    redirect_html = request.session.pop('form_error_html', False)
    if redirect_html : return HttpResponse(redirect_html)

    old_data = {'title': 'SakaiCar', 'mileage' : 42, 
        'purchase_date': '2018-08-14' }
    form = BasicForm(initial=old_data)
    ctx = {'form' : form}
    return render(request, 'form.html', ctx)

def post(self, request) :
    form = BasicForm(request.POST)
    if not form.is_valid() :
        ctx = {'form' : form}
        html = render_to_string('form.html', ctx, request=request)
        request.session['form_error_html'] = html
        return redirect(request.path)

    # Do something with the valid data..
    return redirect('/')

My template is really basic (I like this simplicity):

<p>
  <form action="" method="post">
    {% csrf_token %}
    <table>
    {{ form.as_table }}
    </table>
    <input type="submit" value="Submit">
  </form>
</p>

Now this approach kind of weirds me out because I am sending the entire rendered HTML page through the session from the post() to the get(). But I can't send the form with the errors in place through the session back to the the get() (that would be prettier) because it won't serialize - you get "Object of type 'BasicForm' is not JSON serializable".

I have done this a different way where I extract the errors form the form object into a list and then pass my own list of errors from the post() to the redirected get() and then alter form.html to display errors.

{% if errors %}
    {% for error in errors %}
    <p style="color:red">Error in {{ error.label }}: {{ error.message }}</p>
    {% endfor %}
{% endif %}

I have not included all the Python code to make this work - but you get the idea. This feels more elegant because I am not putting a blob of HTML into the session, but then the errors display in a way other than the normal Django forms way. And if I were using crispy forms - then all that crispy UI goodness would not come into play.

I even thought about pulling out the errors in the post() code and passing them to the get() through the session and then inserting them into the form object in the get() prior to render() - that would feel more elegant too. If I get bored I might try to dig through the forms structure and implement this as well.

I just cannot believe that with Django having so mush awesome built-in magic - that I can't just say something like return form.post_redirect_get() in the not form.is_valid code.

I want something that is a replicable pattern that is easily explained and used the Django UI elements as much as possible.

like image 531
drchuck Avatar asked Feb 14 '19 17:02

drchuck


People also ask

How does Django handle form validation error?

To create such an error, you can raise a ValidationError from the clean() method. For example: from django import forms from django. core.

How does Django validate form data?

Django provides built-in methods to validate form data automatically. Django forms submit only if it contains CSRF tokens. It uses uses a clean and easy approach to validate data. The is_valid() method is used to perform validation for each field of the form, it is defined in Django Form class.

How to use get and POST method in Django?

GET and POSTDjango's login form is returned using the POST method, in which the browser bundles up the form data, encodes it for transmission, sends it to the server, and then receives back its response. GET , by contrast, bundles the submitted data into a string, and uses this to compose a URL.


1 Answers

You're misunderstanding. You're only supposed to redirect after a successful post. On a post that fails validation, you don't redirect at all, you redisplay the invalid form - whichwill show the validation errors.

form = BasicForm(request.POST)
if form.is_valid() 
    return redirect ("/")
else:
    ctx = {'form' : form}
    return render(request, "template.htnl,", ctx)

Note, a FormView will handle all this for you; you shouldn't have to define post or get methods at all.

like image 92
Daniel Roseman Avatar answered Sep 20 '22 03:09

Daniel Roseman