4

I'm using messages to add flash messages to the template (just as you'd expect).

The problem I have is that if you double click a link to a page that generates a message then the message appears twice.

I am using the message to tell the user I have redirected them from where they were expecting to go. They don;t need the same message twice.

I understand the logic here but am wondering how I can remove duplicated messages.

  • click url
  • message generated, saved in storage
  • click url again before page renders
  • second message generated, saved in storage
  • response adds all messages from storage
  • renders with two messages

Ultimately I would like this to be a middleware so it can cover off all requests.

rockingskier
  • 9,066
  • 3
  • 40
  • 49
  • duplicated question? http://stackoverflow.com/questions/2136954/does-a-library-to-prevent-duplicate-form-submissions-exist-for-django – cor Apr 23 '14 at 16:28
  • This isn't to do with forms. If a user goes to a page before they should I redirect them and display a message. – rockingskier Apr 23 '14 at 16:30

6 Answers6

8

Ran into the same problem and found another solution, using a custom MESSAGE_STORAGE:

from django.contrib.messages.storage.session import SessionStorage
from django.contrib.messages.storage.base import Message


class DedupMessageMixin(object):
    def __iter__(self):
        msgset = [tuple(m.__dict__.items())
                  for m in super(DedupMessageMixin, self).__iter__()]
        return iter([Message(**dict(m)) for m in set(msgset)])


class SessionDedupStorage(DedupMessageMixin, SessionStorage):
    pass


# in settings
MESSAGE_STORAGE = 'some.where.SessionDedupStorage'

This will work fine with code that would also play with messages directly, say in a view for example. Since it's a mixin, you can easily reuse it for other message storages.

Here is an alternative to avoid storing duplicates at all:

from django.contrib.messages.storage.session import SessionStorage
from itertools import chain


class DedupMessageMixin(object):
    def add(self, level, message, extra_tags):
        messages = chain(self._loaded_messages, self._queued_messages)
        for m in messages:
            if m.message == message:
                return
        return super(DedupMessageMixin, self).add(level, message, extra_tags)
qur2
  • 450
  • 5
  • 9
  • The second mixin seems much cleaner and better, can't think of a situation where you'd want to store duplicate messages to begin with. – kontextify Mar 23 '22 at 11:08
6

I had the same problem in middleware but preferred a little wrapper around the info call that I was using:

from django.contrib.messages import info
from django.contrib.messages import get_messages


def info_once_only(request, msg):
    """
    Just add the message once
    :param request:
    :param msg:
    :return:
    """
    if msg not in [m.message for m in get_messages(request)]:
        info(request, msg)


class PaymentsMiddleware(object):

    @staticmethod
    def process_request(request):
        """
        Put up a message letting a new user know that they are being dealt with.
        :param request:
        :return:
        """
        if hasattr(request, 'user'):
            user_profile = request.user.get_profile()
            if user_profile and user_profile.is_suspended:
                info_once_only(
                    request,
                    "Hi {username}, your account has been suspended, we'll be in touch shortly.".format(                           
                        username=request.user.username))
    return None
Paul Whipp
  • 16,028
  • 4
  • 42
  • 54
2

Opted for a custom TEMPLATE_CONTEXT_PROCESSORS.

Replace the default messages context processor ('django.contrib.messages.context_processors.messages',) with a simple custom version:

from django.contrib.messages.api import get_messages


def messages(request):
    """Remove duplicate messages

    """
    messages = []
    unique_messages = []
    for m in get_messages(request):
        if m.message not in messages:
            messages.append(m.message)
            unique_messages.append(m)

    return {'messages': unique_messages}
katericata
  • 1,008
  • 3
  • 14
  • 33
rockingskier
  • 9,066
  • 3
  • 40
  • 49
1

For those using Paul Whipp's suggestion.

def info_once_only(request, msg):
    if msg not in [m.message for m in get_messages(request)]:
        info(request, msg)

Note that iterating over get_messages(request) will mark each message as 'to be cleared', so the user won't see any messages except the one passed into info_once_only(request, msg).

You should set storage.used = False so that all the other messages aren't cleared.

def info_once_only(request, msg):
    storage = get_messages(request)
    if msg not in [m.message for m in storage]:
        info(request, msg)
    storage.used = False

See the corresponding section in the official documentation.

Filip Kilibarda
  • 2,484
  • 2
  • 20
  • 31
1

Inspired by @rockingskier's answer, but improved with level checking and code sugar.

  1. Add this messages function to your context processors -- you may need to create one.

    # myproject/context_processors.py
    from django.contrib.messages import get_messages
    
    
    def messages(request):
        """
        Remove duplicate messages
        """
        current_messages = get_messages(request)
        unique_messages = {(m.level, m.message): m for m in current_messages}
        return {'messages': unique_messages.values()}
    
  2. Append the context processor above to the project template settings, after django.contrib.messages.context_processors.messages. e.g:

    # myproject/settings.py
    TEMPLATES = [
        {
            # ...
            'OPTIONS': {
                'context_processors': [
                    # ...
                    'django.contrib.messages.context_processors.messages',
                    'myproject.context_processors.messages',  # Here
                ],
            },
        },
    ]
    
emyller
  • 2,648
  • 1
  • 24
  • 16
0

I decided to use modified Paul's approach. I use standard messages calls, but I import:

from myapp.utils import Messages as messages

In the utils.py file I have:

from django.contrib.messages import (get_messages,
    error as _error, info as _info, success as _success, warning as _warning)

class Messages:
    @classmethod
    def error(cls, request, msg):
        cls._add_if_unique(request, msg, _error)

    @classmethod
    def info(cls, request, msg):
        cls._add_if_unique(request, msg, _info)

    @classmethod
    def success(cls, request, msg):
        cls._add_if_unique(request, msg, _success)

    @classmethod
    def warning(cls, request, msg):
        cls._add_if_unique(request, msg, _warning)

    @classmethod
    def _add_if_unique(cls, request, msg, msg_method):
        storage = get_messages(request)
        if msg not in [m.message for m in storage]:
            msg_method(request, msg)
        storage.used = False  # iterating over get_messages mark them as gone, so lets prevent this
mirek
  • 1,140
  • 11
  • 10