Jack Evans
Jack Evans

Reputation: 1717

Django redirect, but pass form error context

I have a very simple index page view, from which the user can fill in a login popup, which sends a post request to /login

def index(request):
    """Shows list of studyspaces, along with corresponding 'busyness' score"""
    context = {'study_space_list': StudySpace.objects.order_by('-avg_rating')}
    if request.user.is_authenticated():
        context['user'] = request.user
    else:
        context['login_form'] = LoginForm()
        context['user_form'] = UserForm()
        context['student_form'] = StudentForm()
    return render(request, 'spacefinder/index.html', context)

If the login is valid it simply redirects to the index page, this works fine.

The login view looks as follows:

def user_login(request):
    form = LoginForm(request.POST)
    if request.method == 'POST' and form.is_valid():
        user = form.login(request)
        if user:
            login(request, user)
            return redirect(reverse('spacefinder:index'))
    # Load the context all over again
    context = {
        'study_space_list': StudySpace.objects.order_by('-avg_rating')
    }
    context['login_form'] = form
    context['user_form'] = UserForm()
    context['student_form'] = StudentForm()
    return render(request, 'spacefinder/index.html', context)

However when the login is incorrect I want to be able to refresh the page and show the login form errors inside the index template (in the login popup)

I'm actually able to achieve this with the above code, but I'm unhappy with the solution for the following reasons:

Screenshot of behaviour here

I'm wondering if there's somehow a way to use redirect to reload the index page and somehow pass errors from my login_form, e.g. something like:

return redirect('spacefinder:index', {'login_form': form})

I've looked into using messages to pass form validation errors, but struggled to get this working since Validation Errors are thrown inside forms.py, and I'm unable to fetch the request instance from inside a ModalForm to properly create a message

Upvotes: 2

Views: 2238

Answers (1)

Risadinha
Risadinha

Reputation: 16666

You are doing it the wrong way around. Consider these prerequisites:

  1. entry point to your page is the index view
  2. the index view must only be accessible by authenticated users
  3. the login view allows both methods GET and POST and is accessible to anonymous users only

The reason to use Django is to make use of all the features that it offers, and that includes handling of the above (because that is what most pages need, not only you).

To set it up correctly you need to define your urls.py like this:

from django.contrib.auth.decorators import login_required

urlpatterns = [
    ....
    url('^login/$', user_login, 'login'),
    url('^/$', login_required(index), 'index'),
    ....
]

In your settings/base.py (or settings.py if you have no environment differentiation) tell Django how to redirect users:

LOGIN_URL = reverse_lazy('login')
LOGIN_REDIRECT_URL = reverse_lazy('index')

https://docs.djangoproject.com/en/1.9/ref/settings/#login-url https://docs.djangoproject.com/en/1.9/ref/settings/#login-redirect-url

Simplify your index view:

def index(request):
    """Shows list of studyspaces, along with corresponding 'busyness' score"""
    context = {'study_space_list': StudySpace.objects.order_by('-avg_rating')}
    if request.user.is_authenticated():
        context['user'] = request.user
    else:
        return HttpResponseForbidden()  # prevented by Django, should never happen
    return render(request, 'spacefinder/index.html', context)

Let the user_login view deliver the empty login form:

@require_http_methods(["GET", "POST"])
def user_login(request):
    params = getattr(request, request.method)
    form = LoginForm(params)
    if request.method == 'POST' and form.is_valid():
        user = form.login(request)
        if user:
            login(request, user)
            return redirect(reverse('spacefinder:index'))

    # Load the context for new form or form with errors
    context = {
        'study_space_list': StudySpace.objects.order_by('-avg_rating')
    }
    context['login_form'] = form
    context['user_form'] = UserForm()
    context['student_form'] = StudentForm()
    return render(request, 'spacefinder/index.html', context)

You have not presented any code that handles the UserForm or the StudendForm. You would need to add that to the user_login view, as well - if this is something that all users should fill in every time they login. Otherwise use a different view.

It's worth looking at modules like allauth. They might spare you some work when it comes to allowing users to login with their e-mail addresses, ascertain that e-mail addresses are unique in the system etc.

Upvotes: 1

Related Questions