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

32👍

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)
👤C14L

17👍

The best approach is using Websockets.

But I think you should store not just the status, but also a session key or a device identification. If you use just a counter, you are losing valuable information, for example, from what device is the user connected at a specific moment. That is key in some projects. Besides, if something wrong happens (disconnection, server crashes, etc), you are not going to be able to track what counter is related with each device and probably you’ll need to reset the counter at the end.

I recommend you to store this information in another related table:

from django.db import models
from django.conf import settings


class ConnectionHistory(models.Model):
    ONLINE = 'online'
    OFFLINE = 'offline'
    STATUS = (
        (ONLINE, 'On-line'),
        (OFFLINE, 'Off-line'),
    )
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE
    )
    device_id = models.CharField(max_lenght=100)
    status = models.CharField(
        max_lenght=10, choices=STATUS,
        default=ONLINE
    )
    first_login = models.DatetimeField(auto_now_add=True)
    last_echo = models.DatetimeField(auto_now=True)

    class Meta:
        unique_together = (("user", "device_id"),)

This way you have a record per device to track their status and maybe some other information like ip address, geoposition, etc. Then you can do something like (based on your code):

@database_sync_to_async
def update_user_status(self, user, device_id, status):
    return ConnectionHistory.objects.get_or_create(
        user=user, device_id=device_id,
    ).update(status=status)

How to get a device identification

There are plenty of libraries do it like https://www.npmjs.com/package/device-uuid. They simply use a bundle of browser parameters to generate a hash key. It is better than use session id alone, because it changes less frencuently.

Tracking away status

After each action, you can simply update last_echo. This way you can figured out who is connected or away and from what device.


Advantage: In case of crash, restart, etc, the status of the tracking could be re-establish at any time.

👤ddiazp

1👍

My answer is based on the answer of C14L. The idea of counting connections is very clever. I just make some improvement, at least in my case. It’s quite messy and complicated, but I think it’s necessary

Sometimes, WebSocket connects more than it disconnects, for example, when it has errors. That makes the connection keep increasing. My approach is instead of increasing the connection when WebSocket opens, I increase it before the user accesses the page. When the WebSocket disconnects, I decrease the connection

in views.py

def homePageView(request):
    updateOnlineStatusi_goIn(request)
    # continue normal code
    ...


def updateOnlineStatusi_goIn(request):
    useri = request.user
    if OnlineStatus.objects.filter(user=useri).exists() == False:
        dct = {
            'online': False,
            'connections': 0,
            'user': useri
        }
        onlineStatusi = OnlineStatus.objects.create(**dct)
    else:
        onlineStatusi = OnlineStatus.objects.get(user=useri)

    onlineStatusi.connections += 1
    onlineStatusi.online = True
    onlineStatusi.save()

    dct = {
        'action': 'updateOnlineStatus',
        'online': onlineStatusi.online,
        'userId': useri.id,
    }
    async_to_sync(get_channel_layer().group_send)(
        'commonRoom', {'type': 'sendd', 'dct': dct})

In models.py

class OnlineStatus(models.Model):
    online = models.BooleanField(null=True, blank=True)
    connections = models.BigIntegerField(null=True, blank=True)
    user = models.OneToOneField(User, on_delete=models.CASCADE, null=True, blank=True)

in consummers.py

class Consumer (AsyncWebsocketConsumer):
    async def sendd(self, e): await self.send(json.dumps(e["dct"]))

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


    async def disconnect(self, _):
        await self.channel_layer.group_discard('commonRoom', self.channel_name)
        dct = await updateOnlineStatusi_goOut(self)
        await self.channel_layer.group_send(channelRoom, {"type": "sendd", "dct": dct})


@database_sync_to_async
def updateOnlineStatusi_goOut(self):
    useri = self.scope["user"]

    onlineStatusi = OnlineStatus.objects.get(user=useri)
    onlineStatusi.connections -= 1

    if onlineStatusi.connections <= 0:
        onlineStatusi.connections = 0
        onlineStatusi.online = False
    else:
        onlineStatusi.online = True
    onlineStatusi.save()

    dct = {
        'action': 'updateOnlineStatus',
        'online': onlineStatusi.online,
        'userId': useri.id,
    }
        
    return dct
👤T H

Leave a comment