You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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.


1. Fixing Django Signals Not Working in Channels Workers

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:

  1. Ensure signals load across all processes
    Register your signals in your app’s ready() 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 startup
    
  2. Send 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'
        })
    

2. Pushing Notifications Only to Users with 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:

  1. 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)
    
  2. Filter users by permission and send notifications
    Create a consumer to handle the model.update events, 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)
    
  3. Update your routing
    Map your channels to the consumers in routing.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_view permission set up for My_model (you can create it via Django’s admin or guardian’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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 04:26:40