'User' object has no attribute 'backend' - issue with using auth.login()

2,320 views
Skip to first unread message

Yo-Yo Ma

unread,
Sep 23, 2010, 2:38:58 PM9/23/10
to Django developers
I think I've found a bug in auth.login.


user = User.objects.get(username=request.POST.get('username', ''))
if user.check_password(request.POST.get('password', '')):
login(request, user)

This raises the following exception:

Exception: 'User' object has no attribute 'backend'

Location: C:\Python26\Lib\site-packages\django-trunk\django\contrib
\auth\__init__.py in login, line 80

Jacob Kaplan-Moss

unread,
Sep 23, 2010, 2:53:42 PM9/23/10
to django-d...@googlegroups.com
Hi --

On Thu, Sep 23, 2010 at 1:38 PM, Yo-Yo Ma <baxters...@gmail.com> wrote:
> I think I've found a bug in auth.login.

Thanks for the report.

However, for this to be useful, we're going to need a *lot* more
information -- a complete traceback, the code you used to trigger the
error, etc. Quoting from the contribution guide [1]: "Do write
complete, reproducible, specific bug reports. Include as much
information as you possibly can, complete with code snippets, test
cases, etc. This means including a clear, concise description of the
problem, and a clear set of instructions for replicating the problem.
A minimal example that illustrates the bug in a nice small test case
is the best possible bug report."

Remember: this code is used every time anyone logs into any Django
site. That means across the entire Web this code path gets executed
roughly seventy gazillion times a day. So if there is a bug in it,
it's a very specific edge-case. We need to know exactly what that edge
case is.

Thanks again!

Jacob

[1] http://docs.djangoproject.com/en/dev/internals/contributing/#reporting-bugs

Santiago Perez

unread,
Sep 23, 2010, 2:56:23 PM9/23/10
to django-d...@googlegroups.com
You need to obtain the user by calling auth.authenticate, this sets the backend to the user. 

auth.login expects an user created this way and stores the backend in the session, if you want to avoid the auth.authenticate you will have to set a backend in the user yourself.




--
You received this message because you are subscribed to the Google Groups "Django developers" group.
To post to this group, send email to django-d...@googlegroups.com.
To unsubscribe from this group, send email to django-develop...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/django-developers?hl=en.


Santiago Perez

unread,
Sep 23, 2010, 2:59:01 PM9/23/10
to django-d...@googlegroups.com
For reference: http://docs.djangoproject.com/en/dev/topics/auth/#django.contrib.auth.login (look at the note at the end of "login()" called "Calling authenticate() first")

Yo-Yo Ma

unread,
Sep 23, 2010, 3:18:13 PM9/23/10
to Django developers
Hey Jacob, understood. Here's some more details that might help:


class LoginForm(Form):
username = fields.CharField(max_length=40)
password = fields.CharField(max_length=40,
widget=widgets.PasswordInput)

