Unit test in Django dont see correct redirect url for successfull login?

2.4k Views Asked by At

In my Django project I create custom admin page (NOT admin from Django). I have 2 login pages. One for admin, second for other users.

Here below you can see urls.py file for admin. I test it and it works fine. After successful login Django redirect user to url which was next parameter (/administration/dashboard/).

I wrote unit test and it raise error. From error I understand that Django redirect to default url (/accounts/profile/). Why unit test dont use settings which I did in urls.py file (next parameter)?

How to fix this problem?

Right now I notice that problem disappear only if I use this code LOGIN_REDIRECT_URL = '/administration/dashboard/' in settings.py. I cant use it cause in the future I will use LOGIN_REDIRECT_URL to my other login page.

I would be grateful for any help!

urls.py:

from django.contrib.auth import views as authentication_views

urlpatterns = [
    # Administration Login
    url(r'^login/$',
        authentication_views.login,
        {
            'template_name': 'administration/login.html',
            'authentication_form': AdministrationAuthenticationForm,
            'extra_context': {
                'next': reverse_lazy('administration:dashboard'),
            },
            'redirect_authenticated_user': True
        },
        name='administration_login'),
]

tests.py:

class AdministrationViewTestCase(TestCase):
    def setUp(self):
        self.client = Client()
        self.credentials = {'username': 'user', 'password': 'password'}
        self.user = User.objects.create_user(self.credentials, is_staff=True)
        self.data = dict(
            self.credentials,
            next=reverse("administration:dashboard")
        )

    def test_administration_authorization(self):
        self.assertTrue(self.user)

        # logged_in = self.client.login(**self.credentials)
        # self.assertTrue(logged_in)

        response = self.client.post(
            reverse("administration:administration_login"),
            self.data,
            follow=True
        )
        # self.assertEqual(response.status_code, 302)
        self.assertRedirects(
            response,
            reverse("administration:dashboard"),
            status_code=302,
            target_status_code=200
        )

ERROR:

Traceback (most recent call last):
  File "/home/nurzhan/CA/administration/tests.py", line 51, in test_administration_authorization
    reverse("administration:dashboard"),
  File "/srv/envs/py27/lib/python2.7/site-packages/django/test/testcases.py", line 271, in assertRedirects
    % (response.status_code, status_code)
AssertionError: Response didn't redirect as expected: Response code was 200 (expected 302)

forms.py:

from django import forms
from django.contrib.auth.forms import AuthenticationForm
from django.utils.translation import ugettext_lazy as _


class AdministrationAuthenticationForm(AuthenticationForm):
    """
        A custom authentication form used in the administration application.
    """
    error_messages = {
        'invalid_login': (
            _("ERROR MESSAGE.")
        ),
    }
    required_css_class = 'required'

def confirm_login_allowed(self, user):
    if not user.is_active or not user.is_staff:
        raise forms.ValidationError(
            self.error_messages['invalid_login'],
            code='invalid_login',
            params={
                'username': self.username_field.verbose_name
            }
        )

login.html:

<form action="{% url 'administration:administration_login' %}" method="post">
  {% csrf_token %}
  {{ form.as_p }}
  <input type="submit" value="Submit">
  <input type="hidden" name="next" value="{{ next }}"/>
</form>
2

There are 2 best solutions below

0
On

Have you tried removing the follow=True in the POST request. You are checking for a redirection but you are telling the requests modules to follow the redirection, so your response will be directly the page and not the 302 redirection HTTP response.

To be more explicit. You are sending a request with requests.post(follow=True), that will follow the 302 redirection to the destination page and your response will be a HTTP 200 with the destination page. Even if the destination page is the one you want, the test assertion will fail because is looking for a HTTP 302 code in your response and you've already followed the redirection.

4
On

Django isn't checking next in extra_context but in GET and POST params.

extra_context is used to update your template context. So if you want to pass the values for variables to your template you can set those with extra_context.

However, you can fix your test by either setting LOGIN_REDIRECT_URL in settings.py or passing next as a POST or GET param.

params = dict(**self.credentials, next=reverse("home:index"))
response = self.client.post(
    url,
    params,
    follow=True
)

NOTE:

self.credentials should be unpacked in the dict being applied to self.data.

self.data = dict(
    **self.credentials,
    next=reverse("administration:dashboard")
)

If you wanted to test without making this change, I can recommend another way to test this.

You need to render your template in a browser and fill out the login form. This way your browser client will make the POST request with next passed as a parameter. You can get this done with a headless browser using selenium webdriver.