What i am trying to do is to nest a DRF model serializer into another model serialiser's field like so
class username_serial(ModelSerializer):
class Meta:
model = User
fields = ['username','email']
class game_serial(ModelSerializer):
user_01 = username_serial()
class Meta:
model = game
fields = ['id','user_01','user_02','is_private','is_accepted']
Error :
Exception inside application: You cannot call this from an async context - use a thread or sync_to_async. Traceback (most recent call last): File "C:\Users\baza\Desktop\production\venv\lib\site-packages\django\db\models\fields\related_descriptors.py", line 173, in get rel_obj = self.field.get_cached_value(instance) File "C:\Users\baza\Desktop\production\venv\lib\site-packages\django\db\models\fields\mixins.py", line 15, in get_cached_value return instance._state.fields_cache[cache_name] KeyError: 'user_01'
This works normally without Django Chennels because channels is async and i can't use sync code with, works fine by using:
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
In the settings file but it's not a safe approach when it comes to production. i tried using channel's database_sync_to_async as a decorator and as well as a function with a SerializerMethodField like so:
class game_serial(ModelSerializer):
user_01 = SerializerMethodField(read_only=True)
@database_sync_to_async
def get_user_01(self, obj):
username = obj.user_01.username
return str(username)
class Meta:
model = game
fields = ['id','user_01','user_02','is_private','is_accepted']
but i get back:
[OrderedDict([('id', 23), ('user_01', <coroutine object SyncToAsync.__call__ at 0x0000000005DF6678>), ('user_02', None), ('is_private', False), ('is_accepted', False)]), OrderedDict([('id', 24), ('user_01', <coroutine object SyncToAsync.__call__ at 0
x0000000005DF6D58>), ('user_02', None), ('is_private', False), ('is_accepted', False)])]
with an
Exception inside application: Object of type 'coroutine' is not JSON serializable
so i guess that JSON couldn't serialize those "coroutine" objects and normally i should or "want" to get the actual values of them instead.
How can i do the task? any workaround or other methods are welcomed, thank you in advance.
in consumers.py ..
games = await database_sync_to_async(self.get_games)()
serialized_games = game_serial(games, many=True)
await self.send({
"type": "websocket.send",
'text': json.dumps(serialized_games.data)
})
def get_games(self):
return list(game.objects.all())
I never used Django Channels but I know Django and async. I'll be honest, I don't like these hacky decorators. And I don't think running such a simple task in a thread is a good idea.
You have an obj
, so you were able to query the DB earlier. If so, there's a place without async context or async context where accessing the DB works. In the error message user_01
is not found in the "cached" object from the DB. So just prefetch what you need before.
def get_queryset(self):
return game_serial.objects.select_related('user_01')
class game_serial(ModelSerializer):
user_01 = serializers.CharField(source='user_01.username')
This way you don't have problems with this sync-to-async
magic, it's more efficient and easier to reason about.
EDIT:
I repeat, you should select related where you fetch the data. After you added another example, I can suggest something like that
def get_games(self):
return list(game.objects.select_related('user_01').all())
and it will work just fine.
You can also try
@database_sync_to_async
def get_games(self):
return list(game.objects.select_related('user_01').all())
and
serialized_games = await game_serial(games, many=True)
In both cases this serializer will work just fine.
class game_serial(ModelSerializer):
user_01 = serializers.CharField(source='user_01.username')
class Meta:
model = game
fields = ['id','user_01','user_02','is_private','is_accepted']