def backend_login(request):
from django.contrib.auth.models import User
from django.contrib.auth import login
if request.method == 'POST':
form = LoginForm(request.POST)
if form.is_valid():
try:
user =
User.objects.get(username=request.POST.get('username', ''))
if user.check_password(request.POST.get('password',
'')):
login(request, user)
messages.success(request, 'You\'re now logged
in!')
return
HttpResponseRedirect(reverse('backend_login'))
except User.DoesNotExist:
messages.error(request, 'Incorrect username / password
combo.')
return HttpResponseRedirect(reverse('backend_login'))
else:
form = LoginForm()
data = {
'form': form,
'title': 'User Login'
}
return render_to_response('backend/base.html', data,
RequestContext(request))

Traceback:
File "C:\Python26\Lib\site-packages\django-trunk\django\core\handlers
\base.py" in get_response
100. response = callback(request,
*callback_args, **callback_kwargs)
File "D:\dev\myproject\apps\backend\views.py" in backend_login
25. login(request, user)
File "C:\Python26\Lib\site-packages\django-trunk\django\contrib\auth
\__init__.py" in login
80. request.session[BACKEND_SESSION_KEY] = user.backend

Exception Type: AttributeError at /backend/login/
Exception Value: 'User' object has no attribute 'backend'


On Sep 23, 12:53 pm, Jacob Kaplan-Moss <ja...@jacobian.org> wrote:
> Hi --
>
> [1]http://docs.djangoproject.com/en/dev/internals/contributing/#reportin...

Yo-Yo Ma

unread,
Sep 23, 2010, 3:19:37 PM9/23/10
to Django developers
It should also be noted that I don't have installed "contrib.admin" as
I don't need it. I hope that doesn't matter regarding a separate app,
but I thought I'd mention it.

On Sep 23, 12:53 pm, Jacob Kaplan-Moss <ja...@jacobian.org> wrote:
> Hi --
> [1]http://docs.djangoproject.com/en/dev/internals/contributing/#reportin...

Jacob Kaplan-Moss

unread,
Sep 23, 2010, 3:41:33 PM9/23/10
to django-d...@googlegroups.com
On Thu, Sep 23, 2010 at 2:18 PM, Yo-Yo Ma <baxters...@gmail.com> wrote:
> Hey Jacob, understood. Here's some more details that might help:
[snip]

>                if user.check_password(request.POST.get('password',
> '')):
>                    login(request, user)

As Santiago mentioned, you need to call authenticate() before calling
login(). See http://docs.djangoproject.com/en/dev/topics/auth/#django.contrib.auth.login
for details.

Jacob

David P. Novakovic

unread,
Sep 23, 2010, 5:47:41 PM9/23/10
to django-d...@googlegroups.com
This probably should have been posted to django-users anyway.

Chances are, getting a stacktrace like this one or the last error you
posted are actually problems with your code and not django itself.

Unless you can show that it is actually a problem with django and not
the way you are using it, it'd be better addressed on django-users
first.

David

Yo-Yo Ma

unread,
Sep 23, 2010, 7:02:19 PM9/23/10
to Django developers
It is a problem with Django. I thought it was a problem with the code
but it isn't. It's a problem with the documentation, or worse. An
function of an API that requires running of another function to alter
an object behind the scenes is an architectural problem that needs
fixing. See http://www.joelonsoftware.com/articles/LeakyAbstractions.html
- and furthermore, if the choice is made to leave problems like this
unfixed, they should be documented as so. The current documentation
here says, "It takes an HttpRequest object and a User object.". This
isn't true, as as simple User object will not suffice. It should say,
"It takes an HttpRequest object and a User object that has been run
through the function authenticate() first to alter the auth backends
that are attached as attributes to it.".

This has very bad code smell, IMHO.

On Sep 23, 3:47 pm, "David P. Novakovic" <davidnovako...@gmail.com>
wrote:
> This probably should have been posted to django-users anyway.
>
> Chances are, getting a stacktrace like this one or the last error you
> posted are actually problems with your code and not django itself.
>
> Unless you can show that it is actually a problem with django and not
> the way you are using it, it'd be better addressed on django-users
> first.
>
> David
>
> On Fri, Sep 24, 2010 at 5:41 AM, Jacob Kaplan-Moss <ja...@jacobian.org> wrote:
> > On Thu, Sep 23, 2010 at 2:18 PM, Yo-Yo Ma <baxterstock...@gmail.com> wrote:
> >> Hey Jacob, understood. Here's some more details that might help:
> > [snip]
> >>                if user.check_password(request.POST.get('password',
> >> '')):
> >>                    login(request, user)
>
> > As Santiago mentioned, you need to call authenticate() before calling
> > login(). Seehttp://docs.djangoproject.com/en/dev/topics/auth/#django.contrib.auth...

Yo-Yo Ma

unread,
Sep 23, 2010, 7:04:19 PM9/23/10
to Django developers
Btw, yes I am aware of the paragraph below explaining the confusing
dilemma. That's how I fixed my issue. Thank you Santiago.

On Sep 23, 5:02 pm, Yo-Yo Ma <baxterstock...@gmail.com> wrote:
> It is a problem with Django. I thought it was a problem with the code
> but it isn't. It's a problem with the documentation, or worse. An
> function of an API that requires running of another function to alter
> an object behind the scenes is an architectural problem that needs
> fixing. Seehttp://www.joelonsoftware.com/articles/LeakyAbstractions.html

David P. Novakovic

unread,
Sep 23, 2010, 7:32:24 PM9/23/10
to django-d...@googlegroups.com
Apart from being slightly offended at you posting a Joel Spolski link
to make a point, I'll address the actual issue at hand :P

These docs pretty clearly show authenticate happening before login.
Both in examples and the actual docs.

http://docs.djangoproject.com/en/dev/topics/auth/#how-to-log-a-user-in

Notice in particular:

"""
Calling authenticate() first

When you're manually logging a user in, you must call authenticate()
before you call login(). authenticate() sets an attribute on the User
noting which authentication backend successfully authenticated that
user (see the backends documentation for details), and this
information is needed later during the login process.
"""

The only time I could see this being a documentation issue is when
someone is implementing their own authenticate function but this
breaks the django convention if simply implementing a backend and
adding it to the list of auth backends and letting authenticate()
provide the actual authentication.

So yep, unfortunately this is an issue for django-users.

David

David P. Novakovic

unread,
Sep 23, 2010, 7:43:56 PM9/23/10
to django-d...@googlegroups.com
To take something constructive from this.. perhaps backend could be a
property that raises a more meaningful exception when it is called the
wrong way?

I'm not particularly for or against the idea.. but I know raising more
meaningful exceptions is an issue that has received some attention
previously.

Thoughts anyone?

Santiago Perez

unread,
Sep 23, 2010, 10:10:10 PM9/23/10
to django-d...@googlegroups.com
I'm not particularly for or against the idea.. but I know raising more
meaningful exceptions is an issue that has received some attention
previously.

Thoughts anyone?

I think its fairly simple and definitely positive. If we apply the DRY principle to the mailing lists this would be a good way to prevent the same issue being brought to the lists repeatedly. 

Yo-Yo Ma

unread,
Sep 24, 2010, 12:58:03 AM9/24/10
to Django developers
Thanks for the replies David. I didn't mean to sound brash with the
Joel stuff. It's just that the API didn't doesn't feel right. Perhaps
changing login to a method or User would fix both problems (explicit
better than implicit avoids confusion) because only a User logs in. A
User derived from authenticate() already has permission (almost) to
log in, so why not let them do it, and raise an exception if they try
when they're not .is_active?

user = authenticated('mike', 'openSesame')
user.login(request)

This prevents the problem from arising, without having to define a
__gettattr__ on User for some edge case. It also cleans up the smell.
I assume something like this could be changed in 1.4, no?


On Sep 23, 5:43 pm, "David P. Novakovic" <davidnovako...@gmail.com>
wrote:
> > On Fri, Sep 24, 2010 at 9:02 AM, Yo-Yo Ma <baxterstock...@gmail.com> wrote:
> >> It is a problem with Django. I thought it was a problem with the code
> >> but it isn't. It's a problem with the documentation, or worse. An
> >> function of an API that requires running of another function to alter
> >> an object behind the scenes is an architectural problem that needs
> >> fixing. Seehttp://www.joelonsoftware.com/articles/LeakyAbstractions.html

Harro

unread,
Sep 25, 2010, 6:13:15 AM9/25/10
to Django developers
Authentication = verification
Login = saving the authenticated user so we remember them.

Putting login on the user model is a bad idea.
That will only make the whole auth app less flexible than it already
is.
What if I have another model which isn't a user but is able to login.

Besides.. is_active does not mean you're not allowed to log in.. I'm
currently on a website where a user can register and do things on the
website right away and they won't be active till they have verified
their e-mail address. So the in active user can login and start using
the basic features right away.

Yo-Yo Ma

unread,
Sep 26, 2010, 12:12:43 AM9/26/10
to Django developers
How can an object other than a User login with that function? The
proprietary implementation of User logins is even more of a case to
out login on User, isn't it? If you cant even login a User object
without doing special things to it first, how can you expect to login
a Foo?

Will Hardy

unread,
Sep 27, 2010, 5:08:20 AM9/27/10
to django-d...@googlegroups.com
<djangoUsers>
I hope I understand your problem correctly, but authentication is
handled by your authentication backend, not your model. Your backend
can return anything you like (eg Foo) and that is what you'll get when
you call authenticate(). This object is given a .backend attribute by
django.contrib.auth, which is then used when you use
django.contrib.auth to login.
</djangoUsers>

Django's auth design is fine for a huge number of django developers, I
get the feeling that people here are skeptical that you have found an
architectural/design bug. If you're absolutely certain this isn't
possible for you, then keep pursuing this. But be wary that doing so
might distract a lot of people from other tasks, I hope it's worth it!

I like the idea of improving the exception raised when .backend is
missing. I think working on a patch to improve the exception raised
would be a perfectly sensible improvement to django.

Cheers,

Will

Tom Evans

unread,
Sep 27, 2010, 9:53:45 AM9/27/10
to django-d...@googlegroups.com
On Sat, Sep 25, 2010 at 11:13 AM, Harro <hvdk...@gmail.com> wrote:
> Authentication = verification
> Login = saving the authenticated user so we remember them.
>
> Putting login on the user model is a bad idea.
> That will only make the whole auth app less flexible than it already
> is.
> What if I have another model which isn't a user but is able to login.
>
> Besides.. is_active does not mean you're not allowed to log in.. I'm
> currently on a website where a user can register and do things on the
> website right away and they won't be active till they have verified
> their e-mail address. So the in active user can login and start using
> the basic features right away.
>

As far as django.contrib.auth is concerned it does. If your login
forms extend from django.contrib.auth.forms.AuthenticationForm, you
wouldn't be able to log in using an inactive account.

The help text for is_active also clearly indicates the intended usage
of this flag:
"Designates whether this user should be treated as active. Unselect
this instead of deleting accounts."

Of course, if you work around all that, and don't use most of the
contrib.auth framework, then you can use it to mean anything you like.


Cheers

Tom

Reply all
Reply to author
Forward
0 new messages