Django returns UserLazyObject: TypeError: Field 'id' expected a number but got <channels.auth.UserLazyObject object

Viewed 675

I'm using Django Channels to make a chat app following a tutorial. in my code I have a custom manager for my models. here is the manager:

from   import models
from django.db.models import Count

class ThreadManager(models.Manager):
    def get_or_create_personal_thread(self, user1, user2):
        threads = self.get_queryset().filter(thread_type='personal')
        threads = threads.filter(users__in=[user1, user2]).distinct()
        threads = threads.annotate(u_count=Count('users')).filter(u_count=2)
        if threads.exists():
            return threads.first()
        else:
            thread = self.create(thread_type='personal')
            thread.users.add(user1)
            thread.users.add(user2)
            return thread

    def by_user(self, user):
        return self.get_queryset().filter(users__in=[user])

the problem is that when i introduce the model class in my consumer, I get this unfamiliar error: TypeError: int() argument must be a string, a bytes-like object or a number, not 'UserLazyObject'

what is a UserLazyObject ? and is there any way around it?

here is my consumer.py: the model is introduce by this line of code below, commenting it or uncommenting it removes or reintroduces the error again and again.

thread_obj = Thread.objects.get_or_create_personal_thread(me, other_user)



from channels.consumer import SyncConsumer
from asgiref.sync import async_to_sync
from chat.models import Thread
from django.contrib.auth.models import User

class ChatConsumer(SyncConsumer):    
    def websocket_connect(self, event):
        # get the two users
        me = self.scope['user']
        other_username = self.scope['url_route']['kwargs']['username']
        other_user = User.objects.get(username=other_username)
        # get or create incase
        thread_obj = Thread.objects.get_or_create_personal_thread(me, other_user)
        self.room_name = 'presonal_thread_'
        async_to_sync(self.channel_layer.group_add)(self.room_name, self.channel_name)
        self.send({
            'type': 'websocket.accept'
        })
        print(f'[{self.channel_name}]  - connected now')


    def websocket_receive(self, event):
        print(f'[{self.channel_name}]  - received now {event["text"]}')
        async_to_sync(self.channel_layer.group_send)(
            self.room_name, 
            {
                'type': 'websocket.message',
                'text': event.get('text')
            }
        )
    def websocket_message(self, event):
        print(f'[{self.channel_name}] - sent now {event["text"]}')
        self.send({
            'type': 'websocket.send',
            'text': event.get('text')
        })
    
    def websocket_disconnect(self, event):
        print(f'[{self.channel_name}]  - disconnected now')
        async_to_sync(self.channel_layer.group_discard)(self.room_name, self.channel_name)

and the models.py

    from chat.managers import ThreadManager
from django.db import models

