2

I am trying to redirect to a URL taking user's pk as argument after successful log-in using Django's built-in login view.

Instead of dynamic {{ next }} variable in my login.html I have a generic landing view of logged-in users;

<input type="submit" value="login" />
<input type="hidden" name="next" value="{% url 'userredirect' %}" />

In my urls.py I have;

url(r'^users/', views.users, name='userredirect'),
url(r'^(?P<pk>\d+)/', UserHome.as_view(), name='userhome'),

and in my views.py I have

@login_required
def users(request):
    url = reverse('userhome', kwargs={'pk':request.user.id})
    return HttpResponseRedirect(url)

What I am doing here is redirect to a detail view that I have named UserHome on the user model after successful login using 2 redirects as I do not know of a way to redirect to UserHome directly (it takes user's pk as argument). It works and I indeed get redirected to the user's homepage when checking via the browser.

Reference;

The "next" parameter, redirect, django.contrib.auth.login

But when running the below test

    def test_page_redirects_to_user_home_on_login(self):
    """
    Test to assure that the login page redirects to the user's
    home page
    """
    username = "someusername"
    password = "somepassword"
    user = User.objects.create_user(username=username,
                password=password)
    user.save()
    response = self.client.post(reverse("userlogin"),
                                {"username":username,
                                 "password":password},
                                follow=True)
    assert response.path == self.client.get(reverse("userhome", 
                                                    kwargs={"pk":user.id}
                                               )
                                       )

I get the below failure

AttributeError: 'HttpResponseNotFound' object has no attribute 'path'

It seems the test client gets no page. Would it be that I am using the userredirect view simply for redirecting and the client do not go ahead and get the UserHome class view to its context.

I'm a newbie to Django/Python. Someone please sort this out for me :).

I look forward either to a way where I can redirect directly from the template for login view to UserHome or a way to rewrite my test.

Community
  • 1
  • 1
Afzal S.H.
  • 925
  • 1
  • 10
  • 24
  • Do you have an entry in your urls.py for your login url? – schillingt Apr 09 '15 at 18:08
  • Is the pk as part of GET or non visible? Like social media to view a profile? – Mikeec3 Apr 09 '15 at 18:46
  • It looks like the problem is that your `response` object has no `path` attribute. Perhaps the correct way to assert a redirect is here: http://stackoverflow.com/questions/14951356/django-testing-if-the-page-has-redirected-to-the-desired-url – Hybrid Apr 09 '15 at 19:37

2 Answers2

2

Hard to say without much more insight in your project. Here are a few possibilities and such.

Response has no path

response indeed has no path, you probably wanted this:

assert response.wsgi_request.path == reverse("userhome", kwargs={"pk":user.id})

Include next in your test

You're simulating data from the login form, but you're omitting the next field. Add it to the POSTed data:

{"username":username,
"password":password,
"next": '/users/',}

Take a look what's in the response

It might help to see what's in the response in your test. For example:

print(response.redirect_chain)

Perhaps you're not even reaching the login page?

Are you missing LOGIN_URL in your settings.py?

LOGIN_URL = '/login/'

Without it, you'll be redirected to '/accounts/login/', which might be the 404 you're seeing.


Finaly - why? :)

Perhaps you have some special use case, but I'd usually read user's id (a.k.a. pk) from request.user. That way I (for example) can't access example.com/<your_id> and access your homepage. Of course, that might be just what you intend. In that case I'd still have a separate URL for current user, it will probably pay off later. Something like this:

    ...
    url(r'^/', UserHome.as_view(), name='userhome'),
    url(r'^(?P<pk>\d+)/', UserHome.as_view(), name='userhome'),
    ...)



class UserHome(DetailView):  # also protect with some LoginRequiredMixin
    model = User

    def get_object(self, queryset=None):
        if queryset is None:
            queryset = self.get_queryset()
        id = self.kwargs.get('pk', self.request.user.id)
        return queryset.filter(id=id).get()
