render_to_string async

Hello All,

Trying to work out this issue with a chat website built with Django, Channels, and HTMX over an AsyncWebsocketConsumer.

class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.user = self.scope["user"]
        self.user_phone_num = self.scope['url_route']['kwargs']['user_phone_num']
        self.room_group_name = 'chat_%s' % self.user_phone_num
        user_phone_num_obj = await UserPhone.objects.aget(phone=self.user_phone_num)
        conv_obj = await Conversation.objects.aget(phonenumber=user_phone_num_obj)
        redis_online_name = self.room_group_name + '_online'
        await redis_instance.sadd(redis_online_name, self.user.first_name + ' ' + self.user.last_name)

        await self.channel_layer.group_add(
            self.room_group_name,
            self.channel_name
        )

        context = {
            "online_users": redis_instance.smembers(redis_online_name)
        }
        rendered_html = render_to_string("partials/message/hx_online_users.html", context)

        await self.channel_layer.group_send(
            self.room_group_name,
            {
                'type': 'online_users_list',
                'html': rendered_html,
            }
        )

        await self.accept()

My code renders the error:

2023-03-27 01:49:45,008 server ERROR Exception inside application: 'coroutine' object is not iterable
Traceback (most recent call last):
  File "/...//venv/lib/python3.11/site-packages/channels/routing.py", line 62, in __call__
    return await application(scope, receive, send)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/...//venv/lib/python3.11/site-packages/channels/sessions.py", line 47, in __call__
    return await self.inner(dict(scope, cookies=cookies), receive, send)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/...//venv/lib/python3.11/site-packages/channels/sessions.py", line 263, in __call__
    return await self.inner(wrapper.scope, receive, wrapper.send)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/...//venv/lib/python3.11/site-packages/channels/auth.py", line 185, in __call__
    return await super().__call__(scope, receive, send)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/...//venv/lib/python3.11/site-packages/channels/middleware.py", line 24, in __call__
    return await self.inner(scope, receive, send)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/...//venv/lib/python3.11/site-packages/channels/routing.py", line 116, in __call__
    return await application(
           ^^^^^^^^^^^^^^^^^^
  File "/...//venv/lib/python3.11/site-packages/channels/consumer.py", line 94, in app
    return await consumer(scope, receive, send)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/...//venv/lib/python3.11/site-packages/channels/consumer.py", line 58, in __call__
    await await_many_dispatch(
  File "/...//venv/lib/python3.11/site-packages/channels/utils.py", line 50, in await_many_dispatch
    await dispatch(result)
  File "/...//venv/lib/python3.11/site-packages/channels/consumer.py", line 73, in dispatch
    await handler(message)
  File "/...//venv/lib/python3.11/site-packages/channels/generic/websocket.py", line 173, in websocket_connect
    await self.connect()
  File "/...//chat/consumers.py", line 68, in connect
    rendered_html = render_to_string("partials/message/hx_online_users.html", context)
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/...//venv/lib/python3.11/site-packages/django/template/loader.py", line 62, in render_to_string
    return template.render(context, request)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/...//venv/lib/python3.11/site-packages/django/template/backends/django.py", line 61, in render
    return self.template.render(context)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/...//venv/lib/python3.11/site-packages/django/template/base.py", line 175, in render
    return self._render(context)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/...//venv/lib/python3.11/site-packages/django/test/utils.py", line 111, in instrumented_test_render
    return self.nodelist.render(context)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/...//venv/lib/python3.11/site-packages/django/template/base.py", line 1005, in render
    return SafeString("".join([node.render_annotated(context) for node in self]))
                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/...//venv/lib/python3.11/site-packages/django/template/base.py", line 1005, in <listcomp>
    return SafeString("".join([node.render_annotated(context) for node in self]))
                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/...//venv/lib/python3.11/site-packages/django/template/base.py", line 966, in render_annotated
    return self.render(context)
           ^^^^^^^^^^^^^^^^^^^^
  File "/...//venv/lib/python3.11/site-packages/django/template/defaulttags.py", line 193, in render
    values = list(values)
             ^^^^^^^^^^^^
TypeError: 'coroutine' object is not iterable

I understand my issue must be related to the fact that rendering templates is synchronous - however I have attempted to use the sync_to_async function and await, but I am met with the same error.

How can I render the template from an async function?

In this line you can see that coroutine whatever object it is, you might be using loop in the html to iterate this object also in your code I’m seeing that you are getting an object instead of array or list. Check the loop in html or wherever you are using this objects

1 Like

Wow - you led me to my answer very quickly. I truly appreciate it. The html template iterates over online_users, which you can see in

context = {
            "online_users": redis_instance.smembers(redis_online_name)
        }

I did not await this function call, so of course it returns a coroutine. Changing to the following fixed the issue:

context = {
            "online_users": await redis_instance.smembers(redis_online_name)
        }