class TrackingModel(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        abstract = True

class Thread(TrackingModel):
    THREAD_TYPE = (
        ('personal', 'Personal'),
        ('group', 'Group')
    )

    name = models.CharField(max_length=50, null=True, blank=True)
    thread_type = models.CharField(max_length=15, choices=THREAD_TYPE, default='group')
    users = models.ManyToManyField('auth.User')

    objects = ThreadManager()

    def __str__(self) -> str:
        if self.thread_type == 'personal' and self.users.count() == 2:
            return f'{self.users.first()} and {self.users.last()}'
        return f'{self.name}'

class Message(TrackingModel):
    thread = models.ForeignKey(Thread, on_delete=models.CASCADE)
    sender = models.ForeignKey('auth.User', on_delete=models.CASCADE)
    text = models.TextField(blank=False, null=False)

    def __str__(self) -> str:
        return f'From <Thread - {self.thread}>'

here is my routing.py:

from channels.routing import ProtocolTypeRouter, URLRouter
from django.urls import path
from chat.consumers import EchoConsumer, ChatConsumer
from channels.auth import AuthMiddlewareStack

application = ProtocolTypeRouter({
    'websocket': AuthMiddlewareStack(
        URLRouter([
        path('ws/chat/<str:username>/', ChatConsumer.as_asgi()),
        path('ws/chat/', EchoConsumer.as_asgi() ),
        ])
    ),
})

and my views.py:

from django.shortcuts import render
from django.views import View
from django.contrib.auth import get_user_model
from django.shortcuts import Http404
from chat.models import Thread, Message


class ThreadView(View):
    template_name = 'chat/chat.html'

    def get_queryset(self):
        return Thread.objects.by_user(self.request.user)

    def get_object(self):
        other_username  = self.kwargs.get("username")
        self.other_user = get_user_model().objects.get(username=other_username)
        obj = Thread.objects.get_or_create_personal_thread(self.request.user, self.other_user)
        if obj == None:
            raise Http404
        return obj

    def get_context_data(self, **kwargs):
        context = {}
        context['me'] = self.request.user
        context['thread'] = self.get_object()
        context['user'] = self.other_user
        context['messages'] = self.get_object().message_set.all()
        return context

    def get(self, request, **kwargs):
        context = self.get_context_data(**kwargs)
        return render(request, self.template_name, context=context)

    def post(self, request, **kwargs):
        self.object = self.get_object()
        thread = self.get_object()
        data = request.POST
        user = request.user
        text = data.get("message")
        Message.objects.create(sender=user, thread=thread, text=text)
        context = self.get_context_data(**kwargs)
        return render(request, self.template_name, context=context)

here is the full traceback:

    (myenv) ➜  chat_demo git:(master) ✗ python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
December 21, 2020 - 14:51:36
Django version 3.1.4, using settings 'chat_demo.settings'
Starting ASGI/Channels version 3.0.2 development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
HTTP GET /chat/kelly1/ 200 [1.57, 127.0.0.1:49479]
WebSocket HANDSHAKING /ws/chat/kelly1/ [127.0.0.1:49482]
Exception inside application: Field 'id' expected a number but got <channels.auth.UserLazyObject object at 0x1086c5e50>.
Traceback (most recent call last):
  File "/Users/kelly/Desktop/programms/djchat/myenv/lib/python3.8/site-packages/django/db/models/fields/__init__.py", line 1774, in get_prep_value
    return int(value)
TypeError: int() argument must be a string, a bytes-like object or a number, not 'UserLazyObject'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/kelly/Desktop/programms/djchat/myenv/lib/python3.8/site-packages/channels/staticfiles.py", line 44, in __call__
    return await self.application(scope, receive, send)
  File "/Users/kelly/Desktop/programms/djchat/myenv/lib/python3.8/site-packages/channels/routing.py", line 71, in __call__
    return await application(scope, receive, send)
  File "/Users/kelly/Desktop/programms/djchat/myenv/lib/python3.8/site-packages/channels/sessions.py", line 47, in __call__
    return await self.inner(dict(scope, cookies=cookies), receive, send)
  File "/Users/kelly/Desktop/programms/djchat/myenv/lib/python3.8/site-packages/channels/sessions.py", line 254, in __call__
    return await self.inner(wrapper.scope, receive, wrapper.send)
  File "/Users/kelly/Desktop/programms/djchat/myenv/lib/python3.8/site-packages/channels/auth.py", line 181, in __call__
    return await super().__call__(scope, receive, send)
  File "/Users/kelly/Desktop/programms/djchat/myenv/lib/python3.8/site-packages/channels/middleware.py", line 26, in __call__
    return await self.inner(scope, receive, send)
  File "/Users/kelly/Desktop/programms/djchat/myenv/lib/python3.8/site-packages/channels/routing.py", line 150, in __call__
    return await application(
  File "/Users/kelly/Desktop/programms/djchat/myenv/lib/python3.8/site-packages/channels/consumer.py", line 94, in app
    return await consumer(scope, receive, send)
  File "/Users/kelly/Desktop/programms/djchat/myenv/lib/python3.8/site-packages/channels/consumer.py", line 58, in __call__
    await await_many_dispatch(
  File "/Users/kelly/Desktop/programms/djchat/myenv/lib/python3.8/site-packages/channels/utils.py", line 51, in await_many_dispatch
    await dispatch(result)
  File "/Users/kelly/Desktop/programms/djchat/myenv/lib/python3.8/site-packages/asgiref/sync.py", line 304, in __call__
    ret = await asyncio.wait_for(future, timeout=None)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/asyncio/tasks.py", line 455, in wait_for
    return await fut
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/concurrent/futures/thread.py", line 57, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/Users/kelly/Desktop/programms/djchat/myenv/lib/python3.8/site-packages/channels/db.py", line 13, in thread_handler
    return super().thread_handler(loop, *args, **kwargs)
  File "/Users/kelly/Desktop/programms/djchat/myenv/lib/python3.8/site-packages/asgiref/sync.py", line 343, in thread_handler
    return func(*args, **kwargs)
  File "/Users/kelly/Desktop/programms/djchat/myenv/lib/python3.8/site-packages/channels/consumer.py", line 125, in dispatch
    handler(message)
  File "/Users/kelly/Desktop/programms/djchat/chat_demo/chat/consumers.py", line 13, in websocket_connect
    thread_obj = Thread.objects.get_or_create_personal_thread(me, other_user)
  File "/Users/kelly/Desktop/programms/djchat/chat_demo/chat/managers.py", line 8, in get_or_create_personal_thread
    threads = threads.filter(users__in=[user1, user2]).distinct()
  File "/Users/kelly/Desktop/programms/djchat/myenv/lib/python3.8/site-packages/django/db/models/query.py", line 942, in filter
    return self._filter_or_exclude(False, *args, **kwargs)
  File "/Users/kelly/Desktop/programms/djchat/myenv/lib/python3.8/site-packages/django/db/models/query.py", line 962, in _filter_or_exclude
    clone._filter_or_exclude_inplace(negate, *args, **kwargs)
  File "/Users/kelly/Desktop/programms/djchat/myenv/lib/python3.8/site-packages/django/db/models/query.py", line 969, in _filter_or_exclude_inplace
    self._query.add_q(Q(*args, **kwargs))
  File "/Users/kelly/Desktop/programms/djchat/myenv/lib/python3.8/site-packages/django/db/models/sql/query.py", line 1358, in add_q
    clause, _ = self._add_q(q_object, self.used_aliases)
  File "/Users/kelly/Desktop/programms/djchat/myenv/lib/python3.8/site-packages/django/db/models/sql/query.py", line 1377, in _add_q
    child_clause, needed_inner = self.build_filter(
  File "/Users/kelly/Desktop/programms/djchat/myenv/lib/python3.8/site-packages/django/db/models/sql/query.py", line 1319, in build_filter
    condition = self.build_lookup(lookups, col, value)
  File "/Users/kelly/Desktop/programms/djchat/myenv/lib/python3.8/site-packages/django/db/models/sql/query.py", line 1165, in build_lookup
    lookup = lookup_class(lhs, rhs)
  File "/Users/kelly/Desktop/programms/djchat/myenv/lib/python3.8/site-packages/django/db/models/lookups.py", line 24, in __init__
    self.rhs = self.get_prep_lookup()
  File "/Users/kelly/Desktop/programms/djchat/myenv/lib/python3.8/site-packages/django/db/models/fields/related_lookups.py", line 59, in get_prep_lookup
    self.rhs = [target_field.get_prep_value(v) for v in self.rhs]
  File "/Users/kelly/Desktop/programms/djchat/myenv/lib/python3.8/site-packages/django/db/models/fields/related_lookups.py", line 59, in <listcomp>
    self.rhs = [target_field.get_prep_value(v) for v in self.rhs]
  File "/Users/kelly/Desktop/programms/djchat/myenv/lib/python3.8/site-packages/django/db/models/fields/__init__.py", line 1776, in get_prep_value
    raise e.__class__(
TypeError: Field 'id' expected a number but got <channels.auth.UserLazyObject object at 0x1086c5e50>.
WebSocket DISCONNECT /ws/chat/kelly1/ [127.0.0.1:49482]

any help will be highly appreciated. thanks in advance I have now edited the question to include the full traceback

2 Answers

First check if scope['user'] is returning anonymousUser or not. If it is returning anonymousUser then this might be the issue.

the problem for this might be if you are not logged in admin site or authenticated.

The other common problem would be using localhost:8000 port in client side and being authenticated in 127.0.0.1:8000. Make sure you are using same port.

The source code of channels shows (https://github.com/django/channels/blob/4ddf6e70f34790fb7131a10fc5af285dc3dcbeb1/channels/auth.py#L139):

class UserLazyObject(LazyObject):
    """
    Throw a more useful error message when scope['user'] is accessed before
    it's resolved
    """

    def _setup(self):
        raise ValueError("Accessing scope user before it is ready.")

and here is how the scope is populated (https://github.com/django/channels/blob/4ddf6e70f34790fb7131a10fc5af285dc3dcbeb1/channels/auth.py#L155):

def populate_scope(self, scope):
    # Make sure we have a session
    if "session" not in scope:
        raise ValueError(
            "AuthMiddleware cannot find session in scope. "
            "SessionMiddleware must be above it."
        )
    # Add it to the scope if it's not there already
    if "user" not in scope:
        scope["user"] = UserLazyObject()

async def resolve_scope(self, scope):
    scope["user"]._wrapped = await get_user(scope)

To resolve it in your code, you would have to use something along these lines (untested - might need async/await handling):

        if isinstance(user, UserLazyObject):
            user._setup()
            user = user._wrapped

Original Django code resolving a user from SimpleLazyObject for reference (working example from my non-channels code):

        if isinstance(user, SimpleLazyObject):
            user._setup()
            user = user._wrapped
Related