I’ve recently started learning to program in Python with a Django front-end, and I’ve found that while most problems I come across can very easily be solved by looking at Django’s documentation, or by finding an answer on StackOverflow, occasionally I’ll come across a problem that I have to Google around for hours to figure out, piecing together solutions from all sorts of different sources. So I thought, I’ll post the solutions I finally figure out, and maybe, sometime down the road, some other Django newbie will find my article and it might save them a couple of hours.
With that, I give you my very first Django mini-tutorial!
Today’s problem was about redirecting. Whenever a view in Django uses the very handy @login_required
decorator, you may notice that the URL for the login page contains a URL parameter indicating where the user should be redirected after they log in. For example, the URL might be www.domain.com/login/?next=/profile/5/
, indicating that when the user completes logging in they should be redirected to user 5’s profile page. That’s all well and good… as long as the user already has an account. But if you wanted to have a sign up link at the bottom of your sign in form to allow the user to register, then the context will be lost. So my goal was to pass the “next” parameter on to the create account link so that the user will still be redirected to user 5’s profile after creating their shiny new account.
So first things first, let’s pass the parameter on to the create account page. I’m starting with a custom login template, which I’ve created based on Django’s example login.html template.
Heads up! Throughout this post I have spaces in my HTML tags – this is just to work around a WordPress bug. If you copy the HTML code please be sure to remove the spaces. Thanks!
<form role="form" class="form-horizontal section" method="post" action="{% url 'django.contrib.auth.views.login' %}"> {% csrf_token %} < div class="section">{{ form|crispy }}< /div> <input class="btn btn-primary btn-lg btn-block" type="submit" value="Sign in" /> <input type="hidden" name="next" value="{{ next }}" /> </form> < div class="m-b-1 text-xs-center"> < a href="{% url 'password_reset' %}">< small>Forgot your password?< /small>< /a>< /div>
So the key thing here is the hidden field with the value of {{ next }}
. So if the “next” URL parameter is passed in, we can tell that the value will be available in that variable. We can use that variable to pass on the parameter to the sign in page. After the forgot password div, I added this statement:
< div class="text-xs-center">< small>Don't have an account? < a href="{% url 'user_add' %}{% if next %}?next={{ next }}{% endif %}">Sign up< /a>< /small>< /div>
Simple enough ā if we have a value for next we just pass it on to the sign up page using the same URL parameter.
Now we need to add the same hidden field to the sign up page to interpret where we need to go next. Just add the following code to the sign up form. (Important: make sure the field is within the <form>
tags so that it’s included in the form POST request.)
<input type="hidden" name="next" value="{{ next }}" />
Next we’ll need to update the view for the sign up page. The first thing you’ll want to update on your view is to actually pass in the value of the “next” URL parameter to the template. This will make sure that the {{ next }} variable is actually populated in your hidden field. The default render statement should pass in the variable as part of the context.
return render(request, "user/user_add.html", {'form': form, 'next': request.GET.get('next', '')})
Make sure you include that fallback value of ” in the request.GET.get() call ā that way, if the URL parameter isn’t present we can tell our code to do something else.
When the form is posted back, the value of the next variable is now part of the POST request. So, when we finalize the form function we’ll use that value to redirect. One important thing to do here is to make sure that the URL is safe. Without checking the URL attackers could use our site to redirect users to their own sites in an attempt to trap users into revealing their passwords. Luckily, Django includes a handy is_url_safe()
method to check the URL before redirecting. Here’s the last bit of code we need:
# get the redirect location redirect_to = request.POST.get('next', '') url_is_safe = is_safe_url(redirect_to) if redirect_to and url_is_safe: return redirect(redirect_to) return redirect(new_user)
That’s it! With these few lines of code we can now make sure our users get where they need to be after they create their new accounts.
Of course this doesn’t have to be limited to just authentication scenarios. Sometimes you might want to redirect a user to a different location after submitting any form. This code will work just fine in any form-based workflow.
Do you have an interesting way you’re going to use this functionality? Know a better way to accomplish the same thing? Let me know in the comments!
It doesn’t work for me with the error: ‘str’ object is not callable
How can this be? I am passing a string (my view name) in the redirect_to variable.
Kind regards,
LikeLike
Hi Ben,
There are several possibilities for what could cause this issue. My first suggestion would be to make sure you call your view with its full dot-path name, such as my_app.views.my_view.
Hope that helps,
Andrea
LikeLike