Search code examples
pythondjangodjango-messages

Django refresh session to obtain any new messages for user


I'm trying to do a simple push protocol for django messages. So in my REST call, I have the following snippet:

    storage = get_messages(self.request)
    res = dict(messages=[dict(level=item.level, message=item.message) for item in storage])

    now = monotonic()
    while not res.messages and monotonic() - now < 15:
        sleep(.5)
        res.messages = [dict(level=item.level, message=item.message) for item in storage]

Naturally, the while loop does nothing because the messages framework simply re-reads the session variable which only "updates" on new requests.

I tried going into the underlying code to see if there was anything about updating the storage on the fly, but there seems to be no code that would do this, at least in the messaging framework. There was this promising undocumented storage.update() method, but it turned out to do something else.

So, is there anything in Django framework that would allow me to poll for any messages changes and report that to the browser when it happens? Or a different method that would achieve the same thing more elegantly?


Solution

  • I have managed to come to an acceptable solution with a couple of caveats. Using existing message stores (either cookie or session) proved to be either impossible (cookies) or much too ridden with internal method calls & setting / deleting internal members (session).

    This solution uses a new message store, in my case a database one.

    settings.py

    MESSAGE_STORAGE = 'your_django_app.messages_store.SessionDBStorage'
    

    your_django_app/messages_store.py

    from django.contrib.messages.storage.session import SessionStorage
    from main.models import MessagesStore, models
    
    
    class SessionDBStorage(SessionStorage):
        """
        Stores messages in the database based on session id
        """
    
        def _get(self, *args, **kwargs):
            """
            Retrieves a list of messages from the database based on session identifier.
            """
            try:
                return self.deserialize_messages(
                    MessagesStore.objects.get(pk=self.request.session.session_key).messages), True
            except models.ObjectDoesNotExist:
                return [], True
    
        def _store(self, messages, response, *args, **kwargs):
            """
            Stores a list of messages to the database.
            """
            if messages:
                MessagesStore.objects.update_or_create(session_key=self.request.session.session_key,
                                                       defaults={'messages': self.serialize_messages(messages)})
            else:
                MessagesStore.objects.filter(pk=self.request.session.session_key).delete()
            return []
    

    your_django_app/rest.py

    def pushed_messages():
        from time import sleep, monotonic
        # standard get messages code
        ....
        now = monotonic()
        while not res.messages and monotonic() - now < 15:
            sleep(1)
            if hasattr(storage, '_loaded_data'):  # one last hack to make storage reload messages
                delattr(storage, '_loaded_data')
            res.messages = [dict(level=item.level, message=item.message) for item in storage]
    

    Please note that internally this is still a polling solution (the loop constantly reloads the messages), but it proves the concept and ultimately - works. Any underlying storage / signal mechanism optimisations are beyond the scope of this answer.