I am having difficulties in sending all instances of a model through to a Websocket

Hi I am trying to send all instances of an Model through a websocket, following is my code

#consumer.py

class ChangeBoatConsumer(AsyncWebsocketConsumer):

    @database_sync_to_async
    def all_boats(self):
        boats = Boat.objects.all()
        return boats
    
    async def connect(self):
        await self.channel_layer.group_add('all_boats', self.channel_name)
        await self.accept()

        boats = await self.all_boats()

        boats_serializer = BoatSerializer(boats, many=True)
        boats_data = boats_serializer.data

        await self.send(json.dumps({
            "boat": boats_data,
        }))

#models.py

class Boat(models.Model):
    name = models.CharField(max_length=200)
    type = models.CharField(max_length=200)
    captain = models.OneToOneField(Captain,on_delete=models.CASCADE, null=True, blank=True)


class Captain(models.Model):
    name = models.OneToOneField(Person, on_delete=models.CASCADE)
    date_of_birth = models.DateField(null=True, blank=True)
    joined_date = models.DateField(null=True, blank=True)

#Serializer.py

class BoatSerializer(serializers.ModelSerializer):
    captain = serializers.StringRelatedField()

    class Meta:
        model = Boat
        fields = ['name','type', 'captain',]

class CaptainSerializer(serializers.ModelSerializer):
    name = serializers.CharField(source='name.username')
    first_name = serializers.CharField(source='name.first_name')
    last_name = serializers.CharField(source='name.last_name')

    class Meta:
        model = Captain
        fields = ['name','date_of_birth', 'joined_date', ]

and when I visit the link of my consumer I get this message:

raise SynchronousOnlyOperation(message) django.core.exceptions.SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async. WebSocket DISCONNECT /ws/changeboat/

What am I doing wrong here, is there something I am missing?

My guess is that it’s complaining about the implicit reference to the Captain model from the Boat queryset. The instances of Captain aren’t going to be resolved until referenced by the serializer. (Actually, it’s probably complaining about the unresolved queryset itself being returned. Boat.objects.all() by itself does not result in a query being executed. Querysets are lazy.)

The easiest way that I can think of around this would be to move all the database and serializer work into the all_boats function to where it’s returning boats_data.

Side note: You probably also want to add a select_related call to your query to prevent an “N + 1” situation in your serializer.

Thanks for the pointers, it was indeed The Captain model that was raising the error, I have updated it as following:

class ChangeBoatConsumer(AsyncWebsocketConsumer):

    @database_sync_to_async
    def all_boats(self):
        boats = Boat.objects.select_related('captain').all()
        boats_data = [
            {
                "captain": boat.captain.name.username if boat.captain else None,
                "name": boat.name,
            }
            for boat in boats
        ]
        return boats_data

    async def connect(self):
        await self.channel_layer.group_add('all_boat', self.channel_name)
        await self.accept()

        boats = await self.all_boats()

        await self.send(json.dumps({"boats": boats}))

this code is working for the moment , however I think will need to come up better maintainable solution. Will work on implementing your suggestions to improve this simple snippet.
Again thanks for the pointers :+1:

This is my Updated Code with your suggestions, it is working perfectly and as expected, Can please share feedback, anywhere I can improve for best practices.

class ChangeBoatConsumer(AsyncWebsocketConsumer):

    @database_sync_to_async
    def all_boats(self):
        boats = Boat.objects.select_related('captain').all()
        boat_serializer = BoatSerializer(boats, many=True)
        boat_data = boat_serializer.data
        return boat_data

    async def connect(self):
        await self.channel_layer.group_add('all_boat', self.channel_name)
        await self.accept()

        boats = await self.all_boats()

        await self.send(json.dumps({"boats": boats}))

Thanks.