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

1

12

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.

Idiopathy answered 14/2, 2019 at 17:12 Comment(0)
L
3

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.

Lucy answered 14/2, 2019 at 17:41 Comment(4)
That is exactly what I tend to find in all the Django examples and the built in views don't redirect - but it is pretty accepted on the web as the way to make pages so browsers don't freak out when the user refreshes - en.wikipedia.org/wiki/Post/Redirect/Get - It is like Django didn't get the memo :)Idiopathy
Again, you've completely misunderstood. Django certainly got the memo, and redirecting after a successful post is absolutely recommended everywhere in Django - it's even built in to FormViews. But not after an unsuccessful post, that would be pointless. And note, as your link explains, the point of PRG is to avoid a refresh creating a duplicate resource. If the post is invalid, that is impossible, so there is no reason to avoid it.Lucy
I am starting to get the picture. I have always tried to avoid the dreaded "do you want to report" when the user hits a refresh after a POST that fails validation. In a sense this is one of the more likely times a user will hit refresh while they are navigating.Idiopathy
@DanielRoseman ok but what happens when you have multiple forms on same page and you want to handle them with different views? Only one FormView will handle page rendering and other views will have to redirect to "main view" in both valid and invalid cases.Fulcher

© 2022 - 2024 — McMap. All rights reserved.