Search code examples
djangodjango-channelsdjango-notification

Django - How to track if a user is online/offline in realtime?


I'm considering to use django-notifications and Web Sockets to send real-time notifications to iOS/Android and Web apps. So I'll probably use Django Channels.

Can I use Django Channels to track online status of an user real-time? If yes then how I can achieve this without polling constantly the server?

I'm looking for a best practice since I wasn't able to find any proper solution.

UPDATE:

What I have tried so far is the following approach: Using Django Channels, I implemented a WebSocket consumer that on connect will set the user status to 'online', while when the socket get disconnected the user status will be set to 'offline'. Originally I wanted to included the 'away' status, but my approach cannot provide that kind of information. Also, my implementation won't work properly when the user uses the application from multiple device, because a connection can be closed on a device, but still open on another one; the status would be set to 'offline' even if the user has another open connection.

class MyConsumer(AsyncConsumer):

    async def websocket_connect(self, event):
        # Called when a new websocket connection is established
        print("connected", event)
        user = self.scope['user']
        self.update_user_status(user, 'online')

    async def websocket_receive(self, event):
        # Called when a message is received from the websocket
        # Method NOT used
        print("received", event)

    async def websocket_disconnect(self, event):
        # Called when a websocket is disconnected
        print("disconnected", event)
        user = self.scope['user']
        self.update_user_status(user, 'offline')

    @database_sync_to_async
    def update_user_status(self, user, status):
        """
        Updates the user `status.
        `status` can be one of the following status: 'online', 'offline' or 'away'
        """
        return UserProfile.objects.filter(pk=user.pk).update(status=status)

NOTE:

My current working solution is using the Django REST Framework with an API endpoint to let client apps send HTTP POST request with current status. For example, the web app tracks mouse events and constantly POST the online status every X seconds, when there are no more mouse events POST the away status, when the tab/window is about to be closed, the app sends a POST request with status offline. THIS IS a working solution, depending on the browser I have issues when sending the offline status, but it works.

What I'm looking for is a better solution that doesn't need to constantly polling the server.


Solution

  • Using WebSockets is definitely the better approach.

    Instead of having a binary "online"/"offline" status, you could count connections: When a new WebSocket connects, increase the "online" counter by one, when a WebSocket disconnects, decrease it. So that, when it is 0, then the user is offline on all devices.

    Something like this

    @database_sync_to_async
    def update_user_incr(self, user):
        UserProfile.objects.filter(pk=user.pk).update(online=F('online') + 1)
    
    @database_sync_to_async
    def update_user_decr(self, user):
        UserProfile.objects.filter(pk=user.pk).update(online=F('online') - 1)