Django信号在Worker上失效,求助WebSocket消息ACL权限控制方案
Hey Antonio, let's tackle these two issues one by one to get your WebSocket notifications working correctly with Django 1.9, Channels 1.1.8, and django-guardian 1.4.4.
The core problem here is that Channels 1.x workers run as separate processes from your main Django app. Signals registered only in the main process won’t trigger properly in workers, and direct WebSocket pushes from signals fail because they can’t access the channel layer across process boundaries.
The solution is to decouple signal logic from WebSocket pushing: use the channel layer to pass update events to workers, then let a dedicated consumer handle the actual notification.
Step-by-Step Implementation:
Ensure signals load across all processes
Register your signals in your app’sready()method so both the main app and workers load them:# myapp/apps.py from django.apps import AppConfig class MyAppConfig(AppConfig): name = 'myapp' def ready(self): import myapp.signals # Force signal registration on app startupSend events to a dedicated channel in signals
Instead of pushing WebSocket messages directly, send a lightweight event to a Channels channel:# myapp/signals.py from django.db.models.signals import post_save, post_delete from django.dispatch import receiver from channels import Channel from myapp.models import My_model @receiver(post_save, sender=My_model) def handle_model_save(sender, instance, created, **kwargs): Channel('my_model_updates').send({ 'type': 'model.update', 'instance_id': instance.id, 'action': 'created' if created else 'updated' }) @receiver(post_delete, sender=My_model) def handle_model_delete(sender, instance, **kwargs): Channel('my_model_updates').send({ 'type': 'model.update', 'instance_id': instance.id, 'action': 'deleted' })
can_view Permission To target only users with access to the My_model instance, you’ll need to: track active WebSocket connections per user, fetch eligible users via django-guardian, and send messages only to their active channels.
We’ll use Redis (the standard channel layer backend for Channels 1.x) to track user-channel mappings.
Step-by-Step Implementation:
Track WebSocket connections
Update your WebSocket consumer to register/unregister user-channel mappings when connections open/close:# myapp/consumers.py import redis from channels.auth import channel_session_user_from_http from django.conf import settings from guardian.shortcuts import get_users_with_perms from myapp.models import My_model # Initialize Redis connection (match your Channels config) redis_conn = redis.Redis( host=settings.REDIS_HOST, port=settings.REDIS_PORT, db=settings.REDIS_DB ) @channel_session_user_from_http def ws_connect(message): if message.user.is_authenticated: # Store user ID → channel name mapping in Redis redis_conn.hset('user_channels', message.user.id, message.channel.name) @channel_session_user_from_http def ws_disconnect(message): if message.user.is_authenticated: # Clean up the mapping when the connection closes redis_conn.hdel('user_channels', message.user.id)Filter users by permission and send notifications
Create a consumer to handle themodel.updateevents, fetch eligible users, and push messages to their channels:# myapp/consumers.py (continued) def model_update(message): try: instance = My_model.objects.get(id=message['instance_id']) except My_model.DoesNotExist: return # Skip if the instance was deleted before processing # Get all users with `can_view` permission on this instance eligible_users = get_users_with_perms( instance, attach_perms=False, only_with_perms_in=['can_view'] ) # Prepare your notification payload notification = { 'type': 'notification', 'model': 'My_model', 'instance_id': instance.id, 'action': message['action'], 'data': { 'name': instance.name # Add any instance data you want to send } } # Send the notification to each eligible user's active channel for user in eligible_users: channel_name = redis_conn.hget('user_channels', user.id) if channel_name: # Send directly to the user's WebSocket channel Channel(channel_name.decode('utf-8')).send(notification)Update your routing
Map your channels to the consumers inrouting.py:# myapp/routing.py from channels.routing import route from myapp.consumers import ws_connect, ws_disconnect, model_update channel_routing = [ # WebSocket connection routes route('websocket.connect', ws_connect), route('websocket.disconnect', ws_disconnect), # Model update event route route('model.update', model_update), ]
Key Notes:
- Ensure your Channels settings are configured to use Redis as the channel layer.
- Verify django-guardian has the
can_viewpermission set up forMy_model(you can create it via Django’s admin orguardian’s helper functions). - If users might have multiple active WebSocket connections, modify the Redis mapping to store a set of channel names per user instead of a single value.
内容的提问来源于stack exchange,提问作者Antonio Rak

