mirror of
https://github.com/tanrax/django-channels-more-than-present
synced 2026-01-08 06:13:39 +01:00
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
131 lines
4.1 KiB
Python
131 lines
4.1 KiB
Python
import json
|
|
from datetime import timedelta
|
|
|
|
from django.db import models
|
|
from django.conf import settings
|
|
from django.contrib.auth import get_user_model
|
|
from django.utils.timezone import now
|
|
|
|
from asgiref.sync import async_to_sync
|
|
from channels.layers import get_channel_layer
|
|
from channels_presence.signals import presence_changed
|
|
|
|
channel_layer = get_channel_layer()
|
|
|
|
|
|
class PresenceManager(models.Manager):
|
|
def touch(self, channel_name):
|
|
self.filter(channel_name=channel_name).update(last_seen=now())
|
|
|
|
def leave_all(self, channel_name):
|
|
for presence in self.select_related("room").filter(channel_name=channel_name):
|
|
room = presence.room
|
|
room.remove_presence(presence=presence)
|
|
|
|
|
|
class Presence(models.Model):
|
|
room = models.ForeignKey("Room", on_delete=models.CASCADE)
|
|
channel_name = models.CharField(
|
|
max_length=255, help_text="Reply channel for connection that is present"
|
|
)
|
|
user = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE
|
|
)
|
|
last_seen = models.DateTimeField(default=now)
|
|
|
|
objects = PresenceManager()
|
|
|
|
def __str__(self):
|
|
return self.channel_name
|
|
|
|
class Meta:
|
|
constraints = [
|
|
models.UniqueConstraint(
|
|
fields=["room", "channel_name"],
|
|
name="unique_presence_room_channel"
|
|
)
|
|
]
|
|
|
|
|
|
class RoomManager(models.Manager):
|
|
def add(self, room_channel_name, user_channel_name, user=None):
|
|
room, created = Room.objects.get_or_create(channel_name=room_channel_name)
|
|
room.add_presence(user_channel_name, user)
|
|
return room
|
|
|
|
def remove(self, room_channel_name, user_channel_name):
|
|
try:
|
|
room = Room.objects.get(channel_name=room_channel_name)
|
|
except Room.DoesNotExist:
|
|
return
|
|
room.remove_presence(user_channel_name)
|
|
|
|
def prune_presences(self, channel_layer=None, age=None):
|
|
for room in Room.objects.all():
|
|
room.prune_presences(age)
|
|
|
|
def prune_rooms(self):
|
|
Room.objects.filter(presence__isnull=True).delete()
|
|
|
|
|
|
class Room(models.Model):
|
|
channel_name = models.CharField(
|
|
max_length=255, unique=True, help_text="Group channel name for this room"
|
|
)
|
|
|
|
objects = RoomManager()
|
|
|
|
def __str__(self):
|
|
return self.channel_name
|
|
|
|
def add_presence(self, channel_name, user=None):
|
|
if user and user.is_authenticated:
|
|
authed_user = user
|
|
else:
|
|
authed_user = None
|
|
presence, created = Presence.objects.get_or_create(
|
|
room=self, channel_name=channel_name, user=authed_user
|
|
)
|
|
if created:
|
|
async_to_sync(channel_layer.group_add)(self.channel_name, channel_name)
|
|
self.broadcast_changed(added=presence)
|
|
|
|
def remove_presence(self, channel_name=None, presence=None):
|
|
if presence is None:
|
|
try:
|
|
presence = Presence.objects.get(room=self, channel_name=channel_name)
|
|
except Presence.DoesNotExist:
|
|
return
|
|
|
|
async_to_sync(channel_layer.group_discard)(
|
|
self.channel_name, presence.channel_name
|
|
)
|
|
presence.delete()
|
|
self.broadcast_changed(removed=presence)
|
|
|
|
def prune_presences(self, age_in_seconds=None):
|
|
if age_in_seconds is None:
|
|
age_in_seconds = getattr(settings, "CHANNELS_PRESENCE_MAX_AGE", 60)
|
|
|
|
num_deleted, num_per_type = Presence.objects.filter(
|
|
room=self, last_seen__lt=now() - timedelta(seconds=age_in_seconds)
|
|
).delete()
|
|
if num_deleted > 0:
|
|
self.broadcast_changed(bulk_change=True)
|
|
|
|
def get_users(self):
|
|
User = get_user_model()
|
|
return User.objects.filter(presence__room=self).distinct()
|
|
|
|
def get_anonymous_count(self):
|
|
return self.presence_set.filter(user=None).count()
|
|
|
|
def broadcast_changed(self, added=None, removed=None, bulk_change=False):
|
|
presence_changed.send(
|
|
sender=self.__class__,
|
|
room=self,
|
|
added=added,
|
|
removed=removed,
|
|
bulk_change=bulk_change,
|
|
)
|