frnhr
  • 10,486
  • 7
  • 50
  • 76
  • You beat me to it ;-) But I think ``self.kwargs.get(pk=self.request.user.id)`` should in fact be ``self.model.objects..get(pk=self.request.user.id)`` – Emma Apr 09 '15 at 20:00
  • @Emma true, didn't test it, changed now. – frnhr Apr 09 '15 at 20:04
  • Thanx a lot frnhr `assert response.wsgi_request.path == reverse("userhome", kwargs={"pk":user.id})` is what I really wanted :). Now the test passes. Forgive me I'm totally new :). Thanx again! – Afzal S.H. Apr 10 '15 at 09:15
  • @afzal_SH if this or another answer has solved your question please consider accepting it by clicking the check-mark ([more info here](http://meta.stackexchange.com/q/5234/179419)) This indicates to the wider community that you've found a solution and gives some reputation to both the answerer and yourself. There is no obligation to do this. – frnhr Apr 10 '15 at 10:44
  • Did that :) Wasn't aware – Afzal S.H. Apr 10 '15 at 11:16
  • @Emma please check out http://stackoverflow.com/questions/30208159/asserting-for-presence-of-added-objects-in-django-manytomany-relation – Afzal S.H. May 13 '15 at 07:20
  • @frnhr please check out http://stackoverflow.com/questions/30208159/asserting-for-presence-of-added-objects-in-django-manytomany-relation – Afzal S.H. May 13 '15 at 07:20
1

First things first: The error you get is because the line

response = self.client.post(reverse("userlogin"),
                                {"username":username,
                                 "password":password},
                                follow=True)

raises a 404 error, hence resonse is a HttpResponseNotFound. Before testing anything else is it a good practice to first test that your request was successful. Something along the line of:

self.assertEqual(response.status_code, 200)

Also, you are hard-coding url's which goes against DRY and is often the source for trouble (maybe it is the case here). It would be better to name all your urls:

url(r'^users/', views.users, name='user_redirect'),

and then use this in your template

<input type="hidden" name="next" value="{% url 'user_redirect' %}" />

and this in your view

from django.core.urlresolvers import reverse
@login_required
def users(request):
    url = reverse('userhome', kwargs={'pk': request.user.id})
    return HttpResponseRedirect(url)

And finally, you are taking an unnecessary step with the redirect. Assuming UserHome is a DetailView on User, you could have this code:

##urls.py
url(r'^users/', UserHome.as_view(), name='userhome')

##views.py
from django.views.generic import DetailView
from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User


class UserHome(DetailView):
    model = User

    def get_object(self, queryset=None):
        return self.request.user

    @method_decorator(login_required)
    def dispatch(self, *args, **kwargs):
        return super(UserHome, self).disatch(*args, **kwargs)

This would also ensure that no user accesses another user's "userhome".

Doing all this should help you find what went wrong with your code. Good luck!

Emma
  • 1,071
  • 9
  • 9
  • Emma thanks a lot for your time. Thanks to you I have DRY implemented here now. But the thing is my intention here works. I do get redirected to the User's homepage, which indeed is a detail view on the user model, when checking via the browser. But in my test above the client seems to find no page (Has it got something to do with the 2 redirects?). What I need to know is why would that be. And the reason I am using 2 redirects here as I do not know of a way to redirect to my `userhome` which takes the user's pk as argument. The best thing would be if you can tell me a way :) – Afzal S.H. Apr 10 '15 at 05:09
  • And I am very new to Python/Django (this is my first project that too for study purpose mainly, not a job :)) and hence I don't understand what you're trying to convey in the code that you have for me to try at the end of your answer. – Afzal S.H. Apr 10 '15 at 05:13
  • The code at the end of my answer is a view that renders a DetailView of the currently logged-in user and which requires the user to be logged-in without the need to supply the user pk. The overrided "get_object" method (http://ccbv.co.uk/projects/Django/1.8/django.views.generic.detail/DetailView/#get_object) retrieves the user from the session instead of usual behaviour. By using this code, you get get rid of your ``users`` view. This would make things more straight-forward and easier to debug. The final answer to your problem is probably in the full stack trace. – Emma Apr 10 '15 at 07:01
  • I have it solved thanks to frnhr Emma. I am sticking with users/pk cuz intend to make it like a profile which anyone can view and has a specific address for each user. Ofcourse it will have various links which will require the user to be logged in. How to implement that is an issue for later. But thanx to you I'll now be careful about keeping up with DRY, have learned about using the get_object() and dispatch() methods and mainly getting objects from sessions. Much appreciated. Thanx a lot :) – Afzal S.H. Apr 10 '15 at 09:30