Django

Code

Ticket #4604: django-contrib-messages-6399c12d1773.diff

File django-contrib-messages-6399c12d1773.diff, 98.0 kB (added by tobias, 4 months ago)

changes in django-contrib-messages branch as of rev 6399c12d1773

  • a/django/conf/global_settings.py

    old new  
    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) 
  • a/django/conf/project_template/settings.py

    old new  
    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) 
  • a/django/contrib/admin/options.py

    old new  
    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 
    910from django.views.decorators.csrf import csrf_protect 
    1011from django.core.exceptions import PermissionDenied 
    1112from django.db import models, transaction 
     
    541542    def message_user(self, request, message): 
    542543        """ 
    543544        Send a message to the user. The default implementation 
    544         posts a message using the auth Message object
     545        posts a message using the django.contrib.messages backend
    545546        """ 
    546         request.user.message_set.create(message=message) 
     547        messages.info(request, message) 
    547548 
    548549    def save_form(self, request, form, change): 
    549550        """ 
  • a/django/contrib/admin/views/template.py

    old new  
    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 
    910 
    1011 
    1112def template_validator(request): 
     
    2324        form = TemplateValidatorForm(settings_modules, site_list, 
    2425                                     data=request.POST) 
    2526        if form.is_valid(): 
    26             request.user.message_set.create(message='The template is valid.') 
     27            messages.info(request, 'The template is valid.') 
    2728    else: 
    2829        form = TemplateValidatorForm(settings_modules, site_list) 
    2930    return render_to_response('admin/template_validator.html', { 
  • a/django/contrib/auth/admin.py

    old new  
    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 
    67from django.core.exceptions import PermissionDenied 
    78from django.http import HttpResponseRedirect, Http404 
    89from django.shortcuts import render_to_response, get_object_or_404 
     
    6768                msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': 'user', 'obj': new_user} 
    6869                self.log_addition(request, new_user) 
    6970                if "_addanother" in request.POST: 
    70                     request.user.message_set.create(message=msg) 
     71                    messages.success(request, msg) 
    7172                    return HttpResponseRedirect(request.path) 
    7273                elif '_popup' in request.REQUEST: 
    7374                    return self.response_add(request, new_user) 
    7475                else: 
    75                     request.user.message_set.create(message=msg + ' ' + ugettext("You may edit it again below.")) 
     76                    messages.success(request, msg + ' ' + 
     77                                     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                messages.success(request, msg) 
    108110                return HttpResponseRedirect('..') 
    109111        else: 
    110112            form = self.change_password_form(user) 
  • a/django/contrib/auth/models.py

    old new  
    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 is deprecated. Please update' 
     294                      ' your code to use the new messages framework.', 
     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): 
  • /dev/null

    old new  
     1from api import * 
     2from constants import * 
  • /dev/null

    old new  
     1from django.contrib.messages import constants 
     2from django.utils.functional import lazy, memoize 
     3 
     4__all__ = ( 
     5    'add_message', 'get_messages', 
     6    'debug', 'info', 'success', 'warning', 'error', 
     7) 
     8 
     9 
     10class MessageFailure(Exception): 
     11    pass 
     12 
     13 
     14def add_message(request, level, message, extra_tags='', fail_silently=False): 
     15    """ 
     16    Attempts to add a message to the request using the 'messages' app, falling 
     17    back to the user's message_set if MessageMiddleware hasn't been enabled. 
     18    """ 
     19    if hasattr(request, '_messages'): 
     20        return request._messages.add(level, message, extra_tags) 
     21    if hasattr(request, 'user') and request.user.is_authenticated(): 
     22        return request.user.message_set.create(message=message) 
     23    if not fail_silently: 
     24        raise MessageFailure('Without the django.contrib.messages ' 
     25                                'middleware, messages can only be added to ' 
     26                                'authenticated users.') 
     27 
     28 
     29def get_messages(request): 
     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    if hasattr(request, '_messages'): 
     35        return request._messages 
     36 
     37    def get_user(): 
     38        if hasattr(request, 'user'): 
     39            return request.user 
     40        else: 
     41            from django.contrib.auth.models import AnonymousUser 
     42            return AnonymousUser() 
     43 
     44    return lazy(memoize(get_user().get_and_delete_messages, {}, 0), list)() 
     45 
     46 
     47def debug(request, message, extra_tags='', fail_silently=False): 
     48    """ 
     49    Adds a message with the ``DEBUG`` level. 
     50    """ 
     51    add_message(request, constants.DEBUG, message, extra_tags=extra_tags, 
     52                fail_silently=fail_silently) 
     53 
     54 
     55def info(request, message, extra_tags='', fail_silently=False): 
     56    """ 
     57    Adds a message with the ``INFO`` level. 
     58    """ 
     59    add_message(request, constants.INFO, message, extra_tags=extra_tags, 
     60                fail_silently=fail_silently) 
     61 
     62 
     63def success(request, message, extra_tags='', fail_silently=False): 
     64    """ 
     65    Adds a message with the ``SUCCESS`` level. 
     66    """ 
     67    add_message(request, constants.SUCCESS, message, extra_tags=extra_tags, 
     68                fail_silently=fail_silently) 
     69 
     70 
     71def warning(request, message, extra_tags='', fail_silently=False): 
     72    """ 
     73    Adds a message with the ``WARNING`` level. 
     74    """ 
     75    add_message(request, constants.WARNING, message, extra_tags=extra_tags, 
     76                fail_silently=fail_silently) 
     77 
     78 
     79def error(request, message, extra_tags='', fail_silently=False): 
     80    """ 
     81    Adds a message with the ``ERROR`` level. 
     82    """ 
     83    add_message(request, constants.ERROR, message, extra_tags=extra_tags, 
     84                fail_silently=fail_silently) 
  • /dev/null

    old new  
     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} 
  • /dev/null

    old new  
     1from django.contrib.messages.api import get_messages 
     2 
     3 
     4def messages(request): 
     5    """ 
     6    Returns a lazy 'messages' context variable. 
     7    """ 
     8    return {'messages': get_messages(request)} 
  • /dev/null

    old new  
     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 
  • /dev/null

    old new  
     1# Models module required so tests are discovered. 
  • /dev/null

    old new  
     1from django.conf import settings 
     2from django.utils.importlib import import_module 
     3 
     4 
     5def get_storage(import_path): 
     6    """ 
     7    Imports the message storage class described by import_path, where 
     8    import_path is the full Python path to the class. 
     9    """ 
     10    try: 
     11        dot = import_path.rindex('.') 
     12    except ValueError: 
     13        raise ImproperlyConfigured("%s isn't a Python path." % import_path) 
     14    module, classname = import_path[:dot], import_path[dot + 1:] 
     15    try: 
     16        mod = import_module(module) 
     17    except ImportError, e: 
     18        raise ImproperlyConfigured('Error importing module %s: "%s"' % 
     19                                   (module, e)) 
     20    try: 
     21        return getattr(mod, classname) 
     22    except AttributeError: 
     23        raise ImproperlyConfigured('Module "%s" does not define a "%s" ' 
     24                                   'class.' % (module, classname)) 
     25 
     26 
     27Storage = get_storage(getattr(settings, 'MESSAGE_STORAGE', 
     28    'django.contrib.messages.storage.user_messages.LegacyFallbackStorage')) 
  • /dev/null

    old new  
     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 __eq__(self, other): 
     33        return isinstance(other, Message) and self.level == other.level and \ 
     34                                              self.message == other.message 
     35 
     36    def __unicode__(self): 
     37        return force_unicode(self.message) 
     38 
     39    def _get_tags(self): 
     40        label_tag = force_unicode(LEVEL_TAGS.get(self.level, ''), 
     41                                  strings_only=True) 
     42        extra_tags = force_unicode(self.extra_tags, strings_only=True) 
     43        if extra_tags and label_tag: 
     44            return u' '.join([extra_tags, label_tag]) 
     45        elif extra_tags: 
     46            return extra_tags 
     47        elif label_tag: 
     48            return label_tag 
     49        return '' 
     50    tags = property(_get_tags) 
     51 
     52 
     53class BaseStorage(object): 
     54    """ 
     55    This is the base backend for temporary message storage. 
     56 
     57    This is not a complete class; to be a usable storage backend, it must be 
     58    subclassed and the two methods ``_get`` and ``_store`` overridden. 
     59    """ 
     60 
     61    def __init__(self, request, *args, **kwargs): 
     62        self.request = request 
     63        self._queued_messages = [] 
     64        self.used = False 
     65        self.added_new = False 
     66        super(BaseStorage, self).__init__(*args, **kwargs) 
     67 
     68    def __len__(self): 
     69        return len(self._loaded_messages) + len(self._queued_messages) 
     70 
     71    def __iter__(self): 
     72        self.used = True 
     73        if self._queued_messages: 
     74            self._loaded_messages.extend(self._queued_messages) 
     75            self._queued_messages = [] 
     76        return iter(self._loaded_messages) 
     77 
     78    def __contains__(self, item): 
     79        return item in self._loaded_messages or item in self._queued_messages 
     80 
     81    @property 
     82    def _loaded_messages(self): 
     83        """ 
     84        Returns a list of loaded messages, retrieving them first if they have 
     85        not been loaded yet. 
     86        """ 
     87        if not hasattr(self, '_loaded_data'): 
     88            messages, all_retrieved = self._get() 
     89            self._loaded_data = messages or [] 
     90        return self._loaded_data 
     91 
     92    def _get(self, *args, **kwargs): 
     93        """ 
     94        Retrieves a list of stored messages. Returns a tuple of the messages 
     95        and a flag indicating whether or not all the messages originally 
     96        intended to be stored in this storage were, in fact, stored and 
     97        retrieved; e.g., ``(messages, all_retrieved)``. 
     98 
     99        **This method must be implemented by a subclass.** 
     100 
     101        If it is possible to tell if the backend was not used (as opposed to 
     102        just containing no messages) then ``None`` should be returned in 
     103        place of ``messages``. 
     104        """ 
     105        raise NotImplementedError() 
     106 
     107    def _store(self, messages, response, *args, **kwargs): 
     108        """ 
     109        Stores a list of messages, returning a list of any messages which could 
     110        not be stored. 
     111 
     112        One type of object must be able to be stored, ``Message``. 
     113 
     114        **This method must be implemented by a subclass.** 
     115        """ 
     116        raise NotImplementedError() 
     117 
     118    def _prepare_messages(self, messages): 
     119        """ 
     120        Prepares a list of messages for storage. 
     121        """ 
     122        for message in messages: 
     123            message._prepare() 
     124 
     125    def update(self, response): 
     126        """ 
     127        Stores all unread messages. 
     128 
     129        If the backend has yet to be iterated, previously stored messages will 
     130        be stored again. Otherwise, only messages added after the last 
     131        iteration will be stored. 
     132        """ 
     133        self._prepare_messages(self._queued_messages) 
     134        if self.used: 
     135            return self._store(self._queued_messages, response) 
     136        elif self.added_new: 
     137            messages = self._loaded_messages + self._queued_messages 
     138            return self._store(messages, response) 
     139 
     140    def add(self, level, message, extra_tags=''): 
     141        """ 
     142        Queues a message to be stored. 
     143 
     144        The message is only queued if it contained something and its level is 
     145        not less than the recording level (``self.level``). 
     146        """ 
     147        if not message: 
     148            return 
     149        # Check that the message level is not less than the recording level. 
     150        level = int(level) 
     151        if level < self.level: 
     152            return 
     153        # Add the message. 
     154        self.added_new = True 
     155        message = Message(level, message, extra_tags=extra_tags) 
     156        self._queued_messages.append(message) 
     157 
     158    def _get_level(self): 
     159        """ 
     160        Returns the minimum recorded level. 
     161 
     162        The default level is the ``MESSAGE_LEVEL`` setting. If this is 
     163        not found, the ``INFO`` level is used. 
     164        """ 
     165        if not hasattr(self, '_level'): 
     166            self._level = getattr(settings, 'MESSAGE_LEVEL', constants.INFO) 
     167        return self._level 
     168 
     169    def _set_level(self, value=None): 
     170        """ 
     171        Sets a custom minimum recorded level. 
     172 
     173        If set to ``None``, the default level will be used (see the 
     174        ``_get_level`` method). 
     175        """ 
     176        if value is None and hasattr(self, '_level'): 
     177            del self._level 
     178        else: 
     179            self._level = int(value) 
     180 
     181    level = property(_get_level, _set_level, _set_level) 
  • /dev/null

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

    old new  
     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(messages, response, 
     53                                          remove_oldest=False) 
     54            # Even if there are no more messages, continue iterating to ensure 
     55            # storages which contained messages are flushed. 
     56            elif storage in self._used_storages: 
     57                storage._store([], response) 
     58                self._used_storages.remove(storage) 
     59        return messages 
  • /dev/null

    old new  
     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            "and come before the message middleware in the "\ 
     14            "MIDDLEWARE_CLASSES list." 
     15        super(SessionStorage, self).__init__(request, *args, **kwargs) 
     16 
     17    def _get(self, *args, **kwargs): 
     18        """ 
     19        Retrieves a list of messages from the request's session.  This storage 
     20        always stores everything it is given, so return True for the 
     21        all_retrieved flag. 
     22        """ 
     23        return self.request.session.get(self.session_key), True 
     24 
     25    def _store(self, messages, response, *args, **kwargs): 
     26        """ 
     27        Stores a list of messages to the request's session. 
     28        """ 
     29        if messages: 
     30            self.request.session[self.session_key] = messages 
     31        else: 
     32            self.request.session.pop(self.session_key, None) 
     33        return [] 
  • /dev/null

    old new  
     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 
  • /dev/null

    old new  
     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 
  • a/django/core/context_processors.py

    old new  
    1010from django.conf import settings 
    1111from django.middleware.csrf import get_token 
    1212from django.utils.functional import lazy, memoize, SimpleLazyObject 
     13from django.contrib import 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         'perms':  lazy(lambda: PermWrapper(get_user()), PermWrapper)(), 
     41        'messages': messages.get_messages(request), 
     42        'perms': lazy(lambda: PermWrapper(get_user()), PermWrapper)(), 
    4243    } 
    4344 
    4445def csrf(request): 
  • a/django/views/generic/create_update.py

    old new  
    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 
    910 
    1011 
    1112def apply_extra_context(extra_context, context): 
     
    110111        form = form_class(request.POST, request.FILES) 
    111112        if form.is_valid(): 
    112113            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}) 
     114             
     115            msg = ugettext("The %(verbose_name)s was created successfully.") %\ 
     116                                    {"verbose_name": model._meta.verbose_name} 
     117            messages.success(request, msg, fail_silently=True) 
    115118            return redirect(post_save_redirect, new_object) 
    116119    else: 
    117120        form = form_class() 
     
    152155        form = form_class(request.POST, request.FILES, instance=obj) 
    153156        if form.is_valid(): 
    154157            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}) 
     158            msg = ugettext("The %(verbose_name)s was updated successfully.") %\ 
     159                                    {"verbose_name": model._meta.verbose_name} 
     160            messages.success(request, msg, fail_silently=True) 
    157161            return redirect(post_save_redirect, obj) 
    158162    else: 
    159163        form = form_class(instance=obj) 
     
    194198 
    195199    if request.method == 'POST': 
    196200        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}) 
     201        msg = ugettext("The %(verbose_name)s was deleted.") %\ 
     202                                    {"verbose_name": model._meta.verbose_name} 
     203        messages.success(request, msg, fail_silently=True) 
    199204        return HttpResponseRedirect(post_delete_redirect) 
    200205    else: 
    201206        if not template_name: 
  • a/docs/index.txt

    old new  
    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>` 
  • a/docs/internals/deprecation.txt

    old new  
    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``), its related 
     32          manager in the ``User`` model (``user.message_set``), and the 
     33          associated methods (``user.message_set.create()`` and 
     34          ``user.get_and_delete_messages()``), which have 
     35          been deprecated since the 1.2 release, will be removed.  The  
     36          :ref:`messages framework <ref-contrib-messages>` should be used  
     37          instead. 
     38 
    3139    * 2.0 
    3240        * ``django.views.defaults.shortcut()``. This function has been moved 
    3341          to ``django.contrib.contenttypes.views.shortcut()`` as part of the 
  • a/docs/ref/contrib/index.txt

    old new  
    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 
  • /dev/null

    old new  
     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 messages 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      it contains ``'django.contrib.messages.middleware.MessageMiddleware'``. 
     30 
     31      If you are using a :ref:`storage backend <message-storage-backends>` that 
     32      relies on :ref:`sessions <topics-http-sessions>` (the default), 
     33      ``'django.contrib.sessions.middleware.SessionMiddleware'`` must be 
     34      enabled and appear before ``MessageMiddleware`` in your 
     35      :setting:`MIDDLEWARE_CLASSES`. 
     36 
     37    * Edit the :setting:`TEMPLATE_CONTEXT_PROCESSORS` setting and make sure 
     38      it contains ``'django.contrib.messages.context_processors.messages'``. 
     39 
     40    * Add ``'django.contrib.messages'`` to your :setting:`INSTALLED_APPS` 
     41      setting 
     42 
     43The default ``settings.py`` created by ``django-admin.py startproject`` has 
     44``MessageMiddleware`` activated and the ``django.contrib.messages`` app 
     45installed.  Also, the default value for :setting:`TEMPLATE_CONTEXT_PROCESSORS` 
     46contains ``'django.contrib.messages.context_processors.messages'``. 
     47 
     48If you don't want to use messages, you can remove the 
     49``MessageMiddleware`` line from :setting:`MIDDLEWARE_CLASSES`, the ``messages`` 
     50context processor from :setting:`TEMPLATE_CONTEXT_PROCESSORS` and 
     51``'django.contrib.messages'`` from your :setting:`INSTALLED_APPS`. 
     52 
     53Configuring the message engine 
     54============================== 
     55 
     56.. _message-storage-backends: 
     57 
     58Storage backends 
     59---------------- 
     60 
     61The messages framework can use different backends to store temporary messages. 
     62To change which backend is being used, add a `MESSAGE_STORAGE`_ to your 
     63settings, referencing the module and class of the storage class. For 
     64example:: 
     65 
     66    MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage' 
     67 
     68The value should be the full path of the desired storage class. 
     69 
     70Four storage classes are included: 
     71 
     72``'django.contrib.messages.storage.session.SessionStorage'`` 
     73    This class stores all messages inside of the request's session. It 
     74    requires Django's ``contrib.session`` application. 
     75 
     76``'django.contrib.messages.storage.cookie.CookieStorage'`` 
     77    This class stores the message data in a cookie (signed with a secret hash 
     78    to prevent manipulation) to persist notifications across requests. Old 
     79    messages are dropped if the cookie data size would exceed 4096 bytes. 
     80 
     81``'django.contrib.messages.storage.fallback.FallbackStorage'`` 
     82    This class first uses CookieStorage for all messages, falling back to using 
     83    SessionStorage for the messages that could not fit in a single cookie. 
     84 
     85    Since it is uses SessionStorage, it also requires Django's 
     86    ``contrib.session`` application. 
     87 
     88``'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'`` 
     89    This is the default temporary storage class. 
     90 
     91    This class extends FallbackStorage and adds compatibility methods to 
     92    to retrieve any messages stored in the user Message model by code that 
     93    has not yet been updated to use the new API. This storage is temporary 
     94    (because it makes use of code that is pending deprecation) and will be 
     95    removed in Django 1.4. At that time, the default storage will become 
     96    ``django.contrib.messages.storage.fallback.FallbackStorage``. For more 
     97    information, see `LegacyFallbackStorage`_ below. 
     98 
     99To write your own storage class, subclass the ``BaseStorage`` class in 
     100``django.contrib.messages.storage.base`` and implement the ``_get`` and 
     101``_store`` methods. 
     102 
     103LegacyFallbackStorage 
     104^^^^^^^^^^^^^^^^^^^^^ 
     105 
     106The ``LegacyFallbackStorage`` is a temporary tool to facilitate the transition 
     107from the deprecated ``user.message_set`` API and will be removed in Django 1.4 
     108according to Django's standard deprecation policy.  For more information, see 
     109the full :ref:`release process documentation <internals-release-process>`. 
     110 
     111In addition to the functionality in the ``FallbackStorage``, it adds a custom, 
     112read-only storage class that retrieves messages from the user ``Message`` 
     113model. Any messages that were stored in the ``Message`` model (e.g., by code 
     114that has not yet been updated to use the messages framework) will be retrieved 
     115first, followed by those stored in a cookie and in the session, if any.  Since 
     116messages stored in the ``Message`` model do not have a concept of levels, they 
     117will be assigned the ``INFO`` level by default. 
     118 
     119Message levels 
     120-------------- 
     121 
     122The messages framework is based on a configurable level architecture similar 
     123to that of the Python logging module.  Message levels allow you to group 
     124messages by type so they can be filtered or displayed differently in views and 
     125templates. 
     126 
     127The built-in levels (which can be imported from ``django.contrib.messages`` 
     128directly) are: 
     129 
     130=========== ======== 
     131Constant    Purpose 
     132=========== ======== 
     133``DEBUG``   Development-related messages that will be ignored (or removed) in a production deployment 
     134``INFO``    Informational messages for the user 
     135``SUCCESS`` An action was successful, e.g. "Your profile was updated successfully" 
     136``WARNING`` A failure did not occur but may be imminent 
     137``ERROR``   An action was **not** successful or some other failure occurred 
     138=========== ======== 
     139 
     140The `MESSAGE_LEVEL`_ setting can be used to change the minimum recorded 
     141level. Attempts to add messages of a level less than this will be ignored. 
     142 
     143Message tags 
     144------------ 
     145 
     146Message tags are a string representation of the message level plus any 
     147extra tags that were added directly in the view (see 
     148`Adding extra message tags`_ below for more details).  Tags are stored in a 
     149string and are separated by spaces.  Typically, message tags 
     150are used as CSS classes to customize message style based on message type. By 
     151default, each level has a single tag that's a lowercase version of its own 
     152constant: 
     153 
     154==============  =========== 
     155Level Constant  Tag 
     156==============  =========== 
     157``DEBUG``       ``debug`` 
     158``INFO``        ``info`` 
     159``SUCCESS``     ``success`` 
     160``WARNING``     ``warning`` 
     161``ERROR``       ``error`` 
     162==============  =========== 
     163 
     164To change the default tags for a message level (either built-in or custom), 
     165set the `MESSAGE_TAGS`_ setting to a dictionary containing the levels 
     166you wish to change. As this extends the default tags, you only need to provide 
     167tags for the levels you wish to override:: 
     168 
     169    from django.contrib.messages import constants as messages 
     170    MESSAGE_TAGS = { 
     171        messages.INFO: '', 
     172        50: 'critical', 
     173    } 
     174 
     175Using messages in views and templates 
     176===================================== 
     177 
     178Adding a message 
     179---------------- 
     180 
     181To add a message, call:: 
     182 
     183    from django.contrib import messages 
     184    messages.add_message(request, messages.INFO, 'Hello world.') 
     185 
     186Some shortcut methods provide a standard way to add messages with commonly 
     187used tags (which are usually represented as HTML classes for the message):: 
     188 
     189    messages.debug(request, '%s SQL statements were executed.' % count) 
     190    messages.info(request, 'Three credits remain in your account.') 
     191    messages.success(request, 'Profile details updated.') 
     192    messages.warning(request, 'Your account expires in three days.') 
     193    messages.error(request, 'Document deleted.') 
     194 
     195Displaying messages 
     196------------------- 
     197 
     198In your template, use something like:: 
     199 
     200    {% if messages %} 
     201    <ul class="messages"> 
     202        {% for message in messages %} 
     203        <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li> 
     204        {% endfor %} 
     205    </ul> 
     206    {% endif %} 
     207 
     208If you're using the context processor, your template should be rendered with a 
     209``RequestContext``. Otherwise, ensure ``messages`` is available to 
     210the template context. 
     211 
     212Creating custom message levels 
     213------------------------------ 
     214 
     215Messages levels are nothing more than integers, so you can define your own 
     216level constants and use them to create more customized user feedback, e.g.:: 
     217 
     218    CRITICAL = 50 
     219 
     220    def my_view(request): 
     221        messages.add_message(request, CRITICAL, 'A serious error occurred.') 
     222 
     223When creating custom message levels you should be careful to avoid overloading 
     224existing levels.  The values for the built-in levels are: 
     225 
     226.. _message-level-constants: 
     227 
     228==============  ===== 
     229Level Constant  Value 
     230==============  ===== 
     231``DEBUG``       10 
     232``INFO``        20 
     233``SUCCESS``     25 
     234``WARNING``     30 
     235``ERROR``       40 
     236==============  ===== 
     237 
     238If you need to identify the custom levels in your HTML or CSS, you need to 
     239provide a mapping via the `MESSAGE_TAGS`_ setting. 
     240 
     241.. note:: 
     242   If you are creating a reusable application, it is recommended to use 
     243   only the built-in `message levels`_ and not rely on any custom levels. 
     244 
     245Changing the minimum recorded level per-request 
     246----------------------------------------------- 
     247 
     248The minimum recorded level can be set per request by changing the ``level`` 
     249attribute of the messages storage instance:: 
     250 
     251    from django.contrib import messages 
     252 
     253    # Change the messages level to ensure the debug message is added. 
     254    messages.get_messages(request).level = messages.DEBUG 
     255    messages.debug(request, 'Test message...') 
     256 
     257    # In another request, record only messages with a level of WARNING and higher 
     258    messages.get_messages(request).level = messages.WARNING 
     259    messages.success(request, 'Your profile was updated.') # ignored 
     260    messages.warning(request, 'Your account is about to expire.') # recorded 
     261 
     262    # Set the messages level back to default. 
     263    messages.get_messages(request).level = None 
     264 
     265For more information on how the minimum recorded level functions, see 
     266`Message levels`_ above. 
     267 
     268Adding extra message tags 
     269------------------------- 
     270 
     271For more direct control over message tags, you can optionally provide a string 
     272containing extra tags to any of the add methods:: 
     273 
     274    messages.add_message(request, messages.INFO, 'Over 9000!', 
     275                         extra_tags='dragonball') 
     276    messages.error(request, 'Email box full', extra_tags='email') 
     277 
     278Extra tags are added before the default tag for that level and are space 
     279separated. 
     280 
     281Failing silently when the message framework is disabled 
     282------------------------------------------------------- 
     283 
     284If you're writing a reusable app (or other piece of code) and want to include 
     285messaging functionality, but don't want to require your users to enable it 
     286if they don't want to, you may pass an additional keyword argument 
     287``fail_silently=True`` to any of the ``add_message`` family of methods. For 
     288example:: 
     289 
     290    messages.add_message(request, messages.SUCCESS, 'Profile details updated.', 
     291                         fail_silently=True) 
     292    messages.info(request, 'Hello world.', fail_silently=True) 
     293 
     294Internally, Django uses this functionality in the create, update, and delete 
     295:ref:`generic views <topics-generic-views>` so that they work even if the 
     296message framework is disabled. 
     297 
     298.. note:: 
     299   Setting ``fail_silently=True`` only hides the ``MessageFailure`` that would 
     300   otherwise occur when the messages framework disabled and one attempts to 
     301   use one of the ``add_message`` family of methods.  It does not hide failures 
     302   that may occur for other reasons. 
     303 
     304Expiration of messages 
     305====================== 
     306 
     307The messages are marked to be cleared when the storage instance is iterated 
     308(and cleared when the response is processed). 
     309 
     310To avoid the messages being cleared, you can set the messages storage to 
     311``False`` after iterating:: 
     312 
     313    storage = messages.get_messages(request) 
     314    for message in storage: 
     315        do_something_with(message) 
     316    storage.used = False 
     317 
     318Behavior of parallel requests 
     319============================= 
     320 
     321Due to the way cookies (and hence sessions) work, **the behavior of any 
     322backends that make use of cookies or sessions is undefined when the same 
     323client makes multiple requests that set or get messages in parallel**.  For 
     324example, if a client initiates a request that creates a message in one window 
     325(or tab) and then another that fetches any uniterated messages in another 
     326window, before the first window redirects, the message may appear in the 
     327second window instead of the first window where it may be expected. 
     328 
     329In short, when multiple simultaneous requests from the same client are 
     330involved, messages are not guaranteed to be delivered to the same window that 
     331created them nor, in some cases, at all.  Note that this is typically not a 
     332problem in most applications and will become a non-issue in HTML5, where each 
     333window/tab will have its own browsing context. 
     334 
     335Settings 
     336======== 
     337 
     338A few :ref:`Django settings <ref-settings>` give you control over message 
     339behavior: 
     340 
     341MESSAGE_LEVEL 
     342------------- 
     343 
     344Default: ``messages.INFO`` 
     345 
     346This sets the minimum message that will be saved in the message storage.  See 
     347`Message levels`_ above for more details. 
     348 
     349.. admonition:: Important 
     350 
     351   If you override ``MESSAGE_LEVEL`` in your settings file and rely on any of 
     352   the built-in constants, you must import the constants module directly to 
     353   avoid the potential for circular imports, e.g.:: 
     354 
     355       from django.contrib.messages import constants as message_constants 
     356       MESSAGE_LEVEL = message_constants.DEBUG 
     357 
     358   If desired, you may specify the numeric values for the constants directly 
     359   according to the values in the above :ref:`constants table 
     360   <message-level-constants>`. 
     361 
     362MESSAGE_STORAGE 
     363--------------- 
     364 
     365Default: ``'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'`` 
     366 
     367Controls where Django stores message data. Valid values are: 
     368 
     369    * ``'django.contrib.messages.storage.fallback.FallbackStorage'`` 
     370    * ``'django.contrib.messages.storage.session.SessionStorage'`` 
     371    * ``'django.contrib.messages.storage.cookie.CookieStorage'`` 
     372    * ``'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'`` 
     373 
     374See `Storage backends`_ for more details. 
     375 
     376MESSAGE_TAGS 
     377------------ 
     378 
     379Default:: 
     380 
     381        {messages.DEBUG: 'debug', 
     382        messages.INFO: 'info', 
     383        messages.SUCCESS: 'success', 
     384        messages.WARNING: 'warning', 
     385        messages.ERROR: 'error',} 
     386 
     387This sets the mapping of message level to message tag, which is typically 
     388rendered as a CSS class in HTML. If you specify a value, it will extend 
     389the default. This means you only have to specify those values which you need 
     390to override. See `Displaying messages`_ above for more details. 
     391 
     392.. admonition:: Important 
     393 
     394   If you override ``MESSAGE_TAGS`` in your settings file and rely on any of 
     395   the built-in constants, you must import the ``constants`` module directly to 
     396   avoid the potential for circular imports, e.g.:: 
     397 
     398       from django.contrib.messages import constants as message_constants 
     399       MESSAGE_TAGS = {message_constants.INFO: ''} 
     400 
     401   If desired, you may specify the numeric values for the constants directly 
     402   according to the values in the above :ref:`constants table 
     403   <message-level-constants>`. 
     404 
     405.. _Django settings: ../settings/ 
  • a/docs/ref/middleware.txt

    old new  
    139139content for each user. See the :ref:`internationalization documentation 
    140140<topics-i18n>`. 
    141141 
     142Message middleware 
     143------------------ 
     144 
     145.. module:: django.contrib.messages.middleware 
     146   :synopsis: Message middleware. 
     147 
     148.. class:: django.contrib.messages.middleware.MessageMiddleware 
     149 
     150.. versionadded:: 1.2 
     151   ``MessageMiddleware`` was added. 
     152    
     153Enables cookie- and session-based message support. See the  
     154:ref:`messages documentation <ref-contrib-messages>`. 
     155 
    142156Session middleware 
    143157------------------ 
    144158 
  • a/docs/ref/settings.txt

    old new  
    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 
     865.. versionchanged:: 1.2 
     866   ``'django.contrib.messages.middleware.MessageMiddleware'`` was added to the 
     867   default.  For more information, see the :ref:`messages documentation 
     868   <ref-contrib-messages>`. 
     869 
    827870.. setting:: MONTH_DAY_FORMAT 
    828871 
    829872MONTH_DAY_FORMAT 
     
    10591102    ("django.core.context_processors.auth", 
    10601103    "django.core.context_processors.debug", 
    10611104    "django.core.context_processors.i18n", 
    1062     "django.core.context_processors.media") 
     1105    "django.core.context_processors.media", 
     1106    "django.contrib.messages.context_processors.messages") 
    10631107 
    10641108A tuple of callables that are used to populate the context in ``RequestContext``. 
    10651109These callables take a request object as their argument and return a dictionary 
    10661110of items to be merged into the context. 
    10671111 
     1112.. versionchanged:: 1.2 
     1113   ``"django.contrib.messages.context_processors.messages"`` was added to the 
     1114   default.  For more information, see the :ref:`messages documentation 
     1115   <ref-contrib-messages>`. 
     1116 
    10681117.. setting:: TEMPLATE_DEBUG 
    10691118 
    10701119TEMPLATE_DEBUG 
  • a/docs/ref/templates/api.txt

    old new  
    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 :ref:`messages framework <ref-contrib-messages>`. 
    374375 
    375376    * ``perms`` -- An instance of 
    376377      ``django.core.context_processors.PermWrapper``, representing the 
    377378      permissions that the currently logged-in user has. 
    378379 
     380.. versionchanged:: 1.2 
     381   Prior to version 1.2, the ``messages`` variable was a lazy accessor for 
     382   ``user.get_and_delete_messages()``. It has been changed to include any  
     383   messages added 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 
  • a/docs/releases/1.2.txt

    old new  
    121121:meth:`~django.core.mail.get_connection()` call:: 
    122122 
    123123    connection = get_connection('django.core.mail.backends.smtp', hostname='localhost', port=1234) 
     124     
     125User Messages API 
     126----------------- 
     127 
     128The API for storing messages in the user ``Message`` model (via  
     129``user.message_set.create``) is now deprecated and will be removed in Django 
     1301.4 according to the standard :ref:`release process <internals-release-process>`. 
     131 
     132To upgrade your code, you need to replace any instances of:: 
     133 
     134    user.message_set.create('a message') 
     135 
     136with the following:: 
     137 
     138    from django.contrib import messages 
     139    messages.add_message(request, messages.INFO, 'a message') 
     140 
     141Additionally, if you make use of the method, you need to replace the  
     142following:: 
     143 
     144    for message in user.get_and_delete_messages(): 
     145        ... 
     146     
     147with:: 
     148 
     149    from django.contrib import messages 
     150    for message in messages.get_messages(request): 
     151        ... 
     152     
     153For more information, see the full  
     154:ref:`messages documentation <ref-contrib-messages>`. You should begin to  
     155update your code to use the new API immediately. 
    124156 
    125157What's new in Django 1.2 
    126158======================== 
     
    155187:ref:`memory<topic-email-memory-backend>` - you can even configure all 
    156188e-mail to be :ref:`thrown away<topic-email-dummy-backend>`. 
    157189 
     190Messages Framework 
     191------------------------- 
     192 
     193Django now includes a robust and configurable  
     194:ref:`messages framework <ref-contrib-messages>` with built-in support for  
     195cookie- and session-based messaging, for both anonymous and authenticated  
     196clients. The messages framework replaces the deprecated user message API and  
     197allows you to temporarily store messages in one request and retrieve them for  
     198display in a subsequent request (usually the next one). 
  • a/docs/topics/auth.txt

    old new  
    2323      user. 
    2424    * Messages: A simple way to queue messages for given users. 
    2525 
     26.. deprecated:: 1.2 
     27   The Messages component of the auth system will be removed in Django 1.4. 
     28  
    2629Installation 
    2730============ 
    2831 
     
    12891292Messages 
    12901293======== 
    12911294 
     1295.. deprecated:: 1.2 
     1296   This functionality will be removed in Django 1.4.  You should use the 
     1297   :ref:`messages framework <ref-contrib-messages>` for all new projects and 
     1298   begin to update your existing code immediately. 
     1299 
    12921300The message system is a lightweight way to queue messages for given users. 
    12931301 
    12941302A message is associated with a :class:`~django.contrib.auth.models.User`. 
     
    13341342    </ul> 
    13351343    {% endif %} 
    13361344 
    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. 
     1345.. versionchanged:: 1.2 
     1346   The ``messages`` template variable uses a backwards compatible method in the 
     1347   :ref:`messages framework <ref-contrib-messages>` to retrieve messages from 
     1348   both the user ``Message`` model and from the new framework.  Unlike in 
     1349   previous revisions, the messages will not be erased unless they are actually 
     1350   displayed. 
    13401351 
    13411352Finally, note that this messages framework only works with users in the user 
    13421353database. To send messages to anonymous users, use the 
    1343 :ref:`session framework <topics-http-sessions>`. 
     1354:ref:`messages framework <ref-contrib-messages>`. 
    13441355 
    13451356.. _authentication-backends: 
    13461357 
  • /dev/null

    old new  
     1# empty models so tests are discovered 
  • /dev/null

    old new  
     1{% if messages %} 
     2<ul class="messages"> 
     3        {% for message in messages %} 
     4        <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li> 
     5        {% endfor %} 
     6</ul> 
     7{% endif %} 
  • /dev/null

    old new  
     1from regressiontests.message_tests.tests.cookie import CookieTest 
     2from regressiontests.message_tests.tests.fallback import FallbackTest 
     3from regressiontests.message_tests.tests.middleware import MiddlewareTest 
     4from regressiontests.message_tests.tests.session import SessionTest 
     5from regressiontests.message_tests.tests.user_messages import \ 
     6                                           UserMessagesTest, LegacyFallbackTest 
  • /dev/null

    old new  
     1from django import http 
     2from django.test import TestCase 
     3from django.conf import settings 
     4from django.utils.translation import ugettext_lazy 
     5from django.contrib.messages import constants, utils 
     6from django.contrib.messages.storage import Storage, base 
     7from django.contrib.messages.storage.base import Message 
     8from django.core.urlresolvers import reverse 
     9from django.contrib.auth.models import User 
     10from django.contrib.messages.api import MessageFailure 
     11 
     12 
     13def add_level_messages(storage): 
     14    """ 
     15    Adds 6 messages from different levels (including a custom one) to a storage 
     16    instance. 
     17    """ 
     18    storage.add(constants.INFO, 'A generic info message') 
     19    storage.add(29, 'Some custom level') 
     20    storage.add(constants.DEBUG, 'A debugging message', extra_tags='extra-tag') 
     21    storage.add(constants.WARNING, 'A warning') 
     22    storage.add(constants.ERROR, 'An error') 
     23    storage.add(constants.SUCCESS, 'This was a triumph.') 
     24 
     25 
     26class BaseTest(TestCase): 
     27    storage_class = Storage 
     28    restore_settings = ['MESSAGE_LEVEL', 'MESSAGE_TAGS'] 
     29    urls = 'regressiontests.message_tests.urls' 
     30    levels = { 
     31        'debug': constants.DEBUG, 
     32        'info': constants.INFO, 
     33        'success': constants.SUCCESS, 
     34        'warning': constants.WARNING, 
     35        'error': constants.ERROR, 
     36    } 
     37 
     38    def setUp(self): 
     39        self._remembered_settings = {} 
     40        for setting in self.restore_settings: 
     41            if hasattr(settings, setting): 
     42                self._remembered_settings[setting] = getattr(settings, setting) 
     43                delattr(settings._wrapped, setting) 
     44        # backup these manually because we do not want them deleted 
     45        self._middleware_classes = settings.MIDDLEWARE_CLASSES 
     46        self._template_context_processors = \ 
     47           settings.TEMPLATE_CONTEXT_PROCESSORS 
     48        self._installed_apps = settings.INSTALLED_APPS 
     49 
     50    def tearDown(self): 
     51        for setting in self.restore_settings: 
     52            self.restore_setting(setting) 
     53        # restore these manually (see above) 
     54        settings.MIDDLEWARE_CLASSES = self._middleware_classes 
     55        settings.TEMPLATE_CONTEXT_PROCESSORS = \ 
     56           self._template_context_processors 
     57        settings.INSTALLED_APPS = self._installed_apps 
     58 
     59    def restore_setting(self, setting): 
     60        if setting in self._remembered_settings: 
     61            value = self._remembered_settings.pop(setting) 
     62            setattr(settings, setting, value) 
     63        elif hasattr(settings, setting): 
     64            delattr(settings._wrapped, setting) 
     65 
     66    def get_request(self): 
     67        return http.HttpRequest() 
     68 
     69    def get_response(self): 
     70        return http.HttpResponse() 
     71 
     72    def get_storage(self, data=None): 
     73        """ 
     74        Returns the storage backend, setting its loaded data to the ``data`` 
     75        argument. 
     76 
     77        This method avoids the storage ``_get`` method from getting called so 
     78        that other parts of the storage backend can be tested independent of 
     79        the message retrieval logic. 
     80        """ 
     81        storage = self.storage_class(self.get_request()) 
     82        storage._loaded_data = data or [] 
     83        return storage 
     84 
     85    def test_add(self): 
     86        storage = self.get_storage() 
     87        self.assertFalse(storage.added_new) 
     88        storage.add(constants.INFO, 'Test message 1') 
     89        self.assert_(storage.added_new) 
     90        storage.add(constants.INFO, 'Test message 2', extra_tags='tag') 
     91        self.assertEqual(len(storage), 2) 
     92 
     93    def test_add_lazy_translation(self): 
     94        storage = self.get_storage() 
     95        response = self.get_response() 
     96 
     97        storage.add(constants.INFO, ugettext_lazy('lazy message')) 
     98        storage.update(response) 
     99 
     100        storing = self.stored_messages_count(storage, response) 
     101        self.assertEqual(storing, 1) 
     102 
     103    def test_no_update(self): 
     104        storage = self.get_storage() 
     105        response = self.get_response() 
     106        storage.update(response) 
     107        storing = self.stored_messages_count(storage, response) 
     108        self.assertEqual(storing, 0) 
     109 
     110    def test_add_update(self): 
     111        storage = self.get_storage() 
     112        response = self.get_response() 
     113 
     114        storage.add(constants.INFO, 'Test message 1') 
     115        storage.add(constants.INFO, 'Test message 1', extra_tags='tag') 
     116        storage.update(response) 
     117 
     118        storing = self.stored_messages_count(storage, response) 
     119        self.assertEqual(storing, 2) 
     120 
     121    def test_existing_add_read_update(self): 
     122        storage = self.get_existing_storage() 
     123        response = self.get_response() 
     124 
     125        storage.add(constants.INFO, 'Test message 3') 
     126        list(storage)   # Simulates a read 
     127        storage.update(response) 
     128 
     129        storing = self.stored_messages_count(storage, response) 
     130        self.assertEqual(storing, 0) 
     131 
     132    def test_existing_read_add_update(self): 
     133        storage = self.get_existing_storage() 
     134        response = self.get_response() 
     135 
     136        list(storage)   # Simulates a read 
     137        storage.add(constants.INFO, 'Test message 3') 
     138        storage.update(response) 
     139 
     140        storing = self.stored_messages_count(storage, response) 
     141        self.assertEqual(storing, 1) 
     142 
     143    def test_full_request_response_cycle(self): 
     144        """ 
     145        With the message middleware enabled, tests that messages are properly 
     146        stored and then retrieved across the full request/redirect/response 
     147        cycle. 
     148        """ 
     149        settings.MESSAGE_LEVEL = constants.DEBUG 
     150        data = { 
     151            'messages': ['Test message %d' % x for x in xrange(10)], 
     152        } 
     153        show_url = reverse('regressiontests.message_tests.views.show') 
     154        for level in ('debug', 'info', 'success', 'warning', 'error'): 
     155            add_url = reverse('regressiontests.message_tests.views.add', 
     156                              args=(level,)) 
     157            response = self.client.post(add_url, data, follow=True) 
     158            self.assertRedirects(response, show_url) 
     159            self.assertTrue('messages' in response.context) 
     160            messages = [Message(self.levels[level], msg) for msg in 
     161                                                         data['messages']] 
     162            self.assertEqual(list(response.context['messages']), messages) 
     163            for msg in data['messages']: 
     164                self.assertContains(response, msg) 
     165 
     166    def test_multiple_posts(self): 
     167        """ 
     168        Tests that messages persist properly when multiple POSTs are made 
     169        before a GET. 
     170        """ 
     171        settings.MESSAGE_LEVEL = constants.DEBUG 
     172        data = { 
     173            'messages': ['Test message %d' % x for x in xrange(10)], 
     174        } 
     175        show_url = reverse('regressiontests.message_tests.views.show') 
     176        messages = [] 
     177        for level in ('debug', 'info', 'success', 'warning', 'error'): 
     178            messages.extend([Message(self.levels[level], msg) for msg in 
     179                                                             data['messages']]) 
     180            add_url = reverse('regressiontests.message_tests.views.add', 
     181                              args=(level,)) 
     182            self.client.post(add_url, data) 
     183        response = self.client.get(show_url) 
     184        self.assertTrue('messages' in response.context) 
     185        self.assertEqual(list(response.context['messages']), messages) 
     186        for msg in data['messages']: 
     187            self.assertContains(response, msg) 
     188 
     189    def test_middleware_disabled_auth_user(self): 
     190        """ 
     191        Tests that the messages API successfully falls back to using 
     192        user.message_set to store messages directly when the middleware is 
     193        disabled. 
     194        """ 
     195        settings.MESSAGE_LEVEL = constants.DEBUG 
     196        user = User.objects.create_user('test', 'test@example.com', 'test') 
     197        self.client.login(username='test', password='test') 
     198        settings.INSTALLED_APPS = list(settings.INSTALLED_APPS) 
     199        settings.INSTALLED_APPS.remove( 
     200            'django.contrib.messages', 
     201        ) 
     202        settings.MIDDLEWARE_CLASSES = list(settings.MIDDLEWARE_CLASSES) 
     203        settings.MIDDLEWARE_CLASSES.remove( 
     204            'django.contrib.messages.middleware.MessageMiddleware', 
     205        ) 
     206        settings.TEMPLATE_CONTEXT_PROCESSORS = \ 
     207          list(settings.TEMPLATE_CONTEXT_PROCESSORS) 
     208        settings.TEMPLATE_CONTEXT_PROCESSORS.remove( 
     209            'django.contrib.messages.context_processors.messages', 
     210        ) 
     211        data = { 
     212            'messages': ['Test message %d' % x for x in xrange(10)], 
     213        } 
     214        show_url = reverse('regressiontests.message_tests.views.show') 
     215        for level in ('debug', 'info', 'success', 'warning', 'error'): 
     216            add_url = reverse('regressiontests.message_tests.views.add', 
     217                              args=(level,)) 
     218            response = self.client.post(add_url, data, follow=True) 
     219            self.assertRedirects(response, show_url) 
     220            self.assertTrue('messages' in response.context) 
     221            self.assertEqual(list(response.context['messages']), 
     222                             data['messages']) 
     223            for msg in data['messages']: 
     224                self.assertContains(response, msg) 
     225 
     226    def test_middleware_disabled_anon_user(self): 
     227        """ 
     228        Tests that, when the middleware is disabled and a user is not logged 
     229        in, an exception is raised when one attempts to store a message. 
     230        """ 
     231        settings.MESSAGE_LEVEL = constants.DEBUG 
     232        settings.INSTALLED_APPS = list(settings.INSTALLED_APPS) 
     233        settings.INSTALLED_APPS.remove( 
     234            'django.contrib.messages', 
     235        ) 
     236        settings.MIDDLEWARE_CLASSES = list(settings.MIDDLEWARE_CLASSES) 
     237        settings.MIDDLEWARE_CLASSES.remove( 
     238            'django.contrib.messages.middleware.MessageMiddleware', 
     239        ) 
     240        settings.TEMPLATE_CONTEXT_PROCESSORS = \ 
     241          list(settings.TEMPLATE_CONTEXT_PROCESSORS) 
     242        settings.TEMPLATE_CONTEXT_PROCESSORS.remove( 
     243            'django.contrib.messages.context_processors.messages', 
     244        ) 
     245        data = { 
     246            'messages': ['Test message %d' % x for x in xrange(10)], 
     247        } 
     248        show_url = reverse('regressiontests.message_tests.views.show') 
     249        for level in ('debug', 'info', 'success', 'warning', 'error'): 
     250            add_url = reverse('regressiontests.message_tests.views.add', 
     251                              args=(level,)) 
     252            self.assertRaises(MessageFailure, self.client.post, add_url, 
     253                              data, follow=True) 
     254 
     255    def test_middleware_disabled_anon_user_fail_silently(self): 
     256        """ 
     257        Tests that, when the middleware is disabled and a user is not logged 
     258        in, an exception is raised when one attempts to store a message. 
     259        """ 
     260        settings.MESSAGE_LEVEL = constants.DEBUG 
     261        settings.INSTALLED_APPS = list(settings.INSTALLED_APPS) 
     262        settings.INSTALLED_APPS.remove( 
     263            'django.contrib.messages', 
     264        ) 
     265        settings.MIDDLEWARE_CLASSES = list(settings.MIDDLEWARE_CLASSES) 
     266        settings.MIDDLEWARE_CLASSES.remove( 
     267            'django.contrib.messages.middleware.MessageMiddleware', 
     268        ) 
     269        settings.TEMPLATE_CONTEXT_PROCESSORS = \ 
     270          list(settings.TEMPLATE_CONTEXT_PROCESSORS) 
     271        settings.TEMPLATE_CONTEXT_PROCESSORS.remove( 
     272            'django.contrib.messages.context_processors.messages', 
     273        ) 
     274        data = { 
     275            'messages': ['Test message %d' % x for x in xrange(10)], 
     276            'fail_silently': True, 
     277        } 
     278        show_url = reverse('regressiontests.message_tests.views.show') 
     279        for level in ('debug', 'info', 'success', 'warning', 'error'): 
     280            add_url = reverse('regressiontests.message_tests.views.add', 
     281                              args=(level,)) 
     282            response = self.client.post(add_url, data, follow=True) 
     283            self.assertRedirects(response, show_url) 
     284            self.assertTrue('messages' in response.context) 
     285            self.assertEqual(list(response.context['messages']), []) 
     286 
     287    def stored_messages_count(self, storage, response): 
     288        """ 
     289        Returns the number of messages being stored after a 
     290        ``storage.update()`` call. 
     291        """ 
     292        raise NotImplementedError('This method must be set by a subclass.') 
     293 
     294    def test_get(self): 
     295        raise NotImplementedError('This method must be set by a subclass.') 
     296 
     297    def get_existing_storage(self): 
     298        return self.get_storage([Message(constants.INFO, 'Test message 1'), 
     299                                 Message(constants.INFO, 'Test message 2', 
     300                                              extra_tags='tag')]) 
     301 
     302    def test_existing_read(self): 
     303        """ 
     304        Tests that reading the existing storage doesn't cause the data to be 
     305        lost. 
     306        """ 
     307        storage = self.get_existing_storage() 
     308        self.assertFalse(storage.used) 
     309        # After iterating the storage engine directly, the used flag is set. 
     310        data = list(storage) 
     311        self.assert_(storage.used) 
     312        # The data does not disappear because it has been iterated. 
     313        self.assertEqual(data, list(storage)) 
     314 
     315    def test_existing_add(self): 
     316        storage = self.get_existing_storage() 
     317        self.assertFalse(storage.added_new) 
     318        storage.add(constants.INFO, 'Test message 3') 
     319        self.assert_(storage.added_new) 
     320 
     321    def test_default_level(self): 
     322        storage = self.get_storage() 
     323        add_level_messages(storage) 
     324        self.assertEqual(len(storage), 5) 
     325 
     326    def test_low_level(self): 
     327        storage = self.get_storage() 
     328        storage.level = 5 
     329        add_level_messages(storage) 
     330        self.assertEqual(len(storage), 6) 
     331 
     332    def test_high_level(self): 
     333        storage = self.get_storage() 
     334        storage.level = 30 
     335        add_level_messages(storage) 
     336        self.assertEqual(len(storage), 2) 
     337 
     338    def test_settings_level(self): 
     339        settings.MESSAGE_LEVEL = 29 
     340        storage = self.get_storage() 
     341        add_level_messages(storage) 
     342        self.assertEqual(len(storage), 3) 
     343 
     344    def test_tags(self): 
     345        storage = self.get_storage() 
     346        storage.level = 0 
     347        add_level_messages(storage) 
     348        tags = [msg.tags for msg in storage] 
     349        self.assertEqual(tags, 
     350                         ['info', '', 'extra-tag debug', 'warning', 'error', 
     351                          'success']) 
     352 
     353    def test_custom_tags(self): 
     354        settings.MESSAGE_TAGS = { 
     355            constants.INFO: 'info', 
     356            constants.DEBUG: '', 
     357            constants.WARNING: '', 
     358            constants.ERROR: 'bad', 
     359            29: 'custom', 
     360        } 
     361        # LEVEL_TAGS is a constant defined in the 
     362        # django.contrib.messages.storage.base module, so after changing 
     363        # settings.MESSAGE_TAGS, we need to update that constant too. 
     364        base.LEVEL_TAGS = utils.get_level_tags() 
     365        try: 
     366            storage = self.get_storage() 
     367            storage.level = 0 
     368            add_level_messages(storage) 
     369            tags = [msg.tags for msg in storage] 
     370            self.assertEqual(tags, 
     371                         ['info', 'custom', 'extra-tag', '', 'bad', 'success']) 
     372        finally: 
     373            # Ensure the level tags constant is put back like we found it. 
     374            self.restore_setting('MESSAGE_TAGS') 
     375            base.LEVEL_TAGS = utils.get_level_tags() 
  • /dev/null

    old new  
     1from django.contrib.messages import constants 
     2from regressiontests.message_tests.tests.base import BaseTest 
     3from django.contrib.messages.storage.cookie import CookieStorage, \ 
     4                                            MessageEncoder, MessageDecoder 
     5from django.contrib.messages.storage.base import Message 
     6from django.utils import simplejson as json 
     7 
     8 
     9def set_cookie_data(storage, messages, invalid=False, encode_empty=False): 
     10    """ 
     11    Sets ``request.COOKIES`` with the encoded data and removes the storage 
     12    backend's loaded data cache. 
     13    """ 
     14    encoded_data = storage._encode(messages, encode_empty=encode_empty) 
     15    if invalid: 
     16        # Truncate the first character so that the hash is invalid. 
     17        encoded_data = encoded_data[1:] 
     18    storage.request.COOKIES = {CookieStorage.cookie_name: encoded_data} 
     19    if hasattr(storage, '_loaded_data'): 
     20        del storage._loaded_data 
     21 
     22 
     23def stored_cookie_messages_count(storage, response): 
     24    """ 
     25    Returns an integer containing the number of messages stored. 
     26    """ 
     27    # Get a list of cookies, excluding ones with a max-age of 0 (because 
     28    # they have been marked for deletion). 
     29    cookie = response.cookies.get(storage.cookie_name) 
     30    if not cookie or cookie['max-age'] == 0: 
     31        return 0 
     32    data = storage._decode(cookie.value) 
     33    if not data: 
     34        return 0 
     35    if data[-1] == CookieStorage.not_finished: 
     36        data.pop() 
     37    return len(data) 
     38 
     39 
     40class CookieTest(BaseTest): 
     41    storage_class = CookieStorage 
     42 
     43    def stored_messages_count(self, storage, response): 
     44        return stored_cookie_messages_count(storage, response) 
     45 
     46    def test_get(self): 
     47        storage = self.storage_class(self.get_request()) 
     48        # Set initial data. 
     49        example_messages = ['test', 'me'] 
     50        set_cookie_data(storage, example_messages) 
     51        # Test that the message actually contains what we expect. 
     52        self.assertEqual(list(storage), example_messages) 
     53 
     54    def test_get_bad_cookie(self): 
     55        request = self.get_request() 
     56        storage = self.storage_class(request) 
     57        # Set initial (invalid) data. 
     58        example_messages = ['test', 'me'] 
     59        set_cookie_data(storage, example_messages, invalid=True) 
     60        # Test that the message actually contains what we expect. 
     61        self.assertEqual(list(storage), []) 
     62 
     63    def test_max_cookie_length(self): 
     64        """ 
     65        Tests that, if the data exceeds what is allowed in a cookie, older 
     66        messages are removed before saving (and returned by the ``update`` 
     67        method). 
     68        """ 
     69        storage = self.get_storage() 
     70        response = self.get_response() 
     71 
     72        for i in range(5): 
     73            storage.add(constants.INFO, str(i) * 900) 
     74        unstored_messages = storage.update(response) 
     75 
     76        cookie_storing = self.stored_messages_count(storage, response) 
     77        self.assertEqual(cookie_storing, 4) 
     78 
     79        self.assertEqual(len(unstored_messages), 1) 
     80        self.assert_(unstored_messages[0].message == '0' * 900) 
     81 
     82    def test_json_encoder_decoder(self): 
     83        """ 
     84        Tests that an complex nested data structure containing Message 
     85        instances is properly encoded/decoded by the custom JSON 
     86        encoder/decoder classes. 
     87        """ 
     88        messages = [ 
     89            { 
     90                'message': Message(constants.INFO, 'Test message'), 
     91                'message_list': [Message(constants.INFO, 'message %s') \ 
     92                                 for x in xrange(5)] + [{'another-message': \ 
     93                                 Message(constants.ERROR, 'error')}], 
     94            }, 
     95            Message(constants.INFO, 'message %s'), 
     96        ] 
     97        encoder = MessageEncoder(separators=(',', ':')) 
     98        value = encoder.encode(messages) 
     99        decoded_messages = json.loads(value, cls=MessageDecoder) 
     100        self.assertEqual(messages, decoded_messages) 
  • /dev/null

    old new  
     1from django.contrib.messages import constants 
     2from django.contrib.messages.storage.fallback import FallbackStorage, \ 
     3    CookieStorage 
     4from regressiontests.message_tests.tests.base import BaseTest 
     5from regressiontests.message_tests.tests.cookie import set_cookie_data, \ 
     6    stored_cookie_messages_count 
     7from regressiontests.message_tests.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) 
  • /dev/null

    old new  
     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) 
  • /dev/null

    old new  
     1from regressiontests.message_tests.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) 
  • /dev/null

    old new  
     1from django import http 
     2from django.contrib.auth.models import User 
     3from django.contrib.messages.storage.user_messages import UserMessagesStorage,\ 
     4    LegacyFallbackStorage 
     5from regressiontests.message_tests.tests.cookie import set_cookie_data 
     6from regressiontests.message_tests.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') 
  • /dev/null

    old new  
     1from django.conf.urls.defaults import * 
     2 
     3urlpatterns = patterns('regressiontests.message_tests.views', 
     4    ('^add/(debug|info|success|warning|error)/$', 'add'), 
     5    ('^show/$', 'show'), 
     6) 
  • /dev/null

    old new  
     1from django.contrib import messages 
     2from django.core.urlresolvers import reverse 
     3from django.http import HttpResponseRedirect 
     4from django.shortcuts import render_to_response 
     5from django.template import RequestContext 
     6 
     7 
     8def add(request, message_type): 
     9    # don't default to False here, because we want to test that it defaults 
     10    # to False if unspecified 
     11    fail_silently = request.POST.get('fail_silently', None) 
     12    for msg in request.POST.getlist('messages'): 
     13        if fail_silently is not None: 
     14            getattr(messages, message_type)(request, msg, 
     15                                            fail_silently=fail_silently) 
     16        else: 
     17            getattr(messages, message_type)(request, msg) 
     18    show_url = reverse('regressiontests.message_tests.views.show') 
     19    return HttpResponseRedirect(show_url) 
     20 
     21 
     22def show(request): 
     23    return render_to_response('message_tests/show.html', {}, 
     24                              context_instance=RequestContext(request)) 
  • a/tests/runtests.py

    old new  
    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