Model Instance not being returned in a Django-Channels WebsocketConsumer Initiated by a post_save signal

I am working on a project where I would like to update the the DOM with a combination of Django post_save signal, django-channels and websockets.

I have set up a signal to to listen for a Notification save, the code here:

@receiver(post_save, sender=Notification)
def display_notification_on_creation(sender, instance, **kwargs):
    channel_layer = get_channel_layer()
    group_name = "banner-notifications"

    id = instance.id
    str_id = id.hex
    h = Notification.objects.get(id=instance.id)
    print("\n","model", h)


    event = {
        "type": "notification_create",
        "notification_id": str_id
    }

    async_to_sync(channel_layer.group_send)(group_name, event) 

This code connects to a django-channels’ WebsocketConsumer that has the following funciton:

def notification_create(self, event):
        notification_id = event["notification_id"]

        not_i = uuid.UUID(notification_id)
        print("here", "\n", "\n", type(not_i), notification_id)
        now = datetime.datetime.now()

        new_notification = Notification.objects.filter(
            id=not_i,
            translations__language_code=self.language

        ).first()

        print("\n", "in consumer", new_notification)
        # need a check for if the notification should currently be show

        html = get_template("notification_detail_banner.html").render(
            context={ "notification": new_notification }
        )

        self.send(text_data=html)

The Problem:

The filter in the notification_create function does not always return a value, instead it sometimes returns None. The reason the filter is required is to capture the user’s selected language, otherwise I would pass a serialized version of the Notification instance in the event.

I am not sure what is causing the issue, I added the get statement in the signal as a check to see if the instance was being inserted into the database, and it appears to be the case, further I have looked through my logs (included below) and can see that the Notification object has been inserted before the print statement on the returned

Additionally there is a not problem when updating an instance, which further makes me think that it has to do with the insertion into the database.

As a side note that may be relevant, I’m not sure if the issue is caused due to using django-parler, which stores its translations in a separate table, but it seems like the object itself is not being returned not just the translated fields.

I am not sure what direction I should go at this point, if I should add a while loop and a timer to wait for the instance. to be returnd (and have a timeout that would break the loop in the event that the insert failed). Or if there is something I am missing.

Logs:

Update:

I added/modified the following code, it consistently works now, but I don’t think it is best practice

any advice?

        now = datetime.datetime.now()
        timeout = time.time() + 1
        new_notification = None
        while time.time() < timeout and new_notification == None:
            new_notification = Notification.objects.filter(
                id=not_i,
                translations__language_code=self.language

            ).first()

The object itself won’t be returned if the query fails because the filter doesn’t find a row - and it won’t find a row if the translation hasn’t completed.

<conjecture>
Now, I don’t know django-parler at all - but I’m guessing it may also be triggered by a signal for data needing to be translated. Either that, or it ties into the save method of the model being translated - in either case, the signal method is being called prior to the translations occuring.
</conjecture>

How to fix this?

First, get rid of the signal handler.

Then, my attempt to resolve this would be to override the Notification.save() method, so that it calls super to save the data and (hopefully) trigger the translation process, and then send the channels event.

If that doesn’t work - if you end up in the same spot with the same conditions, then I’d be looking to identify all locations where Notification in created and generate the events in those views.

Welcome to the wonderful world of Django signals and the fact that they tend not to work as people generally expect them to work. It’s why I always recommend against using them except in those cases where they’re truly needed, and this isn’t one of those cases. (The most common case where you need to use them are on models that exist in third-party libraries where you aren’t able to modify those models.)

1 Like

Thank you for taking the time to answer my question, this ended up being the solution.

For anyone looking for the solution in the future this is what I did:

    @transaction.atomic
    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)
        channel_layer = get_channel_layer()
        group_name = "banner-notifications"

        id = self.id
        str_id = id.hex

        event = {
        "type": "notification_create",
        "notification_id": str_id
        }

        async_to_sync(channel_layer.group_send)(group_name, event)