73

I have the following method:

def _attempt(actor):
    if actor.__class__ != User:
        raise TypeError

Which is called from a view:

self.object.attempt(self.request.user)

As you can see, the _attempt method expects actor to be type django.contrib.auth.models.User, however the object appears to be of type django.utils.functional.SimpleLazyObject. Why is this so? And more importantly, how can I convert the LazyObject (which apparently is a kind of wrapper for a User object) into a User object?

More info on Request.user is available here: https://docs.djangoproject.com/en/dev/ref/request-response/#django.http.HttpRequest.user This documentation seems to indicate the request.user should be a User object...

======Post-edit=====

I've got the following method now:

def _attempt(obj, action, actor, msg): 
    actor.is_authenticated() 
    if isinstance(actor, LazyObject): 
        print type(actor) 

I'm passing a user, however the if condition is still true, actor is still a LazyObject. Why is this so?

Stephane Rolland
  • 38,876
  • 35
  • 121
  • 169
Himerzi
  • 899
  • 1
  • 8
  • 16

7 Answers7

46

See my answer on a similar question.

Django lazy loads request.user so that it can be either User or AnonymousUser depending on the authentication state. It only "wakes up" and returns the appropriate class when an attribute is accessed on it. Unfortunately, __class__ doesn't count because that's a primitive class attribute. There's occasions where you might need to know that this is actually a SimpleLazyObject type, and therefore it would be wrong to proxy it on to User or AnonymousUser.

Long and short, you simply can't do this comparison as you have it. But, what are you really trying to achieve here? If you're trying to check if it's a User or AnonymousUser, there's request.user.is_authenticated() for that, for example.

As a general rule though, you shouldn't abuse duck typing. A parameter should always be a particularly type or subtype (User or UserSubClass), even though it doesn't have to be. Otherwise, you end up with confusing and brittle code.

Community
  • 1
  • 1
Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • 2
    Thanks for your answer Chris. Indeed, I'm trying to check if it's a User or AnonymousUser. I've got the following method now: `def _attempt(obj, action, actor, msg): actor.is_authenticated() if isinstance(actor, LazyObject): print type(actor)` I'm passing a user, however the if condition is still true, actor is still a LazyObject. Why is this so ? – Himerzi Jul 04 '12 at 13:33
  • I realize I could do a try: on actor.isauthenticated() and then catch an AttributeException, which would indicate actor has no isauthenticated method (and thus is not an User). But I'm still a bit puzzled about why the snippet of code in my previous comment isn't working. – Himerzi Jul 04 '12 at 13:37
  • It *is* working, in one sense; `__class__` is returning exactly what it should return, `SimpleLazyObject`, because that's what the class *is*. – Chris Pratt Jul 05 '12 at 14:32
  • If I understood your original response correctly, the SimpleLazyObject should be "woken up" into a User object when an attribute is accessed on it. So, I would have thought that calling .is_authenticated(), should have turned the object into a User. Hence my confusion at the if condition I list in the first comment being false. – Himerzi Jul 10 '12 at 10:58
  • (I've added the if statement I'm referring to to my original question, so it's easier to understand) – Himerzi Jul 10 '12 at 11:00
  • 2
    We've been speaking in simplistic terms, but in reality, request.user is *always* a `SimpleLazyObject`. The instantiated user (be it `User` or `AnonymousUser`) is stored as an attribute on it, and it merely proxies the request to most attributes on itself to attributes on the user object (`__class__` being a notable exception). – Chris Pratt Jul 10 '12 at 14:23
  • Ahh I see. So using isInstance (which I assume just looks at `__class__`) on request.user will never work. Thank you for time. – Himerzi Jul 11 '12 at 15:22
  • I have created three subclasses of User. My user types are Admin, Employee and Relatives which are derivatives of User. For login, User model is used and multitable inheritance is being used. How do I convert request.user to one of the 3 types? – Vishesh Mangla Jan 24 '21 at 19:59
  • `request.user.is_authenticated` is a property, not a function. – Árni St. Steinunnarson Jun 30 '22 at 13:16
21

This should do it:

# handle django 1.4 pickling bug
if hasattr(user, '_wrapped') and hasattr(user, '_setup'):
    if user._wrapped.__class__ == object:
        user._setup()
    user = user._wrapped

I had to write this so I could add a user to the session dictionary. (SimpleLazyObjects are not picklable!)

Ashwini Chaudhary
  • 244,495
  • 58
  • 464
  • 504
UsAaR33
  • 3,536
  • 2
  • 34
  • 55
11
user= request.user._wrapped if hasattr(request.user,'_wrapped') else request.user

Then you use user instead of request.user.

This is similar to UsAaR33's answer, but a one-liner is nicer for converting the object.

André Staltz
  • 13,304
  • 9
  • 48
  • 58
  • 4
    It is similar but it doesn't attempt to called `_setup()` on the user object if the `_wrapped` part isn't initialised properly. I think that UsAaR33's answer is preferable for this reason. – MichaelJones Jun 03 '14 at 09:12
4

For anyone wanting to write a failing "small" unittest for your code, you can generate a wrapped User and stuff it inside a request.

from django.contrib.auth import get_user_model
from django.test import RequestFactory
from django.utils.functional import SimpleLazyObject

user = get_user_model().objects.create_user(
    username='jacob',
    email='jacob@…',
    password='top_secret',
)

factory = RequestFactory()
request = factory.get('/')
request.user = SimpleLazyObject(lambda: user)

See:

jamesc
  • 5,852
  • 3
  • 32
  • 42
1

This might be helpful to others and a bit cleaner.

The python method isinstance(instance, class) returns if the SimpleLazyObject's contained instance is of the provided class.

if isinstance(request.user, User):
    user = request.user
0

change your code like this and there should be no problem:

from copy import deepcopy

def _attempt(actor):
    actor = deepcopy(actor)
    if actor.__class__ != User:
        raise TypeError
Mahdi Sorkhmiri
  • 356
  • 2
  • 4
  • 16
  • Please add an explanation to what your changed code does and how it helps solve the original question. – Cray Feb 22 '20 at 10:42
0

This is the schema of the auth_user table in Django -

id
password
last_login
is_superuser
username
last_name
email
is_staff
is_active
date_joined
first_name

Now if you want id write like -

req.user.id

and if you want username write like -

req.user.username
codingbruh
  • 2,263
  • 1
  • 13
  • 14