“Get back to where you once belonged…”

I recently ran into the situation where I wanted to be able to return from a form to the previous view.  There was not a way to add kids to the system (short of using the admin portal) and there were two obvious places that should have the ability to do that.  Of course, after the kid is added, you want to take the user back to where they were.

After a bit of searching I ended up on this SO page which has a good description of how to make this work.  I’m writing this up here as some of the examples are a bit terse and it seemed like a more detailed example might be useful.

The Idea

The general idea is that all links and referrers to the new form view will contains a GET parameter containing a link back to the referring page.  This parameter needs to get passed through the GET (or non-POST) portion of the view to be part of the form.  Then, when the user POSTs the form back to the view, it is returned there and used by the POST portion of the view to redirect to the starting point.  Got it?  Let’s walk through in code.

The Code

First, let’s start with the referring code.  I’m using simple hrefs in the referring page to bring up this form, so it’s pretty easy.

The link in the referring templates change from

<br/> <a href="{% url 'kid_new' %}">Add New Kid</a>

to

<br/> <a href="{% url 'kid_new' %}?from=
       {{ request.path|urlencode }}">Add New Kid</a>

The added “?from” portion is what builds the return info.

Moving on to the view, we’ll skip the POST portion and see what the GET portion looks like:

def new_kid(request):
    """ Create a new kid!  Sounds more exciting than it is. """
    if request.method == "POST":
        # skipped
    else:
        form = KidForm()
    return render(request, 'tasks/kid_edit.html',
                  {'form': form, 
                   'from': request.GET.get('from', None)})

You can see that it still build the same form, but passes in the ‘from’ field in the context to the template.

The template (kid_edit.html in this case) adds that to the form itself (this is the part that confused me at first).  So the form went from looking like:

    <form method="POST" class="kid-form">
        skipped...
    </form>

to:

    <form method="POST" class="kid-form"
       action="{% if from %}?next={{ from }}{% endif %}" >
         same stuff skipped ...
    </form>

The action field is what is going to pass the from field back to the view.

Finally, let’s go back and look at the rest of the view.  Here’s the entire function:

def new_kid(request):
    """ Create a new kid!  Sounds more exciting than it is. """
    if request.method == "POST":
        form = KidForm(request.POST)
        if form.is_valid():
            form.save()
            next = request.GET.get('next', None)
            if next:
                return redirect(next)
            # if for some reason it was not set, default to today
            return redirect(reverse('today'))
    else:
        form = KidForm()
    return render(request, 'tasks/kid_edit.html',
                  {'form': form, 'from': request.GET.get('from', None)})

You can see in the POST section that we do the normal form validation, then get the next field out of the POSTed form.  If it exists, we go to the referring page.  In this case, just for safety, if it does not exist we still go back to one of our two main pages.  You could also raise an error here if that was more appropriate.

The form portion of this code is exactly like any other form you’ve used.  Nothing special needs to be added there.

That’s all it takes!

Author: Jim Anderson

I'm a gray-haired coder and an avid snowboarder. I've had a long career doing all levels of software development from assembly language on 8 bit micros to Java Servlets for enterprise servers. Mainly my work consists of C/C++ on embedded systems, however. I enjoy Python coding and Django as a hobby. Almost as much fun as a powder day!

Leave a Reply

Your email address will not be published. Required fields are marked *