Ticket #4604: django-contrib-messages-9f54c0f8719c.diff

File django-contrib-messages-9f54c0f8719c.diff, 81.8 KB (added by tobias, 5 years ago)

diff showing changes in django-contrib-messages branch as of changeset 9f54c0f8719c

  • django/conf/global_settings.py

    comparing with http://bitbucket.org/mirror/django-trunk/
    searching for changes
    diff -r ed90e13d2065 django/conf/global_settings.py
    a b  
    172172    'django.core.context_processors.i18n',
    173173    'django.core.context_processors.media',
    174174#    'django.core.context_processors.request',
     175    'django.contrib.messages.context_processors.messages',
    175176)
    176177
    177178# Output to use in template system for invalid (e.g. misspelled) variables.
     
    308309    'django.contrib.sessions.middleware.SessionMiddleware',
    309310    'django.middleware.csrf.CsrfViewMiddleware',
    310311    'django.contrib.auth.middleware.AuthenticationMiddleware',
     312    'django.contrib.messages.middleware.MessageMiddleware',
    311313#     'django.middleware.http.ConditionalGetMiddleware',
    312314#     'django.middleware.gzip.GZipMiddleware',
    313315)
  • django/conf/project_template/settings.py

    diff -r ed90e13d2065 django/conf/project_template/settings.py
    a b  
    6262    'django.contrib.sessions.middleware.SessionMiddleware',
    6363    'django.middleware.csrf.CsrfViewMiddleware',
    6464    'django.contrib.auth.middleware.AuthenticationMiddleware',
     65    'django.contrib.messages.middleware.MessageMiddleware',
    6566)
    6667
    6768ROOT_URLCONF = '{{ project_name }}.urls'
     
    7778    'django.contrib.contenttypes',
    7879    'django.contrib.sessions',
    7980    'django.contrib.sites',
     81    'django.contrib.messages',
    8082)
  • django/contrib/admin/options.py

    diff -r ed90e13d2065 django/contrib/admin/options.py
    a b  
    66from django.contrib.admin import widgets
    77from django.contrib.admin import helpers
    88from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects, model_ngettext, model_format_dict
     9from django.contrib import messages
     10from django.contrib.messages.compat import compat_add_message
    911from django.views.decorators.csrf import csrf_protect
    1012from django.core.exceptions import PermissionDenied
    1113from django.db import models, transaction
     
    541543    def message_user(self, request, message):
    542544        """
    543545        Send a message to the user. The default implementation
    544         posts a message using the auth Message object.
     546        posts a message using the django.contrib.messages backend.
    545547        """
    546         request.user.message_set.create(message=message)
     548        compat_add_message(request, messages.INFO, message)
    547549
    548550    def save_form(self, request, form, change):
    549551        """
  • django/contrib/admin/views/template.py

    diff -r ed90e13d2065 django/contrib/admin/views/template.py
    a b  
    66from django.conf import settings
    77from django.utils.importlib import import_module
    88from django.utils.translation import ugettext_lazy as _
     9from django.contrib import messages
     10from django.contrib.messages.compat import compat_add_message
    911
    1012
    1113def template_validator(request):
     
    2325        form = TemplateValidatorForm(settings_modules, site_list,
    2426                                     data=request.POST)
    2527        if form.is_valid():
    26             request.user.message_set.create(message='The template is valid.')
     28            compat_add_message(request, messages.INFO, 'The template is valid.')
    2729    else:
    2830        form = TemplateValidatorForm(settings_modules, site_list)
    2931    return render_to_response('admin/template_validator.html', {
  • django/contrib/auth/admin.py

    diff -r ed90e13d2065 django/contrib/auth/admin.py
    a b  
    33from django.contrib import admin
    44from django.contrib.auth.forms import UserCreationForm, UserChangeForm, AdminPasswordChangeForm
    55from django.contrib.auth.models import User, Group
     6from django.contrib import messages
     7from django.contrib.messages.compat import compat_add_message
    68from django.core.exceptions import PermissionDenied
    79from django.http import HttpResponseRedirect, Http404
    810from django.shortcuts import render_to_response, get_object_or_404
     
    6769                msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': 'user', 'obj': new_user}
    6870                self.log_addition(request, new_user)
    6971                if "_addanother" in request.POST:
    70                     request.user.message_set.create(message=msg)
     72                    compat_add_message(request, messages.SUCCESS, msg)
    7173                    return HttpResponseRedirect(request.path)
    7274                elif '_popup' in request.REQUEST:
    7375                    return self.response_add(request, new_user)
    7476                else:
    75                     request.user.message_set.create(message=msg + ' ' + ugettext("You may edit it again below."))
     77                    compat_add_message(request, messages.SUCCESS, msg + ' ' + ugettext("You may edit it again below."))
    7678                    return HttpResponseRedirect('../%s/' % new_user.id)
    7779        else:
    7880            form = self.add_form()
     
    104106            if form.is_valid():
    105107                new_user = form.save()
    106108                msg = ugettext('Password changed successfully.')
    107                 request.user.message_set.create(message=msg)
     109                compat_add_message(request, messages.SUCCESS, msg)
    108110                return HttpResponseRedirect('..')
    109111        else:
    110112            form = self.change_password_form(user)
  • django/contrib/auth/models.py

    diff -r ed90e13d2065 django/contrib/auth/models.py
    a b  
    288288                raise SiteProfileNotAvailable
    289289        return self._profile_cache
    290290
     291    def _get_message_set(self):
     292        import warnings
     293        warnings.warn('The user messaging API will be deprecated in Django 1.2.'
     294                      ' Please update your code to use the new messaging API.',
     295                      category=PendingDeprecationWarning)
     296        return self._message_set
     297    message_set = property(_get_message_set)
     298
    291299class Message(models.Model):
    292300    """
    293301    The message system is a lightweight way to queue messages for given
     
    297305    actions. For example, "The poll Foo was created successfully." is a
    298306    message.
    299307    """
    300     user = models.ForeignKey(User)
     308    user = models.ForeignKey(User, related_name='_message_set')
    301309    message = models.TextField(_('message'))
    302310
    303311    def __unicode__(self):
  • new file django/contrib/messages/__init__.py

    diff -r ed90e13d2065 django/contrib/messages/__init__.py
    - +  
     1from constants import *
  • new file django/contrib/messages/compat.py

    diff -r ed90e13d2065 django/contrib/messages/compat.py
    - +  
     1"""
     2Compatibility methods to assist in the transition from user.message_set to the
     3messages contrib app.
     4
     5These methods are for use by Django internally and it is not recommended
     6that they called directly from a Django project.  They will be removed in a
     7future version of Django and are not subject to the normal deprecation policy.
     8"""
     9
     10from django.utils.functional import lazy, memoize
     11
     12__all__ = (
     13    'compat_add_message',
     14    'compat_get_messages',
     15)
     16
     17
     18def compat_add_message(request, level, message):
     19    """
     20    Attempts to add a message to the request using the 'messages' app, falling
     21    back to the user's message_set if MessageMiddleware hasn't been enabled.
     22    """
     23    if hasattr(request, 'messages'):
     24        request.messages.add(level, message)
     25    elif hasattr(request, 'user') and request.user.is_authenticated():
     26        request.user.message_set.create(message=message)
     27
     28
     29def compat_get_messages(request, get_user=None):
     30    """
     31    Returns the message storage on the request if it exists, otherwise returns
     32    user.message_set.all() as the old auth context processor did.
     33    """
     34
     35    def get_user():
     36        if hasattr(request, 'user'):
     37            return request.user
     38        else:
     39            from django.contrib.auth.models import AnonymousUser
     40            return AnonymousUser()
     41
     42    if hasattr(request, 'messages'):
     43        return request.messages
     44    else:
     45        return lazy(memoize(get_user().get_and_delete_messages, {}, 0), list)()
  • new file django/contrib/messages/constants.py

    diff -r ed90e13d2065 django/contrib/messages/constants.py
    - +  
     1DEBUG = 10
     2INFO = 20
     3SUCCESS = 25
     4WARNING = 30
     5ERROR = 40
     6 
     7DEFAULT_TAGS = {
     8    DEBUG: 'debug',
     9    INFO: 'info',
     10    SUCCESS: 'success',
     11    WARNING: 'warning',
     12    ERROR: 'error',
     13}
  • new file django/contrib/messages/context_processors.py

    diff -r ed90e13d2065 django/contrib/messages/context_processors.py
    - +  
     1from django.contrib.messages.compat import compat_get_messages
     2
     3
     4def messages(request):
     5    """
     6    Returns a lazy 'messages' context variable.
     7    """
     8    return {'messages': compat_get_messages(request)}
  • new file django/contrib/messages/middleware.py

    diff -r ed90e13d2065 django/contrib/messages/middleware.py
    - +  
     1from django.conf import settings
     2from django.contrib.messages.storage import Storage
     3
     4
     5class MessageMiddleware(object):
     6    """
     7    Middleware that handles temporary messages.
     8    """
     9
     10    def process_request(self, request):
     11        request.messages = Storage(request)
     12
     13    def process_response(self, request, response):
     14        """
     15        Updates the storage backend (i.e., saves the messages).
     16       
     17        If not all messages could not be stored and ``DEBUG`` is ``True``, a
     18        ``ValueError`` is raised.
     19        """
     20        # A higher middleware layer may return a request which does not contain
     21        # messages storage, so make no assumption that it will be there.
     22        if hasattr(request, 'messages'):
     23            unstored_messages = request.messages.update(response)
     24            if unstored_messages and settings.DEBUG:
     25                raise ValueError('Not all temporary messages could be stored.')
     26        return response
  • new file django/contrib/messages/models.py

    diff -r ed90e13d2065 django/contrib/messages/models.py
    - +  
     1# Models module required so tests are discovered.
  • new file django/contrib/messages/storage/__init__.py

    diff -r ed90e13d2065 django/contrib/messages/storage/__init__.py
    - +  
     1from django.conf import settings
     2from django.contrib.messages.storage.base import BaseStorage
     3from django.utils.importcls import get_class
     4
     5Storage = get_class(getattr(settings, 'MESSAGE_STORAGE',
     6    'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'))
  • new file django/contrib/messages/storage/base.py

    diff -r ed90e13d2065 django/contrib/messages/storage/base.py
    - +  
     1from django.conf import settings
     2from django.utils.encoding import force_unicode, StrAndUnicode
     3from django.contrib.messages import constants, utils
     4
     5
     6LEVEL_TAGS = utils.get_level_tags()
     7
     8
     9class Message(StrAndUnicode):
     10    """
     11    Represents an actual message that can be stored in any of the supported
     12    storage classes (typically session- or cookie-based) and rendered in a view
     13    or template.
     14    """
     15
     16    def __init__(self, level, message, extra_tags=None):
     17        self.level = int(level)
     18        self.message = message
     19        self.extra_tags = extra_tags
     20
     21    def _prepare(self):
     22        """
     23        Prepares the message for serialization by forcing the ``message``
     24        and ``extra_tags`` to unicode in case they are lazy translations.
     25       
     26        Known "safe" types (None, int, etc.) are not converted (see Django's
     27        ``force_unicode`` implementation for details).
     28        """
     29        self.message = force_unicode(self.message, strings_only=True)
     30        self.extra_tags = force_unicode(self.extra_tags, strings_only=True)
     31
     32    def __unicode__(self):
     33        return force_unicode(self.message)
     34
     35    def _get_tags(self):
     36        label_tag = force_unicode(LEVEL_TAGS.get(self.level, ''),
     37                                  strings_only=True)
     38        extra_tags = force_unicode(self.extra_tags, strings_only=True)
     39        if extra_tags:
     40            if label_tag:
     41                return u'%s %s' % (extra_tags, label_tag)
     42            return extra_tags
     43        return label_tag or ''
     44
     45    tags = property(_get_tags)
     46
     47
     48class BaseStorage(object):
     49    """
     50    This is the base backend for temporary message storage.
     51   
     52    This is not a complete class; to be a usable storage backend, it must be
     53    subclassed and the two methods ``_get`` and ``_store`` overridden.
     54    """
     55    store_serialized = True
     56
     57    def __init__(self, request, *args, **kwargs):
     58        self.request = request
     59        self._queued_messages = []
     60        self.used = False
     61        self.added_new = False
     62        super(BaseStorage, self).__init__(*args, **kwargs)
     63
     64    def __len__(self):
     65        return len(self._loaded_messages) + len(self._queued_messages)
     66
     67    def __iter__(self):
     68        self.used = True
     69        if self._queued_messages:
     70            self._loaded_messages.extend(self._queued_messages)
     71            self._queued_messages = []
     72        return iter(self._loaded_messages)
     73
     74    def __contains__(self, item):
     75        return item in self._loaded_messages or item in self._queued_messages
     76
     77    @property
     78    def _loaded_messages(self):
     79        """
     80        Returns a list of loaded messages, retrieving them first if they have
     81        not been loaded yet.
     82        """
     83        if not hasattr(self, '_loaded_data'):
     84            messages, all_retrieved = self._get()
     85            self._loaded_data = messages or []
     86        return self._loaded_data
     87
     88    def _get(self, *args, **kwargs):
     89        """
     90        Retrieves a list of stored messages. Returns a tuple of the messages and
     91        a flag indicating whether or not all the messages originally intended
     92        to be stored in this storage were, in fact, stored and now retrieved.
     93       
     94        **This method must be implemented by a subclass.**
     95       
     96        If it is possible to tell if the backend was not used (as opposed to
     97        just containing no messages) then ``None`` should be returned.
     98        """
     99        raise NotImplementedError()
     100
     101    def _store(self, messages, response, *args, **kwargs):
     102        """
     103        Stores a list of messages, returning a list of any messages which could
     104        not be stored.
     105       
     106        One type of object must be able to be stored, ``Message``.
     107       
     108        **This method must be implemented by a subclass.**
     109        """
     110        raise NotImplementedError()
     111
     112    def _prepare_messages(self, messages):
     113        """
     114        Prepares a list of messages for storage.
     115        """
     116        if self.store_serialized:
     117            for message in messages:
     118                message._prepare()
     119
     120    def update(self, response, fail_silently=True):
     121        """
     122        Stores all unread messages.
     123       
     124        If the backend has yet to be iterated, previously stored messages will
     125        be stored again. Otherwise, only messages added after the last
     126        iteration will be stored.
     127        """
     128        if self.used:
     129            self._prepare_messages(self._queued_messages)
     130            return self._store(self._queued_messages, response)
     131        elif self.added_new:
     132            messages = self._loaded_messages + self._queued_messages
     133            self._prepare_messages(messages)
     134            return self._store(messages, response)
     135
     136    def add(self, level, message, extra_tags=''):
     137        """
     138        Queues a message to be stored.
     139       
     140        The message is only queued if it contained something and its level is
     141        not less than the recording level (``self.level``).
     142        """
     143        if not message:
     144            return
     145        # Check that the message level is not less than the recording level.
     146        level = int(level)
     147        if level < self.level:
     148            return
     149        # Add the message.
     150        self.added_new = True
     151        message = Message(level, message, extra_tags=extra_tags)
     152        self._queued_messages.append(message)
     153
     154    def debug(self, message, extra_tags=''):
     155        """
     156        Adds a message with the ``DEBUG`` level.
     157        """
     158        self.add(constants.DEBUG, message, extra_tags=extra_tags)
     159
     160    def info(self, message, extra_tags=''):
     161        """
     162        Adds a message with the ``INFO`` level.
     163        """
     164        self.add(constants.INFO, message, extra_tags=extra_tags)
     165
     166    def success(self, message, extra_tags=''):
     167        """
     168        Adds a message with the ``SUCCESS`` level.
     169        """
     170        self.add(constants.SUCCESS, message, extra_tags=extra_tags)
     171
     172    def warning(self, message, extra_tags=''):
     173        """
     174        Adds a message with the ``WARNING`` level.
     175        """
     176        self.add(constants.WARNING, message, extra_tags=extra_tags)
     177
     178    def error(self, message, extra_tags=''):
     179        """
     180        Adds a message with the ``ERROR`` level.
     181        """
     182        self.add(constants.ERROR, message, extra_tags=extra_tags)
     183
     184    def _get_level(self):
     185        """
     186        Returns the minimum recorded level.
     187       
     188        The default level is the ``MESSAGE_LEVEL`` setting. If this is
     189        not found, the ``INFO`` level is used.
     190        """
     191        if not hasattr(self, '_level'):
     192            self._level = getattr(settings, 'MESSAGE_LEVEL', constants.INFO)
     193        return self._level
     194
     195    def _set_level(self, value=None):
     196        """
     197        Sets a custom minimum recorded level.
     198       
     199        If set to ``None``, the default level will be used (see the
     200        ``_get_level`` method).
     201        """
     202        if value is None and hasattr(self, '_level'):
     203            del self._level
     204        else:
     205            self._level = int(value)
     206
     207    level = property(_get_level, _set_level, _set_level)
  • new file django/contrib/messages/storage/cookie.py

    diff -r ed90e13d2065 django/contrib/messages/storage/cookie.py
    - +  
     1import hmac
     2
     3from django.conf import settings
     4from django.utils.hashcompat import sha_constructor
     5from django.contrib.messages import constants
     6from django.contrib.messages.storage.base import BaseStorage, Message
     7try:
     8    import json   # Available in Python 2.6.
     9except ImportError:
     10    # Otherwise fall back to simplejson bundled with Django.
     11    from django.utils import simplejson as json
     12
     13
     14class MessageEncoder(json.JSONEncoder):
     15    """
     16    Compactly serializes instances of the ``Message`` class as JSON.
     17    """
     18    message_key = '__json_message'
     19
     20    def default(self, obj):
     21        if isinstance(obj, Message):
     22            message = [self.message_key, obj.level, obj.message]
     23            if obj.extra_tags:
     24                message.append(obj.extra_tags)
     25            return message
     26        return super(MessageEncoder, self).default(obj)
     27
     28
     29class MessageDecoder(json.JSONDecoder):
     30    """
     31    Decodes JSON that includes serialized ``Message`` instances.
     32    """
     33
     34    def process_messages(self, obj):
     35        if isinstance(obj, list) and obj:
     36            if obj[0] == MessageEncoder.message_key:
     37                return Message(*obj[1:])
     38            return [self.process_messages(item) for item in obj]
     39        if isinstance(obj, dict):
     40            return dict([(key, self.process_messages(value))
     41                         for key, value in obj.iteritems()])
     42        return obj
     43
     44    def decode(self, s, **kwargs):
     45        decoded = super(MessageDecoder, self).decode(s, **kwargs)
     46        return self.process_messages(decoded)
     47
     48
     49class CookieStorage(BaseStorage):
     50    """
     51    Stores messages in a cookie.
     52    """
     53    cookie_name = 'messages'
     54    max_cookie_size = 4096
     55    not_finished = '__messagesnotfinished__'
     56   
     57    def _get(self, *args, **kwargs):
     58        """
     59        Retrieves a list of messages from the messages cookie.  If the
     60        not_finished sentinel value is found at the end of the message list,
     61        remove it and return a result indicating that not all messages were
     62        retrieved by this storage.
     63        """
     64        data = self.request.COOKIES.get(self.cookie_name)
     65        messages = self._decode(data)
     66        all_retrieved = not (messages and messages[-1] == self.not_finished)
     67        if messages and not all_retrieved:
     68            # remove the sentinel value
     69            messages.pop()
     70        return messages, all_retrieved
     71
     72    def _update_cookie(self, encoded_data, response):
     73        """
     74        Either sets the cookie with the encoded data if there is any data to
     75        store, or deletes the cookie.
     76        """
     77        if encoded_data:
     78            response.set_cookie(self.cookie_name, encoded_data)
     79        else:
     80            response.delete_cookie(self.cookie_name)
     81
     82    def _store(self, messages, response, remove_oldest=True, *args, **kwargs):
     83        """
     84        Stores the messages to a cookie, returning a list of any messages which
     85        could not be stored.
     86       
     87        If the encoded data is larger than ``max_cookie_size``, removes
     88        messages until the data fits (these are the messages which are
     89        returned), and add the not_finished sentinel value to indicate as much.
     90        """
     91        unstored_messages = []
     92        encoded_data = self._encode(messages)
     93        if self.max_cookie_size:
     94            while encoded_data and len(encoded_data) > self.max_cookie_size:
     95                if remove_oldest:
     96                    unstored_messages.append(messages.pop(0))
     97                else:
     98                    unstored_messages.insert(0, messages.pop())
     99                encoded_data = self._encode(messages + [self.not_finished],
     100                                            encode_empty=unstored_messages)
     101        self._update_cookie(encoded_data, response)
     102        return unstored_messages
     103
     104    def _hash(self, value):
     105        """
     106        Creates an HMAC/SHA1 hash based on the value and the project setting's
     107        SECRET_KEY, modified to make it unique for the present purpose.
     108        """
     109        key = 'django.contrib.messages' + settings.SECRET_KEY
     110        return hmac.new(key, value, sha_constructor).hexdigest()
     111   
     112    def _encode(self, messages, encode_empty=False):
     113        """
     114        Returns an encoded version of the messages list which can be stored as
     115        plain text.
     116       
     117        Since the data will be retrieved from the client-side, the encoded data
     118        also contains a hash to ensure that the data was not tampered with.
     119        """
     120        if messages or encode_empty:
     121            encoder = MessageEncoder(separators=(',', ':'))
     122            value = encoder.encode(messages)
     123            return '%s$%s' % (self._hash(value), value)
     124
     125    def _decode(self, data):
     126        """
     127        Safely decodes a encoded text stream back into a list of messages.
     128       
     129        If the encoded text stream contained an invalid hash or was in an
     130        invalid format, ``None`` is returned.
     131        """
     132        if not data:
     133            return None
     134        bits = data.split('$', 1)
     135        if len(bits) == 2:
     136            hash, value = bits
     137            if hash == self._hash(value):
     138                try:
     139                    # If we get here (and the JSON decode works), everything is
     140                    # good. In any other case, drop back and return None.
     141                    return json.loads(value, cls=MessageDecoder)
     142                except ValueError:
     143                    pass
     144        # Mark the data as used (so it gets removed) since something was wrong
     145        # with the data.
     146        self.used = True
     147        return None
  • new file django/contrib/messages/storage/fallback.py

    diff -r ed90e13d2065 django/contrib/messages/storage/fallback.py
    - +  
     1from django.contrib.messages.storage.base import BaseStorage
     2from django.contrib.messages.storage.cookie import CookieStorage
     3from django.contrib.messages.storage.session import SessionStorage
     4try:
     5    set
     6except NameError:
     7    from sets import Set as set   # Python 2.3
     8
     9
     10class FallbackStorage(BaseStorage):
     11    """
     12    Tries to store all messages in the first backend, storing any unstored
     13    messages in each subsequent backend backend.
     14    """
     15    storage_classes = (CookieStorage, SessionStorage)
     16
     17    def __init__(self, *args, **kwargs):
     18        super(FallbackStorage, self).__init__(*args, **kwargs)
     19        self.storages = [storage_class(*args, **kwargs)
     20                         for storage_class in self.storage_classes]
     21        self._used_storages = set()
     22
     23    def _get(self, *args, **kwargs):
     24        """
     25        Gets a single list of messages from all storage backends.
     26        """
     27        all_messages = []
     28        for storage in self.storages:
     29            messages, all_retrieved = storage._get()
     30            # If the backend hasn't been used, no more retrieval is necessary.
     31            if messages is None:
     32                break
     33            if messages:
     34                self._used_storages.add(storage)
     35            all_messages.extend(messages)
     36            # If this storage class contained all the messages, no further
     37            # retrieval is necessary
     38            if all_retrieved:
     39                break
     40        return all_messages, all_retrieved
     41
     42    def _store(self, messages, response, *args, **kwargs):
     43        """
     44        Stores the messages, returning any unstored messages after trying all
     45        backends.
     46       
     47        For each storage backend, any messages not stored are passed on to the
     48        next backend.
     49        """
     50        for storage in self.storages:
     51            if messages:
     52                messages = storage._store(
     53                    messages,
     54                    response,
     55                    remove_oldest=False,
     56                )
     57            # Even if there are no more messages, continue iterating to ensure
     58            # storages which contained messages are flushed.
     59            elif storage in self._used_storages:
     60                storage._store([], response)
     61                self._used_storages.remove(storage)
     62        return messages
  • new file django/contrib/messages/storage/session.py

    diff -r ed90e13d2065 django/contrib/messages/storage/session.py
    - +  
     1from django.contrib.messages.storage.base import BaseStorage
     2
     3
     4class SessionStorage(BaseStorage):
     5    """
     6    Stores messages in the session (that is, django.contrib.sessions).
     7    """
     8    session_key = '_messages'
     9
     10    def __init__(self, request, *args, **kwargs):
     11        assert hasattr(request, 'session'), "The session-based temporary "\
     12            "message storage requires session middleware to be installed."
     13        super(SessionStorage, self).__init__(request, *args, **kwargs)
     14
     15    def _get(self, *args, **kwargs):
     16        """
     17        Retrieves a list of messages from the request's session.  This storage
     18        always stores everything it is given, so return True for the
     19        all_retrieved flag.
     20        """
     21        return self.request.session.get(self.session_key), True
     22
     23    def _store(self, messages, response, *args, **kwargs):
     24        """
     25        Stores a list of messages to the request's session.
     26        """
     27        if messages:
     28            self.request.session[self.session_key] = messages
     29        else:
     30            self.request.session.pop(self.session_key, None)
  • new file django/contrib/messages/storage/user_messages.py

    diff -r ed90e13d2065 django/contrib/messages/storage/user_messages.py
    - +  
     1"""
     2Storages used to assist in the deprecation of contrib.auth User messages.
     3
     4"""
     5from django.contrib.messages import constants
     6from django.contrib.messages.storage.base import BaseStorage, Message
     7from django.contrib.auth.models import User
     8from django.contrib.messages.storage.fallback import FallbackStorage
     9
     10
     11class UserMessagesStorage(BaseStorage):
     12    """
     13    Retrieves messages from the User, using the legacy user.message_set API.
     14   
     15    This storage is "read-only" insofar as it can only retrieve and delete
     16    messages, not store them.
     17    """
     18    session_key = '_messages'
     19
     20    def _get_messages_queryset(self):
     21        """
     22        Returns the QuerySet containing all user messages (or ``None`` if
     23        request.user is not a contrib.auth User).
     24        """
     25        user = getattr(self.request, 'user', None)
     26        if isinstance(user, User):
     27            return user.message_set.all()
     28
     29    def add(self, *args, **kwargs):
     30        raise NotImplementedError('This message storage is read-only.')
     31
     32    def _get(self, *args, **kwargs):
     33        """
     34        Retrieves a list of messages assigned to the User.  This backend never
     35        stores anything, so all_retrieved is assumed to be False.
     36        """
     37        queryset = self._get_messages_queryset()
     38        if queryset is None:
     39            # This is a read-only and optional storage, so to ensure other
     40            # storages will also be read if used with FallbackStorage an empty
     41            # list is returned rather than None.
     42            return [], False
     43        messages = []
     44        for user_message in queryset:
     45            messages.append(Message(constants.INFO, user_message.message))
     46        return messages, False
     47
     48    def _store(self, messages, *args, **kwargs):
     49        """
     50        Removes any messages assigned to the User and returns the list of
     51        messages (since no messages are stored in this read-only storage).
     52        """
     53        queryset = self._get_messages_queryset()
     54        if queryset is not None:
     55            queryset.delete()
     56        return messages
     57
     58
     59class LegacyFallbackStorage(FallbackStorage):
     60    """
     61    Works like ``FallbackStorage`` but also handles retrieving (and clearing)
     62    contrib.auth User messages.
     63    """
     64    storage_classes = (UserMessagesStorage,) + FallbackStorage.storage_classes
  • new file django/contrib/messages/tests/__init__.py

    diff -r ed90e13d2065 django/contrib/messages/tests/__init__.py
    - +  
     1from django.contrib.messages.tests.cookie import CookieTest
     2from django.contrib.messages.tests.fallback import FallbackTest
     3from django.contrib.messages.tests.middleware import MiddlewareTest
     4from django.contrib.messages.tests.session import SessionTest
     5from django.contrib.messages.tests.user_messages import UserMessagesTest, \
     6    LegacyFallbackTest
  • new file django/contrib/messages/tests/base.py

    diff -r ed90e13d2065 django/contrib/messages/tests/base.py
    - +  
     1from django.contrib.messages import constants
     2import unittest
     3from django import http
     4from django.conf import settings
     5from django.utils.translation import ugettext_lazy
     6from django.contrib.messages import utils
     7from django.contrib.messages.storage import Storage, base
     8from django.contrib.messages.storage.base import Message
     9
     10
     11def add_level_messages(storage):
     12    """
     13    Adds 6 messages from different levels (including a custom one) to a storage
     14    instance.
     15    """
     16    storage.add(constants.INFO, 'A generic info message')
     17    storage.add(29, 'Some custom level')
     18    storage.debug('A debugging message', extra_tags='extra-tag')
     19    storage.warning('A warning')
     20    storage.error('An error')
     21    storage.success('This was a triumph.')
     22
     23
     24class BaseTest(unittest.TestCase):
     25    storage_class = Storage
     26    restore_settings = ['MESSAGE_LEVEL', 'MESSAGE_TAGS']
     27
     28    def setUp(self):
     29        self._remembered_settings = {}
     30        for setting in self.restore_settings:
     31            if hasattr(settings, setting):
     32                self._remembered_settings[setting] = getattr(settings, setting)
     33                delattr(settings._wrapped, setting)
     34
     35    def tearDown(self):
     36        for setting in self.restore_settings:
     37            self.restore_setting(setting)
     38
     39    def restore_setting(self, setting):
     40        if setting in self._remembered_settings:
     41            value = self._remembered_settings.pop(setting)
     42            setattr(settings, setting, value)
     43        elif hasattr(settings, setting):
     44            delattr(settings._wrapped, setting)
     45
     46    def get_request(self):
     47        return http.HttpRequest()
     48
     49    def get_response(self):
     50        return http.HttpResponse()
     51
     52    def get_storage(self, data=None):
     53        """
     54        Returns the storage backend, setting its loaded data to the ``data``
     55        argument.
     56       
     57        This method avoids the storage ``_get`` method from getting called so
     58        that other parts of the storage backend can be tested independent of
     59        the message retrieval logic.
     60        """
     61        storage = self.storage_class(self.get_request())
     62        storage._loaded_data = data or []
     63        return storage
     64
     65    def test_add(self):
     66        storage = self.get_storage()
     67        self.assertFalse(storage.added_new)
     68        storage.add(constants.INFO, 'Test message 1')
     69        self.assert_(storage.added_new)
     70        storage.add(constants.INFO, 'Test message 2', extra_tags='tag')
     71        self.assertEqual(len(storage), 2)
     72
     73    def test_add_lazy_translation(self):
     74        storage = self.get_storage()
     75        response = self.get_response()
     76
     77        storage.add(constants.INFO, ugettext_lazy('lazy message'))
     78        storage.update(response)
     79
     80        storing = self.stored_messages_count(storage, response)
     81        self.assertEqual(storing, 1)
     82
     83    def test_no_update(self):
     84        storage = self.get_storage()
     85        response = self.get_response()
     86        storage.update(response)
     87        storing = self.stored_messages_count(storage, response)
     88        self.assertEqual(storing, 0)
     89
     90    def test_add_update(self):
     91        storage = self.get_storage()
     92        response = self.get_response()
     93
     94        storage.add(constants.INFO, 'Test message 1')
     95        storage.add(constants.INFO, 'Test message 1', extra_tags='tag')
     96        storage.update(response)
     97
     98        storing = self.stored_messages_count(storage, response)
     99        self.assertEqual(storing, 2)
     100
     101    def test_existing_add_read_update(self):
     102        storage = self.get_existing_storage()
     103        response = self.get_response()
     104
     105        storage.add(constants.INFO, 'Test message 3')
     106        list(storage)   # Simulates a read
     107        storage.update(response)
     108
     109        storing = self.stored_messages_count(storage, response)
     110        self.assertEqual(storing, 0)
     111
     112    def test_existing_read_add_update(self):
     113        storage = self.get_existing_storage()
     114        response = self.get_response()
     115
     116        list(storage)   # Simulates a read
     117        storage.add(constants.INFO, 'Test message 3')
     118        storage.update(response)
     119
     120        storing = self.stored_messages_count(storage, response)
     121        self.assertEqual(storing, 1)
     122
     123    def stored_messages_count(self, storage, response):
     124        """
     125        Returns the number of messages being stored after a
     126        ``storage.update()`` call.
     127        """
     128        raise NotImplementedError('This method must be set by a subclass.')
     129
     130    def test_get(self):
     131        raise NotImplementedError('This method must be set by a subclass.')
     132
     133    def get_existing_storage(self):
     134        return self.get_storage([Message(constants.INFO, 'Test message 1'),
     135                                 Message(constants.INFO, 'Test message 2',
     136                                              extra_tags='tag')])
     137
     138    def test_existing_read(self):
     139        """
     140        Tests that reading the existing storage doesn't cause the data to be
     141        lost.
     142        """
     143        storage = self.get_existing_storage()
     144        self.assertFalse(storage.used)
     145        # After iterating the storage engine directly, the used flag is set.
     146        data = list(storage)
     147        self.assert_(storage.used)
     148        # The data does not disappear because it has been iterated.
     149        self.assertEqual(data, list(storage))
     150
     151    def test_existing_add(self):
     152        storage = self.get_existing_storage()
     153        self.assertFalse(storage.added_new)
     154        storage.add(constants.INFO, 'Test message 3')
     155        self.assert_(storage.added_new)
     156
     157    def test_default_level(self):
     158        storage = self.get_storage()
     159        add_level_messages(storage)
     160        self.assertEqual(len(storage), 5)
     161
     162    def test_low_level(self):
     163        storage = self.get_storage()
     164        storage.level = 5
     165        add_level_messages(storage)
     166        self.assertEqual(len(storage), 6)
     167
     168    def test_high_level(self):
     169        storage = self.get_storage()
     170        storage.level = 30
     171        add_level_messages(storage)
     172        self.assertEqual(len(storage), 2)
     173
     174    def test_settings_level(self):
     175        settings.MESSAGE_LEVEL = 29
     176        storage = self.get_storage()
     177        add_level_messages(storage)
     178        self.assertEqual(len(storage), 3)
     179
     180    def test_tags(self):
     181        storage = self.get_storage()
     182        storage.level = 0
     183        add_level_messages(storage)
     184        tags = [msg.tags for msg in storage]
     185        self.assertEqual(tags,
     186                         ['info', '', 'extra-tag debug', 'warning', 'error',
     187                          'success'])
     188
     189    def test_custom_tags(self):
     190        settings.MESSAGE_TAGS = {
     191            constants.INFO: 'info',
     192            constants.DEBUG: '',
     193            constants.WARNING: '',
     194            constants.ERROR: 'bad',
     195            29: 'custom',
     196        }
     197        # LEVEL_TAGS is a constant defined in the
     198        # django.contrib.messages.storage.base module, so after changing
     199        # settings.MESSAGE_TAGS, we need to update that constant too.
     200        base.LEVEL_TAGS = utils.get_level_tags()
     201        try:
     202            storage = self.get_storage()
     203            storage.level = 0
     204            add_level_messages(storage)
     205            tags = [msg.tags for msg in storage]
     206            self.assertEqual(tags,
     207                         ['info', 'custom', 'extra-tag', '', 'bad', 'success'])
     208        finally:
     209            # Ensure the level tags constant is put back like we found it.
     210            self.restore_setting('MESSAGE_TAGS')
     211            base.LEVEL_TAGS = utils.get_level_tags()
  • new file django/contrib/messages/tests/cookie.py

    diff -r ed90e13d2065 django/contrib/messages/tests/cookie.py
    - +  
     1from django.contrib.messages import constants
     2from django.contrib.messages.tests.base import BaseTest
     3from django.contrib.messages.storage.cookie import CookieStorage
     4
     5
     6def set_cookie_data(storage, messages, invalid=False, encode_empty=False):
     7    """
     8    Sets ``request.COOKIES`` with the encoded data and removes the storage
     9    backend's loaded data cache.
     10    """
     11    encoded_data = storage._encode(messages, encode_empty=encode_empty)
     12    if invalid:
     13        # Truncate the first character so that the hash is invalid.
     14        encoded_data = encoded_data[1:]
     15    storage.request.COOKIES = {CookieStorage.cookie_name: encoded_data}
     16    if hasattr(storage, '_loaded_data'):
     17        del storage._loaded_data
     18
     19
     20def stored_cookie_messages_count(storage, response):
     21    """
     22    Returns an integer containing the number of messages stored.
     23    """
     24    # Get a list of cookies, excluding ones with a max-age of 0 (because
     25    # they have been marked for deletion).
     26    cookie = response.cookies.get(storage.cookie_name)
     27    if not cookie or cookie['max-age'] == 0:
     28        return 0
     29    data = storage._decode(cookie.value)
     30    if not data:
     31        return 0
     32    if data[-1] == CookieStorage.not_finished:
     33        data.pop()
     34    return len(data)
     35
     36
     37class CookieTest(BaseTest):
     38    storage_class = CookieStorage
     39
     40    def stored_messages_count(self, storage, response):
     41        return stored_cookie_messages_count(storage, response)
     42
     43    def test_get(self):
     44        storage = self.storage_class(self.get_request())
     45        # Set initial data.
     46        example_messages = ['test', 'me']
     47        set_cookie_data(storage, example_messages)
     48        # Test that the message actually contains what we expect.
     49        self.assertEqual(list(storage), example_messages)
     50
     51    def test_get_bad_cookie(self):
     52        request = self.get_request()
     53        storage = self.storage_class(request)
     54        # Set initial (invalid) data.
     55        example_messages = ['test', 'me']
     56        set_cookie_data(storage, example_messages, invalid=True)
     57        # Test that the message actually contains what we expect.
     58        self.assertEqual(list(storage), [])
     59
     60    def test_max_cookie_length(self):
     61        """
     62        Tests that, if the data exceeds what is allowed in a cookie, older
     63        messages are removed before saving (and returned by the ``update``
     64        method).
     65        """
     66        storage = self.get_storage()
     67        response = self.get_response()
     68
     69        for i in range(5):
     70            storage.add(constants.INFO, str(i) * 900)
     71        unstored_messages = storage.update(response)
     72
     73        cookie_storing = self.stored_messages_count(storage, response)
     74        self.assertEqual(cookie_storing, 4)
     75
     76        self.assertEqual(len(unstored_messages), 1)
     77        self.assert_(unstored_messages[0].message == '0' * 900)
  • new file django/contrib/messages/tests/fallback.py

    diff -r ed90e13d2065 django/contrib/messages/tests/fallback.py
    - +  
     1from django.contrib.messages import constants
     2from django.contrib.messages.storage.fallback import FallbackStorage, \
     3    CookieStorage
     4from django.contrib.messages.tests.base import BaseTest
     5from django.contrib.messages.tests.cookie import set_cookie_data, \
     6    stored_cookie_messages_count
     7from django.contrib.messages.tests.session import set_session_data, \
     8    stored_session_messages_count
     9
     10
     11class FallbackTest(BaseTest):
     12    storage_class = FallbackStorage
     13
     14    def get_request(self):
     15        self.session = {}
     16        request = super(FallbackTest, self).get_request()
     17        request.session = self.session
     18        return request
     19
     20    def get_cookie_storage(self, storage):
     21        return storage.storages[-2]
     22
     23    def get_session_storage(self, storage):
     24        return storage.storages[-1]
     25
     26    def stored_cookie_messages_count(self, storage, response):
     27        return stored_cookie_messages_count(self.get_cookie_storage(storage),
     28                                            response)
     29
     30    def stored_session_messages_count(self, storage, response):
     31        return stored_session_messages_count(self.get_session_storage(storage))
     32
     33    def stored_messages_count(self, storage, response):
     34        """
     35        Return the storage totals from both cookie and session backends.
     36        """
     37        total = (self.stored_cookie_messages_count(storage, response) +
     38                 self.stored_session_messages_count(storage, response))
     39        return total
     40
     41    def test_get(self):
     42        request = self.get_request()
     43        storage = self.storage_class(request)
     44        cookie_storage = self.get_cookie_storage(storage)
     45
     46        # Set initial cookie data.
     47        example_messages = [str(i) for i in range(5)]
     48        set_cookie_data(cookie_storage, example_messages)
     49
     50        # Overwrite the _get method of the fallback storage to prove it is not
     51        # used (it would cause a TypeError: 'NoneType' object is not callable).
     52        self.get_session_storage(storage)._get = None
     53
     54        # Test that the message actually contains what we expect.
     55        self.assertEqual(list(storage), example_messages)
     56
     57    def test_get_empty(self):
     58        request = self.get_request()
     59        storage = self.storage_class(request)
     60
     61        # Overwrite the _get method of the fallback storage to prove it is not
     62        # used (it would cause a TypeError: 'NoneType' object is not callable).
     63        self.get_session_storage(storage)._get = None
     64
     65        # Test that the message actually contains what we expect.
     66        self.assertEqual(list(storage), [])
     67
     68    def test_get_fallback(self):
     69        request = self.get_request()
     70        storage = self.storage_class(request)
     71        cookie_storage = self.get_cookie_storage(storage)
     72        session_storage = self.get_session_storage(storage)
     73
     74        # Set initial cookie and session data.
     75        example_messages = [str(i) for i in range(5)]
     76        set_cookie_data(cookie_storage, example_messages[:4] +
     77                        [CookieStorage.not_finished])
     78        set_session_data(session_storage, example_messages[4:])
     79
     80        # Test that the message actually contains what we expect.
     81        self.assertEqual(list(storage), example_messages)
     82
     83    def test_get_fallback_only(self):
     84        request = self.get_request()
     85        storage = self.storage_class(request)
     86        cookie_storage = self.get_cookie_storage(storage)
     87        session_storage = self.get_session_storage(storage)
     88
     89        # Set initial cookie and session data.
     90        example_messages = [str(i) for i in range(5)]
     91        set_cookie_data(cookie_storage, [CookieStorage.not_finished],
     92                        encode_empty=True)
     93        set_session_data(session_storage, example_messages)
     94
     95        # Test that the message actually contains what we expect.
     96        self.assertEqual(list(storage), example_messages)
     97
     98    def test_flush_used_backends(self):
     99        request = self.get_request()
     100        storage = self.storage_class(request)
     101        cookie_storage = self.get_cookie_storage(storage)
     102        session_storage = self.get_session_storage(storage)
     103
     104        # Set initial cookie and session data.
     105        set_cookie_data(cookie_storage, ['cookie', CookieStorage.not_finished])
     106        set_session_data(session_storage, ['session'])
     107
     108        # When updating, previously used but no longer needed backends are
     109        # flushed.
     110        response = self.get_response()
     111        list(storage)
     112        storage.update(response)
     113        session_storing = self.stored_session_messages_count(storage, response)
     114        self.assertEqual(session_storing, 0)
     115
     116    def test_no_fallback(self):
     117        """
     118        Confirms that:
     119       
     120        (1) A short number of messages whose data size doesn't exceed what is
     121        allowed in a cookie will all be stored in the CookieBackend.
     122       
     123        (2) If the CookieBackend can store all messages, the SessionBackend
     124        won't be written to at all.
     125        """
     126        storage = self.get_storage()
     127        response = self.get_response()
     128
     129        # Overwrite the _store method of the fallback storage to prove it isn't
     130        # used (it would cause a TypeError: 'NoneType' object is not callable).
     131        self.get_session_storage(storage)._store = None
     132
     133        for i in range(5):
     134            storage.add(constants.INFO, str(i) * 100)
     135        storage.update(response)
     136
     137        cookie_storing = self.stored_cookie_messages_count(storage, response)
     138        self.assertEqual(cookie_storing, 5)
     139        session_storing = self.stored_session_messages_count(storage, response)
     140        self.assertEqual(session_storing, 0)
     141
     142    def test_session_fallback(self):
     143        """
     144        Confirms that, if the data exceeds what is allowed in a cookie, older
     145        messages which did not "fit" are stored in the SessionBackend.
     146        """
     147        storage = self.get_storage()
     148        response = self.get_response()
     149
     150        for i in range(5):
     151            storage.add(constants.INFO, str(i) * 900)
     152        storage.update(response)
     153
     154        cookie_storing = self.stored_cookie_messages_count(storage, response)
     155        self.assertEqual(cookie_storing, 4)
     156        session_storing = self.stored_session_messages_count(storage, response)
     157        self.assertEqual(session_storing, 1)
     158
     159    def test_session_fallback_only(self):
     160        """
     161        Confirms that large messages, none of which fit in a cookie, are stored
     162        in the SessionBackend (and nothing is stored in the CookieBackend).
     163        """
     164        storage = self.get_storage()
     165        response = self.get_response()
     166
     167        storage.add(constants.INFO, 'x' * 5000)
     168        storage.update(response)
     169
     170        cookie_storing = self.stored_cookie_messages_count(storage, response)
     171        self.assertEqual(cookie_storing, 0)
     172        session_storing = self.stored_session_messages_count(storage, response)
     173        self.assertEqual(session_storing, 1)
  • new file django/contrib/messages/tests/middleware.py

    diff -r ed90e13d2065 django/contrib/messages/tests/middleware.py
    - +  
     1import unittest
     2from django import http
     3from django.contrib.messages.middleware import MessageMiddleware
     4
     5
     6class MiddlewareTest(unittest.TestCase):
     7
     8    def setUp(self):
     9        self.middleware = MessageMiddleware()
     10
     11    def test_response_without_messages(self):
     12        """
     13        Makes sure that the response middleware is tolerant of messages not
     14        existing on request.
     15        """
     16        request = http.HttpRequest()
     17        response = http.HttpResponse()
     18        self.middleware.process_response(request, response)
  • new file django/contrib/messages/tests/session.py

    diff -r ed90e13d2065 django/contrib/messages/tests/session.py
    - +  
     1from django.contrib.messages.tests.base import BaseTest
     2from django.contrib.messages.storage.session import SessionStorage
     3
     4
     5def set_session_data(storage, messages):
     6    """
     7    Sets the messages into the backend request's session and remove the
     8    backend's loaded data cache.
     9    """
     10    storage.request.session[storage.session_key] = messages
     11    if hasattr(storage, '_loaded_data'):
     12        del storage._loaded_data
     13
     14
     15def stored_session_messages_count(storage):
     16    data = storage.request.session.get(storage.session_key, [])
     17    return len(data)
     18
     19
     20class SessionTest(BaseTest):
     21    storage_class = SessionStorage
     22
     23    def get_request(self):
     24        self.session = {}
     25        request = super(SessionTest, self).get_request()
     26        request.session = self.session
     27        return request
     28
     29    def stored_messages_count(self, storage, response):
     30        return stored_session_messages_count(storage)
     31
     32    def test_get(self):
     33        storage = self.storage_class(self.get_request())
     34        # Set initial data.
     35        example_messages = ['test', 'me']
     36        set_session_data(storage, example_messages)
     37        # Test that the message actually contains what we expect.
     38        self.assertEqual(list(storage), example_messages)
  • new file django/contrib/messages/tests/user_messages.py

    diff -r ed90e13d2065 django/contrib/messages/tests/user_messages.py
    - +  
     1from django import http
     2from django.contrib.auth.models import User
     3from django.contrib.messages.storage.user_messages import UserMessagesStorage,\
     4    LegacyFallbackStorage
     5from django.contrib.messages.tests.cookie import set_cookie_data
     6from django.contrib.messages.tests.fallback import FallbackTest
     7from django.test import TestCase
     8
     9
     10class UserMessagesTest(TestCase):
     11
     12    def setUp(self):
     13        self.user = User.objects.create(username='tester')
     14   
     15    def test_add(self):
     16        storage = UserMessagesStorage(http.HttpRequest())
     17        self.assertRaises(NotImplementedError, storage.add, 'Test message 1')
     18
     19    def test_get_anonymous(self):
     20        # Ensure that the storage still works if no user is attached to the
     21        # request.
     22        storage = UserMessagesStorage(http.HttpRequest())
     23        self.assertEqual(len(storage), 0)
     24
     25    def test_get(self):
     26        storage = UserMessagesStorage(http.HttpRequest())
     27        storage.request.user = self.user
     28        self.user.message_set.create(message='test message')
     29
     30        self.assertEqual(len(storage), 1)
     31        self.assertEqual(list(storage)[0].message, 'test message')
     32
     33
     34class LegacyFallbackTest(FallbackTest, TestCase):
     35    storage_class = LegacyFallbackStorage
     36
     37    def setUp(self):
     38        super(LegacyFallbackTest, self).setUp()
     39        self.user = User.objects.create(username='tester')
     40       
     41    def get_request(self, *args, **kwargs):
     42        request = super(LegacyFallbackTest, self).get_request(*args, **kwargs)
     43        request.user = self.user
     44        return request
     45
     46    def test_get_legacy_only(self):
     47        request = self.get_request()
     48        storage = self.storage_class(request)
     49        self.user.message_set.create(message='user message')
     50
     51        # Test that the message actually contains what we expect.
     52        self.assertEqual(len(storage), 1)
     53        self.assertEqual(list(storage)[0].message, 'user message')
     54
     55    def test_get_legacy(self):
     56        request = self.get_request()
     57        storage = self.storage_class(request)
     58        cookie_storage = self.get_cookie_storage(storage)
     59        self.user.message_set.create(message='user message')
     60        set_cookie_data(cookie_storage, ['cookie'])
     61
     62        # Test that the message actually contains what we expect.
     63        self.assertEqual(len(storage), 2)
     64        self.assertEqual(list(storage)[0].message, 'user message')
     65        self.assertEqual(list(storage)[1], 'cookie')
  • new file django/contrib/messages/utils.py

    diff -r ed90e13d2065 django/contrib/messages/utils.py
    - +  
     1from django.conf import settings
     2from django.contrib.messages import constants
     3
     4
     5def get_level_tags():
     6    """
     7    Returns the message level tags.
     8    """
     9    level_tags = constants.DEFAULT_TAGS.copy()
     10    level_tags.update(getattr(settings, 'MESSAGE_TAGS', {}))
     11    return level_tags
  • django/core/context_processors.py

    diff -r ed90e13d2065 django/core/context_processors.py
    a b  
    1010from django.conf import settings
    1111from django.middleware.csrf import get_token
    1212from django.utils.functional import lazy, memoize, SimpleLazyObject
     13from django.contrib.messages.compat import compat_get_messages
    1314
    1415def auth(request):
    1516    """
     
    3738
    3839    return {
    3940        'user': SimpleLazyObject(get_user),
    40         'messages': lazy(memoize(lambda: get_user().get_and_delete_messages(), {}, 0), list)(),
     41        'messages': compat_get_messages(request),
    4142        'perms':  lazy(lambda: PermWrapper(get_user()), PermWrapper)(),
    4243    }
    4344
  • django/core/files/storage.py

    diff -r ed90e13d2065 django/core/files/storage.py
    a b  
    88from django.core.files.move import file_move_safe
    99from django.utils.encoding import force_unicode, smart_str
    1010from django.utils.functional import LazyObject
    11 from django.utils.importlib import import_module
     11from django.utils.importcls import get_class
    1212from django.utils.text import get_valid_filename
    1313from django.utils._os import safe_join
    1414
     
    221221def get_storage_class(import_path=None):
    222222    if import_path is None:
    223223        import_path = settings.DEFAULT_FILE_STORAGE
    224     try:
    225         dot = import_path.rindex('.')
    226     except ValueError:
    227         raise ImproperlyConfigured("%s isn't a storage module." % import_path)
    228     module, classname = import_path[:dot], import_path[dot+1:]
    229     try:
    230         mod = import_module(module)
    231     except ImportError, e:
    232         raise ImproperlyConfigured('Error importing storage module %s: "%s"' % (module, e))
    233     try:
    234         return getattr(mod, classname)
    235     except AttributeError:
    236         raise ImproperlyConfigured('Storage module "%s" does not define a "%s" class.' % (module, classname))
     224    return get_class(import_path)
    237225
    238226class DefaultStorage(LazyObject):
    239227    def _setup(self):
  • new file django/utils/importcls.py

    diff -r ed90e13d2065 django/utils/importcls.py
    - +  
     1from django.utils.importlib import import_module
     2
     3
     4def get_class(import_path):
     5    """
     6    Imports the class described by import_path, where import_path is the full
     7    Python path to the class.
     8    """
     9    try:
     10        dot = import_path.rindex('.')
     11    except ValueError:
     12        raise ImproperlyConfigured("%s isn't a Python path." % import_path)
     13    module, classname = import_path[:dot], import_path[dot+1:]
     14    try:
     15        mod = import_module(module)
     16    except ImportError, e:
     17        raise ImproperlyConfigured('Error importing module %s: "%s"' % (module, e))
     18    try:
     19        return getattr(mod, classname)
     20    except AttributeError:
     21        raise ImproperlyConfigured('Module "%s" does not define a "%s" class.' % (module, classname))
  • django/views/generic/create_update.py

    diff -r ed90e13d2065 django/views/generic/create_update.py
    a b  
    66from django.utils.translation import ugettext
    77from django.contrib.auth.views import redirect_to_login
    88from django.views.generic import GenericViewError
     9from django.contrib import messages
     10from django.contrib.messages.compat import compat_add_message
    911
    1012
    1113def apply_extra_context(extra_context, context):
     
    110112        form = form_class(request.POST, request.FILES)
    111113        if form.is_valid():
    112114            new_object = form.save()
    113             if request.user.is_authenticated():
    114                 request.user.message_set.create(message=ugettext("The %(verbose_name)s was created successfully.") % {"verbose_name": model._meta.verbose_name})
     115           
     116            compat_add_message(request, messages.SUCCESS, ugettext("The %(verbose_name)s was created successfully.") % {"verbose_name": model._meta.verbose_name})
    115117            return redirect(post_save_redirect, new_object)
    116118    else:
    117119        form = form_class()
     
    152154        form = form_class(request.POST, request.FILES, instance=obj)
    153155        if form.is_valid():
    154156            obj = form.save()
    155             if request.user.is_authenticated():
    156                 request.user.message_set.create(message=ugettext("The %(verbose_name)s was updated successfully.") % {"verbose_name": model._meta.verbose_name})
     157            compat_add_message(request, messages.SUCCESS, ugettext("The %(verbose_name)s was updated successfully.") % {"verbose_name": model._meta.verbose_name})
    157158            return redirect(post_save_redirect, obj)
    158159    else:
    159160        form = form_class(instance=obj)
     
    194195
    195196    if request.method == 'POST':
    196197        obj.delete()
    197         if request.user.is_authenticated():
    198             request.user.message_set.create(message=ugettext("The %(verbose_name)s was deleted.") % {"verbose_name": model._meta.verbose_name})
     198        compat_add_message(request, messages.SUCCESS, ugettext("The %(verbose_name)s was deleted.") % {"verbose_name": model._meta.verbose_name})
    199199        return HttpResponseRedirect(post_delete_redirect)
    200200    else:
    201201        if not template_name:
  • docs/index.txt

    diff -r ed90e13d2065 docs/index.txt
    a b  
    170170    * :ref:`Internationalization <topics-i18n>`
    171171    * :ref:`Jython support <howto-jython>`
    172172    * :ref:`"Local flavor" <ref-contrib-localflavor>`
     173    * :ref:`Messages <ref-contrib-messages>`
    173174    * :ref:`Pagination <topics-pagination>`
    174175    * :ref:`Redirects <ref-contrib-redirects>`
    175176    * :ref:`Serialization <topics-serialization>`
  • docs/internals/deprecation.txt

    diff -r ed90e13d2065 docs/internals/deprecation.txt
    a b  
    2828        * The many to many SQL generation functions on the database backends
    2929          will be removed.  These have been deprecated since the 1.2 release.
    3030
     31        * The ``Message`` model (in ``django.contrib.auth``) and the
     32          associated methods (such as ``user.message_set.create``), which have
     33          been deprecated since the 1.2 release, will be removed.  The
     34          :ref:`messages framework <ref-contrib-messages>` should be used
     35          instead.
     36
    3137    * 2.0
    3238        * ``django.views.defaults.shortcut()``. This function has been moved
    3339          to ``django.contrib.contenttypes.views.shortcut()`` as part of the
  • docs/ref/contrib/index.txt

    diff -r ed90e13d2065 docs/ref/contrib/index.txt
    a b  
    3434   formtools/index
    3535   humanize
    3636   localflavor
     37   messages
    3738   redirects
    3839   sitemaps
    3940   sites
     
    150151.. _Markdown: http://en.wikipedia.org/wiki/Markdown
    151152.. _ReST (ReStructured Text): http://en.wikipedia.org/wiki/ReStructuredText
    152153
     154messages
     155========
     156
     157.. versionchanged:: 1.2
     158    The messages framework was added.
     159
     160A framework for storing and retrieving temporary cookie- or session-based
     161messages
     162
     163See the :ref:`messages documentation <ref-contrib-messages>`.
     164
    153165redirects
    154166=========
    155167
  • new file docs/ref/contrib/messages.txt

    diff -r ed90e13d2065 docs/ref/contrib/messages.txt
    - +  
     1.. _ref-contrib-messages:
     2
     3======================
     4The messages framework
     5======================
     6
     7.. module:: django.contrib.messages
     8   :synopsis: Provides cookie- and session-based temporary message storage.
     9
     10Django provides full support for cookie- and session-based messaging, for
     11both anonymous and authenticated clients. The message framework allows you
     12to temporarily store messages in one request and retrieve them for display
     13in a subsequent request (usually the next one). Every message is tagged
     14with a specific ``level`` that determines its priority (e.g., ``info``,
     15``warning``, or ``error``).
     16
     17.. versionadded:: 1.2
     18   The messages framework was added.
     19
     20Enabling messages
     21=================
     22
     23Messages are implemented through a :ref:`middleware <ref-middleware>`
     24class and corresponding :ref:`context processor <ref-templates-api>`.
     25
     26To enable message functionality, do the following:
     27
     28    * Edit the :setting:`MIDDLEWARE_CLASSES` setting and make sure
     29      :setting:`MIDDLEWARE_CLASSES` contains ``'django.contrib.messages.middleware.MessageMiddleware'``.
     30
     31    * Edit the :setting:`TEMPLATE_CONTEXT_PROCESSORS` setting and make sure
     32      :setting:`TEMPLATE_CONTEXT_PROCESSORS` contains ``'django.contrib.messages.context_processors.messages'``.
     33
     34    * Add ``'django.contrib.messages'`` to your :setting:`INSTALLED_APPS` setting
     35
     36The default ``settings.py`` created by ``django-admin.py startproject`` has
     37``MessageMiddleware`` activated and the ``django.contrib.messages`` app
     38installed.  Also, the default value for :setting:`TEMPLATE_CONTEXT_PROCESSORS`
     39contains ``'django.contrib.messages.context_processors.messages'``.
     40
     41If you don't want to use messages, you can remove the
     42``MessageMiddleware`` line from :setting:`MIDDLEWARE_CLASSES`, the ``messages``
     43context processor from :setting:`TEMPLATE_CONTEXT_PROCESSORS` and
     44``'django.contrib.messages'`` from your :setting:`INSTALLED_APPS`.
     45
     46Configuring the message engine
     47==============================
     48
     49By default, Django first tries to store messages in a lightweight cookie,
     50and falls back to storing any messages that don't fit in a session variable.
     51
     52Storage backends
     53----------------
     54
     55The messages framework can use different backends to store temporary messages.
     56To change which backend is being used, add a `MESSAGE_STORAGE`_ to your
     57settings, referencing the module and class of the storage class. For
     58example::
     59
     60    MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
     61
     62The value should be the full path of the desired storage class.
     63
     64Four storage classes are included:
     65
     66``'django.contrib.messages.storage.session.SessionStorage'``
     67    This class stores all messages inside of the request's session. It
     68    requires Django's ``contrib.session`` application.
     69
     70``'django.contrib.messages.storage.cookie.CookieStorage'``
     71    This class stores the message data in a cookie (signed with a secret hash to
     72    prevent manipulation) to persist notifications across requests. Old messages
     73    are dropped if the cookie data size would exceed 4096 bytes.
     74
     75``'django.contrib.messages.storage.fallback.FallbackStorage'``
     76    This class first uses CookieStorage for all messages, falling back to using
     77    SessionStorage for the messages that could not fit in a single cookie.
     78
     79    Since it is uses SessionStorage, it also requires Django's
     80    ``contrib.session`` application.
     81
     82``'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'``
     83    This is the default temporary storage class.
     84
     85    This class extends FallbackStorage and adds compatibility methods to
     86    to retrieve any messages stored in the user Message model by code that
     87    has not yet been updated to use the new API. This storage is temporary
     88    (because it makes use of code that is pending deprecation) and will be
     89    removed in Django 1.4. At that time, the default storage will become
     90    ``django.contrib.messages.storage.fallback.FallbackStorage``. For more
     91    information, see `LegacyFallbackStorage`_ below.
     92
     93To write your own storage class, subclass the ``BaseStorage`` class in
     94``django.contrib.messages.storage.base`` and implement the ``_get`` and
     95``_store`` methods.
     96
     97LegacyFallbackStorage
     98^^^^^^^^^^^^^^^^^^^^^
     99
     100The ``LegacyFallbackStorage`` is a temporary tool to facilitate the transition
     101from the deprecated ``user.message_set`` API and will be removed in Django 1.4.
     102
     103In addition to the functionality in the ``FallbackStorage``, it adds a custom,
     104read-only storage class that retrieves messages from the user ``Message``
     105model.  Any messages that were stored in the ``Message`` model (e.g., by code
     106that has not yet been updated to use the messages framework) will be
     107retrieved first, followed those stored in a cookie and session, if any.
     108Since messages stored in the ``Message`` model do not have a concept of levels,
     109they will be assigned the ``INFO`` level by default.
     110
     111Existing coding that calls ``user.message_set.create`` will continue to work
     112in Django 1.2 and Django 1.3.  In Django 1.2, the method raises a
     113``PendingDeprecationWarning`` (which is not shown by default).  In Django 1.3,
     114the method will raise a louder ``DeprecationWarning`` stating that the code at
     115fault needs to be updated.  Any code that uses ``user.message_set`` must be
     116updated to use the messages framework before Django 1.4, when the old API will
     117be removed.
     118
     119
     120Message levels
     121--------------
     122
     123The messages framework is based on a configurable level architecture similar
     124to that of the Python logging module.  Message levels allow you to group
     125messages by type so they can be filtered or displayed differently in views and
     126templates.
     127
     128The built-in levels (which can be imported from ``django.contrib.messages``
     129directly) are:
     130
     131=========== ========
     132Constant    Purpose
     133=========== ========
     134``DEBUG``   Development-related messages that will be ignored (or removed) in a production deployment
     135``INFO``    Informational messages for the user
     136``SUCCESS`` An action was successful, e.g. "Your profile was updated successfully"
     137``WARNING`` A failure did not occur but may be imminent
     138``ERROR``   An action was **not** successful or some other failure occurred
     139=========== ========
     140
     141The `MESSAGE_LEVEL`_ setting can be used to change the minimum recorded
     142level. Attempts to add messages of a level less than this will be ignored.
     143
     144Message tags
     145------------
     146
     147Message tags are a string representation of the message level plus any
     148extra tags that were added directly in the view (see
     149`Adding extra message tags`_ below for more details).  Tags are stored in a
     150string and are separated by spaces.  Typically, message tags
     151are used as CSS classes to customize message style based on message type. By
     152default, each level has a single tag that's a lowercase version of its own
     153constant:
     154
     155==============  ===========
     156Level Constant  Tag
     157==============  ===========
     158``DEBUG``       ``debug``
     159``INFO``        ``info``
     160``SUCCESS``     ``success``
     161``WARNING``     ``warning``
     162``ERROR``       ``error``
     163==============  ===========
     164
     165To change the default tags for a message level (either built-in or custom),
     166set the `MESSAGE_TAGS`_ setting to a dictionary containing the levels
     167you wish to change. As this extends the default tags, you only need to provide
     168tags for the levels you wish to override::
     169
     170    from django.contrib.messages import constants as messages
     171    MESSAGE_TAGS = {
     172        messages.INFO: '',
     173        50: 'critical',
     174    }
     175
     176Using messages in views and templates
     177=====================================
     178
     179Adding a message
     180----------------
     181
     182The middleware attaches an instance of a temporary storage class called
     183``messages`` to your ``request``. To add a message, call::
     184
     185    request.messages.add(messages.INFO, 'Hello world.')
     186
     187Some other methods provide a standard way to add messages with commonly
     188used tags (which are usually represented as HTML classes for the message)::
     189
     190    request.messages.debug('%s SQL statements were executed.' % count)
     191    request.messages.info('Three credits remain in your account.')
     192    request.messages.success('Profile details updated.')
     193    request.messages.warning('Your account expires in three days.')
     194    request.messages.error('Document deleted.')
     195
     196Displaying messages
     197-------------------
     198
     199In your template, use something like::
     200
     201        {% if messages %}
     202        <ul class="messages">
     203                {% for message in messages %}
     204                <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
     205                {% endfor %}
     206        </ul>
     207        {% endif %}
     208
     209If you're using the context processor, your template should be rendered with a
     210``RequestContext``. Otherwise, ensure ``request.messages`` is available to
     211the template context.
     212
     213Creating custom message levels
     214------------------------------
     215
     216Messages levels are nothing more than integers, so you can define your own
     217level constants and use them to create more customized user feedback, e.g.::
     218
     219    CRITICAL = 50
     220   
     221    def my_view(request):
     222        request.messages.add(CRITICAL, 'A serious error occured.')
     223
     224When creating custom message levels you should be careful to avoid overloading
     225existing levels.  The values for the built-in levels are:
     226
     227==============  =====
     228Level Constant  Value
     229==============  =====
     230``DEBUG``       10
     231``INFO``        20
     232``SUCCESS``     25
     233``WARNING``     30
     234``ERROR``       40
     235==============  =====
     236
     237If you need to identify the custom levels in your HTML or CSS, you need to
     238provide a mapping via the `MESSAGE_TAGS`_ setting.
     239
     240**Note:** If you are creating a reusable application, it is recommended to use
     241only the built-in `message levels`_ and not rely on any custom levels.
     242
     243Changing the minimum recorded level per-request
     244-----------------------------------------------
     245
     246The minimum recorded level can be set per request by changing the ``level``
     247attribute of the messages storage instance::
     248
     249    from django.contrib import messages
     250   
     251    # Change the messages level to ensure the debug message is added.
     252    request.messages.level = messages.DEBUG
     253    request.messages.debug('Test message...')
     254   
     255    # In another request, record only messages with a level of WARNING and higher
     256    request.messages.level = messages.WARNING
     257    request.messages.success('Your profile was updated.') # ignored
     258    request.messages.warning('Your account is about to expire.') # recorded
     259   
     260    # Set the messages level back to default.
     261    request.messages.level = None
     262
     263For more information, on how the minimum recorded level functions, see
     264`Message levels`_ above.
     265
     266Adding extra message tags
     267-------------------------
     268
     269For more direct control over message tags, you can optionally provide a string
     270containing extra tags to any of the add methods::
     271
     272    request.messages.add(messages.INFO, 'Over 9000!', extra_tags='dragonball')
     273    request.messages.error('Email box full', extra_tags='email')
     274
     275Extra tags are added before the default tag for that level and are space
     276separated.
     277
     278Expiration of messages
     279======================
     280
     281The messages are marked to be cleared when the storage instance is iterated
     282(and cleared when the response is processed).
     283
     284To avoid the messages being cleared, you can set
     285``request.messages.used = False`` after iterating.
     286
     287Settings
     288========
     289
     290A few :ref:`Django settings <ref-settings>` give you control over message behavior:
     291
     292MESSAGE_LEVEL
     293-------------
     294
     295Default: ``messages.INFO``
     296
     297Important: If you override this in your settings file and rely on any of the built-in
     298constants, you must import the constants module directly to avoid the potential
     299for circular imports.
     300
     301This sets the minimum message that will be saved in the message storage.  See
     302`Message levels`_ above for more details.
     303
     304MESSAGE_STORAGE
     305---------------
     306
     307Default: ``'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'``
     308
     309Controls where Django stores message data. Valid values are:
     310
     311    * ``'django.contrib.messages.storage.fallback.FallbackStorage'``
     312    * ``'django.contrib.messages.storage.session.SessionStorage'``
     313    * ``'django.contrib.messages.storage.cookie.CookieStorage'``
     314    * ``'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'``
     315
     316See `Storage backends`_ for more details.
     317
     318MESSAGE_TAGS
     319------------
     320
     321Default::
     322   
     323        {messages.DEBUG: 'debug',
     324        messages.INFO: 'info',
     325        messages.SUCCESS: 'success',
     326        messages.WARNING: 'warning',
     327        messages.ERROR: 'error',}
     328
     329Important: If you override this in your settings file and rely on any of the built-in
     330constants, you must import the constants module directly to avoid the potential
     331for circular imports.
     332
     333This sets the mapping of message level to message tag, which is typically
     334rendered as a CSS class in HTML. If you specify a value, it will extend
     335the default. This means you only have to specify those values which you need
     336to override. See `Displaying messages`_ above for more details.
     337
     338.. _Django settings: ../settings/
     339
  • docs/ref/settings.txt

    diff -r ed90e13d2065 docs/ref/settings.txt
    a b  
    812812
    813813.. setting:: MIDDLEWARE_CLASSES
    814814
     815MESSAGE_LEVEL
     816-------------
     817
     818.. versionadded:: 1.2
     819
     820Default: `messages.INFO`
     821
     822Sets the minimum message level that will be recorded by the messages
     823framework. See the :ref:`messages documentation <ref-contrib-messages>` for
     824more details.
     825
     826MESSAGE_STORAGE
     827---------------
     828
     829.. versionadded:: 1.2
     830
     831Default: ``'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'``
     832
     833Controls where Django stores message data.  See the
     834:ref:`messages documentation <ref-contrib-messages>` for more details.
     835
     836MESSAGE_TAGS
     837------------
     838
     839.. versionadded:: 1.2
     840
     841Default::
     842
     843        {messages.DEBUG: 'debug',
     844        messages.INFO: 'info',
     845        messages.SUCCESS: 'success',
     846        messages.WARNING: 'warning',
     847        messages.ERROR: 'error',}
     848
     849Sets the mapping of message levels to message tags. See the
     850:ref:`messages documentation <ref-contrib-messages>` for more details.
     851
    815852MIDDLEWARE_CLASSES
    816853------------------
    817854
     
    820857    ('django.middleware.common.CommonMiddleware',
    821858     'django.contrib.sessions.middleware.SessionMiddleware',
    822859     'django.middleware.csrf.CsrfViewMiddleware',
    823      'django.contrib.auth.middleware.AuthenticationMiddleware',)
     860     'django.contrib.auth.middleware.AuthenticationMiddleware',
     861     'django.contrib.messages.middleware.MessageMiddleware',)
    824862
    825863A tuple of middleware classes to use. See :ref:`topics-http-middleware`.
    826864
     
    10591097    ("django.core.context_processors.auth",
    10601098    "django.core.context_processors.debug",
    10611099    "django.core.context_processors.i18n",
    1062     "django.core.context_processors.media")
     1100    "django.core.context_processors.media",
     1101    "django.contrib.messages.context_processors.messages")
    10631102
    10641103A tuple of callables that are used to populate the context in ``RequestContext``.
    10651104These callables take a request object as their argument and return a dictionary
  • docs/ref/templates/api.txt

    diff -r ed90e13d2065 docs/ref/templates/api.txt
    a b  
    311311    ("django.core.context_processors.auth",
    312312    "django.core.context_processors.debug",
    313313    "django.core.context_processors.i18n",
    314     "django.core.context_processors.media")
     314    "django.core.context_processors.media",
     315    "django.contrib.messages.context_processors.messages")
    315316
    316317.. versionadded:: 1.2
    317318   In addition to these, ``RequestContext`` always uses
     
    320321   in case of accidental misconfiguration, it is deliberately hardcoded in and
    321322   cannot be turned off by the :setting:`TEMPLATE_CONTEXT_PROCESSORS` setting.
    322323
     324.. versionadded:: 1.2
     325   The ``'messages'`` context processor was added.  For more information, see
     326   the :ref:`messages documentation <ref-contrib-messages>`.
     327
    323328Each processor is applied in order. That means, if one processor adds a
    324329variable to the context and a second processor adds a variable with the same
    325330name, the second will override the first. The default processors are explained
     
    365370      logged-in user (or an ``AnonymousUser`` instance, if the client isn't
    366371      logged in).
    367372
    368     * ``messages`` -- A list of messages (as strings) for the currently
    369       logged-in user. Behind the scenes, this calls
    370       ``request.user.get_and_delete_messages()`` for every request. That method
    371       collects the user's messages and deletes them from the database.
    372 
    373       Note that messages are set with ``user.message_set.create``.
     373    * ``messages`` -- A list of messages (as strings) that have been set
     374      via the user model (using ``user.message_set.create``) or through
     375      the :ref:`messages framework <ref-contrib-messages>`.
    374376
    375377    * ``perms`` -- An instance of
    376378      ``django.core.context_processors.PermWrapper``, representing the
    377379      permissions that the currently logged-in user has.
    378380
     381.. versionchanged:: 1.2
     382   The ``messages`` variable was changed to include any messages added
     383   via the :ref:`messages framework <ref-contrib-messages`.
     384
    379385django.core.context_processors.debug
    380386~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    381387
     
    427433:class:`~django.http.HttpRequest`. Note that this processor is not enabled by default;
    428434you'll have to activate it.
    429435
     436django.contrib.messages.context_processors.messages
     437~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     438
     439If :setting:`TEMPLATE_CONTEXT_PROCESSORS` contains this processor, every
     440``RequestContext`` will contain a single additional variable:
     441
     442    * ``messages`` -- A list of messages (as strings) that have been set
     443      via the user model (using ``user.message_set.create``) or through
     444      the :ref:`messages framework <ref-contrib-messages>`.
     445
     446.. versionadded:: 1.2
     447   This template context variable was previously supplied by the ``'auth'``
     448   context processor.  For backwards compatibility the ``'auth'`` context
     449   processor will continue to supply the ``messages`` variable until Django
     450   1.4.  If you use the ``messages`` variable, your project will work with
     451   either (or both) context processors, but it is recommended to add
     452   ``django.contrib.messages.context_processors.messages`` so your project
     453   will be prepared for the future upgrade.
     454
    430455Writing your own context processors
    431456~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    432457
  • docs/topics/auth.txt

    diff -r ed90e13d2065 docs/topics/auth.txt
    a b  
    12891289Messages
    12901290========
    12911291
     1292**NOTE:** This functionality is pending deprecation and will be removed in
     1293Django 1.4.  It is recommended to use the
     1294:ref:`messages framework <ref-contrib-messages>` for new projects whenever
     1295possible.
     1296
    12921297The message system is a lightweight way to queue messages for given users.
    12931298
    12941299A message is associated with a :class:`~django.contrib.auth.models.User`.
     
    13341339    </ul>
    13351340    {% endif %}
    13361341
    1337 Note that :class:`~django.template.context.RequestContext` calls
    1338 :meth:`~django.contrib.auth.models.User.get_and_delete_messages` behind the
    1339 scenes, so any messages will be deleted even if you don't display them.
     1342.. versionchanged:: 1.2
     1343   The ``messages`` template variable uses a backwards compatible method in the
     1344   :ref:`messages framework <ref-contrib-messages>` to retrieve messages from
     1345   both the user ``Message`` model and from the new framework.  Unlike in
     1346   previous revisions, the messages will not be erased unless they are actually
     1347   displayed.
    13401348
    13411349Finally, note that this messages framework only works with users in the user
    13421350database. To send messages to anonymous users, use the
    1343 :ref:`session framework <topics-http-sessions>`.
     1351:ref:`messages framework <ref-contrib-messages>`.
    13441352
    13451353.. _authentication-backends:
    13461354
  • tests/runtests.py

    diff -r ed90e13d2065 tests/runtests.py
    a b  
    2828    'django.contrib.flatpages',
    2929    'django.contrib.redirects',
    3030    'django.contrib.sessions',
     31    'django.contrib.messages',
    3132    'django.contrib.comments',
    3233    'django.contrib.admin',
    3334]
     
    107108    settings.MIDDLEWARE_CLASSES = (
    108109        'django.contrib.sessions.middleware.SessionMiddleware',
    109110        'django.contrib.auth.middleware.AuthenticationMiddleware',
     111        'django.contrib.messages.middleware.MessageMiddleware',
    110112        'django.middleware.common.CommonMiddleware',
    111113    )
    112114    settings.SITE_ID = 1
Back to Top