Code

Ticket #4604: django-contrib-messages.2.diff

File django-contrib-messages.2.diff, 98.5 KB (added by tobias, 4 years ago)
Line 
1diff -r 70e75e8cd224 AUTHORS
2--- a/AUTHORS   Thu Dec 03 15:11:14 2009 +0000
3+++ b/AUTHORS   Wed Dec 09 08:05:02 2009 -0500
4@@ -60,6 +60,7 @@
5     Ned Batchelder <http://www.nedbatchelder.com/>
6     batiste@dosimple.ch
7     Batman
8+    Chris Beaven <http://smileychris.tactful.co.nz/>
9     Brian Beck <http://blog.brianbeck.com/>
10     Shannon -jj Behrens <http://jjinux.blogspot.com/>
11     Esdras Beleza <linux@esdrasbeleza.com>
12@@ -299,6 +300,7 @@
13     Jason McBrayer <http://www.carcosa.net/jason/>
14     Kevin McConnell <kevin.mcconnell@gmail.com>
15     mccutchen@gmail.com
16+    Tobias McNulty <http://www.caktusgroup.com/blog>
17     Christian Metts
18     michael.mcewan@gmail.com
19     michal@plovarna.cz
20@@ -391,7 +393,6 @@
21     Jozko Skrablin <jozko.skrablin@gmail.com>
22     Ben Slavin <benjamin.slavin@gmail.com>
23     sloonz <simon.lipp@insa-lyon.fr>
24-    SmileyChris <smileychris@gmail.com>
25     Warren Smith <warren@wandrsmith.net>
26     smurf@smurf.noris.de
27     Vsevolod Solovyov
28diff -r 70e75e8cd224 django/conf/global_settings.py
29--- a/django/conf/global_settings.py    Thu Dec 03 15:11:14 2009 +0000
30+++ b/django/conf/global_settings.py    Wed Dec 09 08:05:02 2009 -0500
31@@ -172,6 +172,7 @@
32     'django.core.context_processors.i18n',
33     'django.core.context_processors.media',
34 #    'django.core.context_processors.request',
35+    'django.contrib.messages.context_processors.messages',
36 )
37 
38 # Output to use in template system for invalid (e.g. misspelled) variables.
39@@ -308,6 +309,7 @@
40     'django.contrib.sessions.middleware.SessionMiddleware',
41     'django.middleware.csrf.CsrfViewMiddleware',
42     'django.contrib.auth.middleware.AuthenticationMiddleware',
43+    'django.contrib.messages.middleware.MessageMiddleware',
44 #     'django.middleware.http.ConditionalGetMiddleware',
45 #     'django.middleware.gzip.GZipMiddleware',
46 )
47@@ -393,6 +395,16 @@
48 CSRF_COOKIE_NAME = 'csrftoken'
49 CSRF_COOKIE_DOMAIN = None
50 
51+############
52+# MESSAGES #
53+############
54+
55+# Class to use as messges backend
56+MESSAGE_STORAGE = 'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'
57+
58+# Default values of MESSAGE_LEVEL and MESSAGE_TAGS are defined within
59+# django.contrib.messages to avoid imports in this settings file.
60+
61 ###########
62 # TESTING #
63 ###########
64diff -r 70e75e8cd224 django/conf/project_template/settings.py
65--- a/django/conf/project_template/settings.py  Thu Dec 03 15:11:14 2009 +0000
66+++ b/django/conf/project_template/settings.py  Wed Dec 09 08:05:02 2009 -0500
67@@ -62,6 +62,7 @@
68     'django.contrib.sessions.middleware.SessionMiddleware',
69     'django.middleware.csrf.CsrfViewMiddleware',
70     'django.contrib.auth.middleware.AuthenticationMiddleware',
71+    'django.contrib.messages.middleware.MessageMiddleware',
72 )
73 
74 ROOT_URLCONF = '{{ project_name }}.urls'
75@@ -77,4 +78,5 @@
76     'django.contrib.contenttypes',
77     'django.contrib.sessions',
78     'django.contrib.sites',
79+    'django.contrib.messages',
80 )
81diff -r 70e75e8cd224 django/contrib/admin/options.py
82--- a/django/contrib/admin/options.py   Thu Dec 03 15:11:14 2009 +0000
83+++ b/django/contrib/admin/options.py   Wed Dec 09 08:05:02 2009 -0500
84@@ -6,6 +6,7 @@
85 from django.contrib.admin import widgets
86 from django.contrib.admin import helpers
87 from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects, model_ngettext, model_format_dict
88+from django.contrib import messages
89 from django.views.decorators.csrf import csrf_protect
90 from django.core.exceptions import PermissionDenied
91 from django.db import models, transaction
92@@ -541,9 +542,9 @@
93     def message_user(self, request, message):
94         """
95         Send a message to the user. The default implementation
96-        posts a message using the auth Message object.
97+        posts a message using the django.contrib.messages backend.
98         """
99-        request.user.message_set.create(message=message)
100+        messages.info(request, message)
101 
102     def save_form(self, request, form, change):
103         """
104diff -r 70e75e8cd224 django/contrib/admin/views/template.py
105--- a/django/contrib/admin/views/template.py    Thu Dec 03 15:11:14 2009 +0000
106+++ b/django/contrib/admin/views/template.py    Wed Dec 09 08:05:02 2009 -0500
107@@ -6,6 +6,7 @@
108 from django.conf import settings
109 from django.utils.importlib import import_module
110 from django.utils.translation import ugettext_lazy as _
111+from django.contrib import messages
112 
113 
114 def template_validator(request):
115@@ -23,7 +24,7 @@
116         form = TemplateValidatorForm(settings_modules, site_list,
117                                      data=request.POST)
118         if form.is_valid():
119-            request.user.message_set.create(message='The template is valid.')
120+            messages.info(request, 'The template is valid.')
121     else:
122         form = TemplateValidatorForm(settings_modules, site_list)
123     return render_to_response('admin/template_validator.html', {
124diff -r 70e75e8cd224 django/contrib/auth/admin.py
125--- a/django/contrib/auth/admin.py      Thu Dec 03 15:11:14 2009 +0000
126+++ b/django/contrib/auth/admin.py      Wed Dec 09 08:05:02 2009 -0500
127@@ -3,6 +3,7 @@
128 from django.contrib import admin
129 from django.contrib.auth.forms import UserCreationForm, UserChangeForm, AdminPasswordChangeForm
130 from django.contrib.auth.models import User, Group
131+from django.contrib import messages
132 from django.core.exceptions import PermissionDenied
133 from django.http import HttpResponseRedirect, Http404
134 from django.shortcuts import render_to_response, get_object_or_404
135@@ -67,12 +68,13 @@
136                 msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': 'user', 'obj': new_user}
137                 self.log_addition(request, new_user)
138                 if "_addanother" in request.POST:
139-                    request.user.message_set.create(message=msg)
140+                    messages.success(request, msg)
141                     return HttpResponseRedirect(request.path)
142                 elif '_popup' in request.REQUEST:
143                     return self.response_add(request, new_user)
144                 else:
145-                    request.user.message_set.create(message=msg + ' ' + ugettext("You may edit it again below."))
146+                    messages.success(request, msg + ' ' +
147+                                     ugettext("You may edit it again below."))
148                     return HttpResponseRedirect('../%s/' % new_user.id)
149         else:
150             form = self.add_form()
151@@ -104,7 +106,7 @@
152             if form.is_valid():
153                 new_user = form.save()
154                 msg = ugettext('Password changed successfully.')
155-                request.user.message_set.create(message=msg)
156+                messages.success(request, msg)
157                 return HttpResponseRedirect('..')
158         else:
159             form = self.change_password_form(user)
160diff -r 70e75e8cd224 django/contrib/auth/models.py
161--- a/django/contrib/auth/models.py     Thu Dec 03 15:11:14 2009 +0000
162+++ b/django/contrib/auth/models.py     Wed Dec 09 08:05:02 2009 -0500
163@@ -288,6 +288,14 @@
164                 raise SiteProfileNotAvailable
165         return self._profile_cache
166 
167+    def _get_message_set(self):
168+        import warnings
169+        warnings.warn('The user messaging API is deprecated. Please update'
170+                      ' your code to use the new messages framework.',
171+                      category=PendingDeprecationWarning)
172+        return self._message_set
173+    message_set = property(_get_message_set)
174+
175 class Message(models.Model):
176     """
177     The message system is a lightweight way to queue messages for given
178@@ -297,7 +305,7 @@
179     actions. For example, "The poll Foo was created successfully." is a
180     message.
181     """
182-    user = models.ForeignKey(User)
183+    user = models.ForeignKey(User, related_name='_message_set')
184     message = models.TextField(_('message'))
185 
186     def __unicode__(self):
187diff -r 70e75e8cd224 django/contrib/messages/__init__.py
188--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
189+++ b/django/contrib/messages/__init__.py       Wed Dec 09 08:05:02 2009 -0500
190@@ -0,0 +1,2 @@
191+from api import *
192+from constants import *
193diff -r 70e75e8cd224 django/contrib/messages/api.py
194--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
195+++ b/django/contrib/messages/api.py    Wed Dec 09 08:05:02 2009 -0500
196@@ -0,0 +1,84 @@
197+from django.contrib.messages import constants
198+from django.utils.functional import lazy, memoize
199+
200+__all__ = (
201+    'add_message', 'get_messages',
202+    'debug', 'info', 'success', 'warning', 'error',
203+)
204+
205+
206+class MessageFailure(Exception):
207+    pass
208+
209+
210+def add_message(request, level, message, extra_tags='', fail_silently=False):
211+    """
212+    Attempts to add a message to the request using the 'messages' app, falling
213+    back to the user's message_set if MessageMiddleware hasn't been enabled.
214+    """
215+    if hasattr(request, '_messages'):
216+        return request._messages.add(level, message, extra_tags)
217+    if hasattr(request, 'user') and request.user.is_authenticated():
218+        return request.user.message_set.create(message=message)
219+    if not fail_silently:
220+        raise MessageFailure('Without the django.contrib.messages '
221+                                'middleware, messages can only be added to '
222+                                'authenticated users.')
223+
224+
225+def get_messages(request):
226+    """
227+    Returns the message storage on the request if it exists, otherwise returns
228+    user.message_set.all() as the old auth context processor did.
229+    """
230+    if hasattr(request, '_messages'):
231+        return request._messages
232+
233+    def get_user():
234+        if hasattr(request, 'user'):
235+            return request.user
236+        else:
237+            from django.contrib.auth.models import AnonymousUser
238+            return AnonymousUser()
239+
240+    return lazy(memoize(get_user().get_and_delete_messages, {}, 0), list)()
241+
242+
243+def debug(request, message, extra_tags='', fail_silently=False):
244+    """
245+    Adds a message with the ``DEBUG`` level.
246+    """
247+    add_message(request, constants.DEBUG, message, extra_tags=extra_tags,
248+                fail_silently=fail_silently)
249+
250+
251+def info(request, message, extra_tags='', fail_silently=False):
252+    """
253+    Adds a message with the ``INFO`` level.
254+    """
255+    add_message(request, constants.INFO, message, extra_tags=extra_tags,
256+                fail_silently=fail_silently)
257+
258+
259+def success(request, message, extra_tags='', fail_silently=False):
260+    """
261+    Adds a message with the ``SUCCESS`` level.
262+    """
263+    add_message(request, constants.SUCCESS, message, extra_tags=extra_tags,
264+                fail_silently=fail_silently)
265+
266+
267+def warning(request, message, extra_tags='', fail_silently=False):
268+    """
269+    Adds a message with the ``WARNING`` level.
270+    """
271+    add_message(request, constants.WARNING, message, extra_tags=extra_tags,
272+                fail_silently=fail_silently)
273+
274+
275+def error(request, message, extra_tags='', fail_silently=False):
276+    """
277+    Adds a message with the ``ERROR`` level.
278+    """
279+    add_message(request, constants.ERROR, message, extra_tags=extra_tags,
280+                fail_silently=fail_silently)
281diff -r 70e75e8cd224 django/contrib/messages/constants.py
282--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
283+++ b/django/contrib/messages/constants.py      Wed Dec 09 08:05:02 2009 -0500
284@@ -0,0 +1,13 @@
285+DEBUG = 10
286+INFO = 20
287+SUCCESS = 25
288+WARNING = 30
289+ERROR = 40
290+
291+DEFAULT_TAGS = {
292+    DEBUG: 'debug',
293+    INFO: 'info',
294+    SUCCESS: 'success',
295+    WARNING: 'warning',
296+    ERROR: 'error',
297+}
298diff -r 70e75e8cd224 django/contrib/messages/context_processors.py
299--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
300+++ b/django/contrib/messages/context_processors.py     Wed Dec 09 08:05:02 2009 -0500
301@@ -0,0 +1,8 @@
302+from django.contrib.messages.api import get_messages
303+
304+
305+def messages(request):
306+    """
307+    Returns a lazy 'messages' context variable.
308+    """
309+    return {'messages': get_messages(request)}
310diff -r 70e75e8cd224 django/contrib/messages/middleware.py
311--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
312+++ b/django/contrib/messages/middleware.py     Wed Dec 09 08:05:02 2009 -0500
313@@ -0,0 +1,26 @@
314+from django.conf import settings
315+from django.contrib.messages.storage import default_storage
316+
317+
318+class MessageMiddleware(object):
319+    """
320+    Middleware that handles temporary messages.
321+    """
322+
323+    def process_request(self, request):
324+        request._messages = default_storage(request)
325+
326+    def process_response(self, request, response):
327+        """
328+        Updates the storage backend (i.e., saves the messages).
329+
330+        If not all messages could not be stored and ``DEBUG`` is ``True``, a
331+        ``ValueError`` is raised.
332+        """
333+        # A higher middleware layer may return a request which does not contain
334+        # messages storage, so make no assumption that it will be there.
335+        if hasattr(request, '_messages'):
336+            unstored_messages = request._messages.update(response)
337+            if unstored_messages and settings.DEBUG:
338+                raise ValueError('Not all temporary messages could be stored.')
339+        return response
340diff -r 70e75e8cd224 django/contrib/messages/models.py
341--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
342+++ b/django/contrib/messages/models.py Wed Dec 09 08:05:02 2009 -0500
343@@ -0,0 +1,1 @@
344+# Models module required so tests are discovered.
345diff -r 70e75e8cd224 django/contrib/messages/storage/__init__.py
346--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
347+++ b/django/contrib/messages/storage/__init__.py       Wed Dec 09 08:05:02 2009 -0500
348@@ -0,0 +1,31 @@
349+from django.conf import settings
350+from django.core.exceptions import ImproperlyConfigured
351+from django.utils.importlib import import_module
352+
353+
354+def get_storage(import_path):
355+    """
356+    Imports the message storage class described by import_path, where
357+    import_path is the full Python path to the class.
358+    """
359+    try:
360+        dot = import_path.rindex('.')
361+    except ValueError:
362+        raise ImproperlyConfigured("%s isn't a Python path." % import_path)
363+    module, classname = import_path[:dot], import_path[dot + 1:]
364+    try:
365+        mod = import_module(module)
366+    except ImportError, e:
367+        raise ImproperlyConfigured('Error importing module %s: "%s"' %
368+                                   (module, e))
369+    try:
370+        return getattr(mod, classname)
371+    except AttributeError:
372+        raise ImproperlyConfigured('Module "%s" does not define a "%s" '
373+                                   'class.' % (module, classname))
374+
375+
376+# Callable with the same interface as the storage classes i.e.  accepts a
377+# 'request' object.  It is wrapped in a lambda to stop 'settings' being used at
378+# the module level
379+default_storage = lambda request: get_storage(settings.MESSAGE_STORAGE)(request)
380diff -r 70e75e8cd224 django/contrib/messages/storage/base.py
381--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
382+++ b/django/contrib/messages/storage/base.py   Wed Dec 09 08:05:02 2009 -0500
383@@ -0,0 +1,181 @@
384+from django.conf import settings
385+from django.utils.encoding import force_unicode, StrAndUnicode
386+from django.contrib.messages import constants, utils
387+
388+
389+LEVEL_TAGS = utils.get_level_tags()
390+
391+
392+class Message(StrAndUnicode):
393+    """
394+    Represents an actual message that can be stored in any of the supported
395+    storage classes (typically session- or cookie-based) and rendered in a view
396+    or template.
397+    """
398+
399+    def __init__(self, level, message, extra_tags=None):
400+        self.level = int(level)
401+        self.message = message
402+        self.extra_tags = extra_tags
403+
404+    def _prepare(self):
405+        """
406+        Prepares the message for serialization by forcing the ``message``
407+        and ``extra_tags`` to unicode in case they are lazy translations.
408+
409+        Known "safe" types (None, int, etc.) are not converted (see Django's
410+        ``force_unicode`` implementation for details).
411+        """
412+        self.message = force_unicode(self.message, strings_only=True)
413+        self.extra_tags = force_unicode(self.extra_tags, strings_only=True)
414+
415+    def __eq__(self, other):
416+        return isinstance(other, Message) and self.level == other.level and \
417+                                              self.message == other.message
418+
419+    def __unicode__(self):
420+        return force_unicode(self.message)
421+
422+    def _get_tags(self):
423+        label_tag = force_unicode(LEVEL_TAGS.get(self.level, ''),
424+                                  strings_only=True)
425+        extra_tags = force_unicode(self.extra_tags, strings_only=True)
426+        if extra_tags and label_tag:
427+            return u' '.join([extra_tags, label_tag])
428+        elif extra_tags:
429+            return extra_tags
430+        elif label_tag:
431+            return label_tag
432+        return ''
433+    tags = property(_get_tags)
434+
435+
436+class BaseStorage(object):
437+    """
438+    This is the base backend for temporary message storage.
439+
440+    This is not a complete class; to be a usable storage backend, it must be
441+    subclassed and the two methods ``_get`` and ``_store`` overridden.
442+    """
443+
444+    def __init__(self, request, *args, **kwargs):
445+        self.request = request
446+        self._queued_messages = []
447+        self.used = False
448+        self.added_new = False
449+        super(BaseStorage, self).__init__(*args, **kwargs)
450+
451+    def __len__(self):
452+        return len(self._loaded_messages) + len(self._queued_messages)
453+
454+    def __iter__(self):
455+        self.used = True
456+        if self._queued_messages:
457+            self._loaded_messages.extend(self._queued_messages)
458+            self._queued_messages = []
459+        return iter(self._loaded_messages)
460+
461+    def __contains__(self, item):
462+        return item in self._loaded_messages or item in self._queued_messages
463+
464+    @property
465+    def _loaded_messages(self):
466+        """
467+        Returns a list of loaded messages, retrieving them first if they have
468+        not been loaded yet.
469+        """
470+        if not hasattr(self, '_loaded_data'):
471+            messages, all_retrieved = self._get()
472+            self._loaded_data = messages or []
473+        return self._loaded_data
474+
475+    def _get(self, *args, **kwargs):
476+        """
477+        Retrieves a list of stored messages. Returns a tuple of the messages
478+        and a flag indicating whether or not all the messages originally
479+        intended to be stored in this storage were, in fact, stored and
480+        retrieved; e.g., ``(messages, all_retrieved)``.
481+
482+        **This method must be implemented by a subclass.**
483+
484+        If it is possible to tell if the backend was not used (as opposed to
485+        just containing no messages) then ``None`` should be returned in
486+        place of ``messages``.
487+        """
488+        raise NotImplementedError()
489+
490+    def _store(self, messages, response, *args, **kwargs):
491+        """
492+        Stores a list of messages, returning a list of any messages which could
493+        not be stored.
494+
495+        One type of object must be able to be stored, ``Message``.
496+
497+        **This method must be implemented by a subclass.**
498+        """
499+        raise NotImplementedError()
500+
501+    def _prepare_messages(self, messages):
502+        """
503+        Prepares a list of messages for storage.
504+        """
505+        for message in messages:
506+            message._prepare()
507+
508+    def update(self, response):
509+        """
510+        Stores all unread messages.
511+
512+        If the backend has yet to be iterated, previously stored messages will
513+        be stored again. Otherwise, only messages added after the last
514+        iteration will be stored.
515+        """
516+        self._prepare_messages(self._queued_messages)
517+        if self.used:
518+            return self._store(self._queued_messages, response)
519+        elif self.added_new:
520+            messages = self._loaded_messages + self._queued_messages
521+            return self._store(messages, response)
522+
523+    def add(self, level, message, extra_tags=''):
524+        """
525+        Queues a message to be stored.
526+
527+        The message is only queued if it contained something and its level is
528+        not less than the recording level (``self.level``).
529+        """
530+        if not message:
531+            return
532+        # Check that the message level is not less than the recording level.
533+        level = int(level)
534+        if level < self.level:
535+            return
536+        # Add the message.
537+        self.added_new = True
538+        message = Message(level, message, extra_tags=extra_tags)
539+        self._queued_messages.append(message)
540+
541+    def _get_level(self):
542+        """
543+        Returns the minimum recorded level.
544+
545+        The default level is the ``MESSAGE_LEVEL`` setting. If this is
546+        not found, the ``INFO`` level is used.
547+        """
548+        if not hasattr(self, '_level'):
549+            self._level = getattr(settings, 'MESSAGE_LEVEL', constants.INFO)
550+        return self._level
551+
552+    def _set_level(self, value=None):
553+        """
554+        Sets a custom minimum recorded level.
555+
556+        If set to ``None``, the default level will be used (see the
557+        ``_get_level`` method).
558+        """
559+        if value is None and hasattr(self, '_level'):
560+            del self._level
561+        else:
562+            self._level = int(value)
563+
564+    level = property(_get_level, _set_level, _set_level)
565diff -r 70e75e8cd224 django/contrib/messages/storage/cookie.py
566--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
567+++ b/django/contrib/messages/storage/cookie.py Wed Dec 09 08:05:02 2009 -0500
568@@ -0,0 +1,143 @@
569+import hmac
570+
571+from django.conf import settings
572+from django.utils.hashcompat import sha_constructor
573+from django.contrib.messages import constants
574+from django.contrib.messages.storage.base import BaseStorage, Message
575+from django.utils import simplejson as json
576+
577+
578+class MessageEncoder(json.JSONEncoder):
579+    """
580+    Compactly serializes instances of the ``Message`` class as JSON.
581+    """
582+    message_key = '__json_message'
583+
584+    def default(self, obj):
585+        if isinstance(obj, Message):
586+            message = [self.message_key, obj.level, obj.message]
587+            if obj.extra_tags:
588+                message.append(obj.extra_tags)
589+            return message
590+        return super(MessageEncoder, self).default(obj)
591+
592+
593+class MessageDecoder(json.JSONDecoder):
594+    """
595+    Decodes JSON that includes serialized ``Message`` instances.
596+    """
597+
598+    def process_messages(self, obj):
599+        if isinstance(obj, list) and obj:
600+            if obj[0] == MessageEncoder.message_key:
601+                return Message(*obj[1:])
602+            return [self.process_messages(item) for item in obj]
603+        if isinstance(obj, dict):
604+            return dict([(key, self.process_messages(value))
605+                         for key, value in obj.iteritems()])
606+        return obj
607+
608+    def decode(self, s, **kwargs):
609+        decoded = super(MessageDecoder, self).decode(s, **kwargs)
610+        return self.process_messages(decoded)
611+
612+
613+class CookieStorage(BaseStorage):
614+    """
615+    Stores messages in a cookie.
616+    """
617+    cookie_name = 'messages'
618+    max_cookie_size = 4096
619+    not_finished = '__messagesnotfinished__'
620+
621+    def _get(self, *args, **kwargs):
622+        """
623+        Retrieves a list of messages from the messages cookie.  If the
624+        not_finished sentinel value is found at the end of the message list,
625+        remove it and return a result indicating that not all messages were
626+        retrieved by this storage.
627+        """
628+        data = self.request.COOKIES.get(self.cookie_name)
629+        messages = self._decode(data)
630+        all_retrieved = not (messages and messages[-1] == self.not_finished)
631+        if messages and not all_retrieved:
632+            # remove the sentinel value
633+            messages.pop()
634+        return messages, all_retrieved
635+
636+    def _update_cookie(self, encoded_data, response):
637+        """
638+        Either sets the cookie with the encoded data if there is any data to
639+        store, or deletes the cookie.
640+        """
641+        if encoded_data:
642+            response.set_cookie(self.cookie_name, encoded_data)
643+        else:
644+            response.delete_cookie(self.cookie_name)
645+
646+    def _store(self, messages, response, remove_oldest=True, *args, **kwargs):
647+        """
648+        Stores the messages to a cookie, returning a list of any messages which
649+        could not be stored.
650+
651+        If the encoded data is larger than ``max_cookie_size``, removes
652+        messages until the data fits (these are the messages which are
653+        returned), and add the not_finished sentinel value to indicate as much.
654+        """
655+        unstored_messages = []
656+        encoded_data = self._encode(messages)
657+        if self.max_cookie_size:
658+            while encoded_data and len(encoded_data) > self.max_cookie_size:
659+                if remove_oldest:
660+                    unstored_messages.append(messages.pop(0))
661+                else:
662+                    unstored_messages.insert(0, messages.pop())
663+                encoded_data = self._encode(messages + [self.not_finished],
664+                                            encode_empty=unstored_messages)
665+        self._update_cookie(encoded_data, response)
666+        return unstored_messages
667+
668+    def _hash(self, value):
669+        """
670+        Creates an HMAC/SHA1 hash based on the value and the project setting's
671+        SECRET_KEY, modified to make it unique for the present purpose.
672+        """
673+        key = 'django.contrib.messages' + settings.SECRET_KEY
674+        return hmac.new(key, value, sha_constructor).hexdigest()
675+
676+    def _encode(self, messages, encode_empty=False):
677+        """
678+        Returns an encoded version of the messages list which can be stored as
679+        plain text.
680+
681+        Since the data will be retrieved from the client-side, the encoded data
682+        also contains a hash to ensure that the data was not tampered with.
683+        """
684+        if messages or encode_empty:
685+            encoder = MessageEncoder(separators=(',', ':'))
686+            value = encoder.encode(messages)
687+            return '%s$%s' % (self._hash(value), value)
688+
689+    def _decode(self, data):
690+        """
691+        Safely decodes a encoded text stream back into a list of messages.
692+
693+        If the encoded text stream contained an invalid hash or was in an
694+        invalid format, ``None`` is returned.
695+        """
696+        if not data:
697+            return None
698+        bits = data.split('$', 1)
699+        if len(bits) == 2:
700+            hash, value = bits
701+            if hash == self._hash(value):
702+                try:
703+                    # If we get here (and the JSON decode works), everything is
704+                    # good. In any other case, drop back and return None.
705+                    return json.loads(value, cls=MessageDecoder)
706+                except ValueError:
707+                    pass
708+        # Mark the data as used (so it gets removed) since something was wrong
709+        # with the data.
710+        self.used = True
711+        return None
712diff -r 70e75e8cd224 django/contrib/messages/storage/fallback.py
713--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
714+++ b/django/contrib/messages/storage/fallback.py       Wed Dec 09 08:05:02 2009 -0500
715@@ -0,0 +1,59 @@
716+from django.contrib.messages.storage.base import BaseStorage
717+from django.contrib.messages.storage.cookie import CookieStorage
718+from django.contrib.messages.storage.session import SessionStorage
719+try:
720+    set
721+except NameError:
722+    from sets import Set as set   # Python 2.3
723+
724+
725+class FallbackStorage(BaseStorage):
726+    """
727+    Tries to store all messages in the first backend, storing any unstored
728+    messages in each subsequent backend backend.
729+    """
730+    storage_classes = (CookieStorage, SessionStorage)
731+
732+    def __init__(self, *args, **kwargs):
733+        super(FallbackStorage, self).__init__(*args, **kwargs)
734+        self.storages = [storage_class(*args, **kwargs)
735+                         for storage_class in self.storage_classes]
736+        self._used_storages = set()
737+
738+    def _get(self, *args, **kwargs):
739+        """
740+        Gets a single list of messages from all storage backends.
741+        """
742+        all_messages = []
743+        for storage in self.storages:
744+            messages, all_retrieved = storage._get()
745+            # If the backend hasn't been used, no more retrieval is necessary.
746+            if messages is None:
747+                break
748+            if messages:
749+                self._used_storages.add(storage)
750+            all_messages.extend(messages)
751+            # If this storage class contained all the messages, no further
752+            # retrieval is necessary
753+            if all_retrieved:
754+                break
755+        return all_messages, all_retrieved
756+
757+    def _store(self, messages, response, *args, **kwargs):
758+        """
759+        Stores the messages, returning any unstored messages after trying all
760+        backends.
761+
762+        For each storage backend, any messages not stored are passed on to the
763+        next backend.
764+        """
765+        for storage in self.storages:
766+            if messages:
767+                messages = storage._store(messages, response,
768+                                          remove_oldest=False)
769+            # Even if there are no more messages, continue iterating to ensure
770+            # storages which contained messages are flushed.
771+            elif storage in self._used_storages:
772+                storage._store([], response)
773+                self._used_storages.remove(storage)
774+        return messages
775diff -r 70e75e8cd224 django/contrib/messages/storage/session.py
776--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
777+++ b/django/contrib/messages/storage/session.py        Wed Dec 09 08:05:02 2009 -0500
778@@ -0,0 +1,33 @@
779+from django.contrib.messages.storage.base import BaseStorage
780+
781+
782+class SessionStorage(BaseStorage):
783+    """
784+    Stores messages in the session (that is, django.contrib.sessions).
785+    """
786+    session_key = '_messages'
787+
788+    def __init__(self, request, *args, **kwargs):
789+        assert hasattr(request, 'session'), "The session-based temporary "\
790+            "message storage requires session middleware to be installed, "\
791+            "and come before the message middleware in the "\
792+            "MIDDLEWARE_CLASSES list."
793+        super(SessionStorage, self).__init__(request, *args, **kwargs)
794+
795+    def _get(self, *args, **kwargs):
796+        """
797+        Retrieves a list of messages from the request's session.  This storage
798+        always stores everything it is given, so return True for the
799+        all_retrieved flag.
800+        """
801+        return self.request.session.get(self.session_key), True
802+
803+    def _store(self, messages, response, *args, **kwargs):
804+        """
805+        Stores a list of messages to the request's session.
806+        """
807+        if messages:
808+            self.request.session[self.session_key] = messages
809+        else:
810+            self.request.session.pop(self.session_key, None)
811+        return []
812diff -r 70e75e8cd224 django/contrib/messages/storage/user_messages.py
813--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
814+++ b/django/contrib/messages/storage/user_messages.py  Wed Dec 09 08:05:02 2009 -0500
815@@ -0,0 +1,64 @@
816+"""
817+Storages used to assist in the deprecation of contrib.auth User messages.
818+
819+"""
820+from django.contrib.messages import constants
821+from django.contrib.messages.storage.base import BaseStorage, Message
822+from django.contrib.auth.models import User
823+from django.contrib.messages.storage.fallback import FallbackStorage
824+
825+
826+class UserMessagesStorage(BaseStorage):
827+    """
828+    Retrieves messages from the User, using the legacy user.message_set API.
829+
830+    This storage is "read-only" insofar as it can only retrieve and delete
831+    messages, not store them.
832+    """
833+    session_key = '_messages'
834+
835+    def _get_messages_queryset(self):
836+        """
837+        Returns the QuerySet containing all user messages (or ``None`` if
838+        request.user is not a contrib.auth User).
839+        """
840+        user = getattr(self.request, 'user', None)
841+        if isinstance(user, User):
842+            return user._message_set.all()
843+
844+    def add(self, *args, **kwargs):
845+        raise NotImplementedError('This message storage is read-only.')
846+
847+    def _get(self, *args, **kwargs):
848+        """
849+        Retrieves a list of messages assigned to the User.  This backend never
850+        stores anything, so all_retrieved is assumed to be False.
851+        """
852+        queryset = self._get_messages_queryset()
853+        if queryset is None:
854+            # This is a read-only and optional storage, so to ensure other
855+            # storages will also be read if used with FallbackStorage an empty
856+            # list is returned rather than None.
857+            return [], False
858+        messages = []
859+        for user_message in queryset:
860+            messages.append(Message(constants.INFO, user_message.message))
861+        return messages, False
862+
863+    def _store(self, messages, *args, **kwargs):
864+        """
865+        Removes any messages assigned to the User and returns the list of
866+        messages (since no messages are stored in this read-only storage).
867+        """
868+        queryset = self._get_messages_queryset()
869+        if queryset is not None:
870+            queryset.delete()
871+        return messages
872+
873+
874+class LegacyFallbackStorage(FallbackStorage):
875+    """
876+    Works like ``FallbackStorage`` but also handles retrieving (and clearing)
877+    contrib.auth User messages.
878+    """
879+    storage_classes = (UserMessagesStorage,) + FallbackStorage.storage_classes
880diff -r 70e75e8cd224 django/contrib/messages/tests/__init__.py
881--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
882+++ b/django/contrib/messages/tests/__init__.py Wed Dec 09 08:05:02 2009 -0500
883@@ -0,0 +1,6 @@
884+from django.contrib.messages.tests.cookie import CookieTest
885+from django.contrib.messages.tests.fallback import FallbackTest
886+from django.contrib.messages.tests.middleware import MiddlewareTest
887+from django.contrib.messages.tests.session import SessionTest
888+from django.contrib.messages.tests.user_messages import \
889+                                           UserMessagesTest, LegacyFallbackTest
890diff -r 70e75e8cd224 django/contrib/messages/tests/base.py
891--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
892+++ b/django/contrib/messages/tests/base.py     Wed Dec 09 08:05:02 2009 -0500
893@@ -0,0 +1,375 @@
894+from django import http
895+from django.test import TestCase
896+from django.conf import settings
897+from django.utils.translation import ugettext_lazy
898+from django.contrib.messages import constants, utils
899+from django.contrib.messages.storage import default_storage, base
900+from django.contrib.messages.storage.base import Message
901+from django.core.urlresolvers import reverse
902+from django.contrib.auth.models import User
903+from django.contrib.messages.api import MessageFailure
904+
905+
906+def add_level_messages(storage):
907+    """
908+    Adds 6 messages from different levels (including a custom one) to a storage
909+    instance.
910+    """
911+    storage.add(constants.INFO, 'A generic info message')
912+    storage.add(29, 'Some custom level')
913+    storage.add(constants.DEBUG, 'A debugging message', extra_tags='extra-tag')
914+    storage.add(constants.WARNING, 'A warning')
915+    storage.add(constants.ERROR, 'An error')
916+    storage.add(constants.SUCCESS, 'This was a triumph.')
917+
918+
919+class BaseTest(TestCase):
920+    storage_class = default_storage
921+    restore_settings = ['MESSAGE_LEVEL', 'MESSAGE_TAGS']
922+    urls = 'django.contrib.messages.tests.urls'
923+    levels = {
924+        'debug': constants.DEBUG,
925+        'info': constants.INFO,
926+        'success': constants.SUCCESS,
927+        'warning': constants.WARNING,
928+        'error': constants.ERROR,
929+    }
930+
931+    def setUp(self):
932+        self._remembered_settings = {}
933+        for setting in self.restore_settings:
934+            if hasattr(settings, setting):
935+                self._remembered_settings[setting] = getattr(settings, setting)
936+                delattr(settings._wrapped, setting)
937+        # backup these manually because we do not want them deleted
938+        self._middleware_classes = settings.MIDDLEWARE_CLASSES
939+        self._template_context_processors = \
940+           settings.TEMPLATE_CONTEXT_PROCESSORS
941+        self._installed_apps = settings.INSTALLED_APPS
942+
943+    def tearDown(self):
944+        for setting in self.restore_settings:
945+            self.restore_setting(setting)
946+        # restore these manually (see above)
947+        settings.MIDDLEWARE_CLASSES = self._middleware_classes
948+        settings.TEMPLATE_CONTEXT_PROCESSORS = \
949+           self._template_context_processors
950+        settings.INSTALLED_APPS = self._installed_apps
951+
952+    def restore_setting(self, setting):
953+        if setting in self._remembered_settings:
954+            value = self._remembered_settings.pop(setting)
955+            setattr(settings, setting, value)
956+        elif hasattr(settings, setting):
957+            delattr(settings._wrapped, setting)
958+
959+    def get_request(self):
960+        return http.HttpRequest()
961+
962+    def get_response(self):
963+        return http.HttpResponse()
964+
965+    def get_storage(self, data=None):
966+        """
967+        Returns the storage backend, setting its loaded data to the ``data``
968+        argument.
969+
970+        This method avoids the storage ``_get`` method from getting called so
971+        that other parts of the storage backend can be tested independent of
972+        the message retrieval logic.
973+        """
974+        storage = self.storage_class(self.get_request())
975+        storage._loaded_data = data or []
976+        return storage
977+
978+    def test_add(self):
979+        storage = self.get_storage()
980+        self.assertFalse(storage.added_new)
981+        storage.add(constants.INFO, 'Test message 1')
982+        self.assert_(storage.added_new)
983+        storage.add(constants.INFO, 'Test message 2', extra_tags='tag')
984+        self.assertEqual(len(storage), 2)
985+
986+    def test_add_lazy_translation(self):
987+        storage = self.get_storage()
988+        response = self.get_response()
989+
990+        storage.add(constants.INFO, ugettext_lazy('lazy message'))
991+        storage.update(response)
992+
993+        storing = self.stored_messages_count(storage, response)
994+        self.assertEqual(storing, 1)
995+
996+    def test_no_update(self):
997+        storage = self.get_storage()
998+        response = self.get_response()
999+        storage.update(response)
1000+        storing = self.stored_messages_count(storage, response)
1001+        self.assertEqual(storing, 0)
1002+
1003+    def test_add_update(self):
1004+        storage = self.get_storage()
1005+        response = self.get_response()
1006+
1007+        storage.add(constants.INFO, 'Test message 1')
1008+        storage.add(constants.INFO, 'Test message 1', extra_tags='tag')
1009+        storage.update(response)
1010+
1011+        storing = self.stored_messages_count(storage, response)
1012+        self.assertEqual(storing, 2)
1013+
1014+    def test_existing_add_read_update(self):
1015+        storage = self.get_existing_storage()
1016+        response = self.get_response()
1017+
1018+        storage.add(constants.INFO, 'Test message 3')
1019+        list(storage)   # Simulates a read
1020+        storage.update(response)
1021+
1022+        storing = self.stored_messages_count(storage, response)
1023+        self.assertEqual(storing, 0)
1024+
1025+    def test_existing_read_add_update(self):
1026+        storage = self.get_existing_storage()
1027+        response = self.get_response()
1028+
1029+        list(storage)   # Simulates a read
1030+        storage.add(constants.INFO, 'Test message 3')
1031+        storage.update(response)
1032+
1033+        storing = self.stored_messages_count(storage, response)
1034+        self.assertEqual(storing, 1)
1035+
1036+    def test_full_request_response_cycle(self):
1037+        """
1038+        With the message middleware enabled, tests that messages are properly
1039+        stored and then retrieved across the full request/redirect/response
1040+        cycle.
1041+        """
1042+        settings.MESSAGE_LEVEL = constants.DEBUG
1043+        data = {
1044+            'messages': ['Test message %d' % x for x in xrange(10)],
1045+        }
1046+        show_url = reverse('django.contrib.messages.tests.urls.show')
1047+        for level in ('debug', 'info', 'success', 'warning', 'error'):
1048+            add_url = reverse('django.contrib.messages.tests.urls.add',
1049+                              args=(level,))
1050+            response = self.client.post(add_url, data, follow=True)
1051+            self.assertRedirects(response, show_url)
1052+            self.assertTrue('messages' in response.context)
1053+            messages = [Message(self.levels[level], msg) for msg in
1054+                                                         data['messages']]
1055+            self.assertEqual(list(response.context['messages']), messages)
1056+            for msg in data['messages']:
1057+                self.assertContains(response, msg)
1058+
1059+    def test_multiple_posts(self):
1060+        """
1061+        Tests that messages persist properly when multiple POSTs are made
1062+        before a GET.
1063+        """
1064+        settings.MESSAGE_LEVEL = constants.DEBUG
1065+        data = {
1066+            'messages': ['Test message %d' % x for x in xrange(10)],
1067+        }
1068+        show_url = reverse('django.contrib.messages.tests.urls.show')
1069+        messages = []
1070+        for level in ('debug', 'info', 'success', 'warning', 'error'):
1071+            messages.extend([Message(self.levels[level], msg) for msg in
1072+                                                             data['messages']])
1073+            add_url = reverse('django.contrib.messages.tests.urls.add',
1074+                              args=(level,))
1075+            self.client.post(add_url, data)
1076+        response = self.client.get(show_url)
1077+        self.assertTrue('messages' in response.context)
1078+        self.assertEqual(list(response.context['messages']), messages)
1079+        for msg in data['messages']:
1080+            self.assertContains(response, msg)
1081+
1082+    def test_middleware_disabled_auth_user(self):
1083+        """
1084+        Tests that the messages API successfully falls back to using
1085+        user.message_set to store messages directly when the middleware is
1086+        disabled.
1087+        """
1088+        settings.MESSAGE_LEVEL = constants.DEBUG
1089+        user = User.objects.create_user('test', 'test@example.com', 'test')
1090+        self.client.login(username='test', password='test')
1091+        settings.INSTALLED_APPS = list(settings.INSTALLED_APPS)
1092+        settings.INSTALLED_APPS.remove(
1093+            'django.contrib.messages',
1094+        )
1095+        settings.MIDDLEWARE_CLASSES = list(settings.MIDDLEWARE_CLASSES)
1096+        settings.MIDDLEWARE_CLASSES.remove(
1097+            'django.contrib.messages.middleware.MessageMiddleware',
1098+        )
1099+        settings.TEMPLATE_CONTEXT_PROCESSORS = \
1100+          list(settings.TEMPLATE_CONTEXT_PROCESSORS)
1101+        settings.TEMPLATE_CONTEXT_PROCESSORS.remove(
1102+            'django.contrib.messages.context_processors.messages',
1103+        )
1104+        data = {
1105+            'messages': ['Test message %d' % x for x in xrange(10)],
1106+        }
1107+        show_url = reverse('django.contrib.messages.tests.urls.show')
1108+        for level in ('debug', 'info', 'success', 'warning', 'error'):
1109+            add_url = reverse('django.contrib.messages.tests.urls.add',
1110+                              args=(level,))
1111+            response = self.client.post(add_url, data, follow=True)
1112+            self.assertRedirects(response, show_url)
1113+            self.assertTrue('messages' in response.context)
1114+            self.assertEqual(list(response.context['messages']),
1115+                             data['messages'])
1116+            for msg in data['messages']:
1117+                self.assertContains(response, msg)
1118+
1119+    def test_middleware_disabled_anon_user(self):
1120+        """
1121+        Tests that, when the middleware is disabled and a user is not logged
1122+        in, an exception is raised when one attempts to store a message.
1123+        """
1124+        settings.MESSAGE_LEVEL = constants.DEBUG
1125+        settings.INSTALLED_APPS = list(settings.INSTALLED_APPS)
1126+        settings.INSTALLED_APPS.remove(
1127+            'django.contrib.messages',
1128+        )
1129+        settings.MIDDLEWARE_CLASSES = list(settings.MIDDLEWARE_CLASSES)
1130+        settings.MIDDLEWARE_CLASSES.remove(
1131+            'django.contrib.messages.middleware.MessageMiddleware',
1132+        )
1133+        settings.TEMPLATE_CONTEXT_PROCESSORS = \
1134+          list(settings.TEMPLATE_CONTEXT_PROCESSORS)
1135+        settings.TEMPLATE_CONTEXT_PROCESSORS.remove(
1136+            'django.contrib.messages.context_processors.messages',
1137+        )
1138+        data = {
1139+            'messages': ['Test message %d' % x for x in xrange(10)],
1140+        }
1141+        show_url = reverse('django.contrib.messages.tests.urls.show')
1142+        for level in ('debug', 'info', 'success', 'warning', 'error'):
1143+            add_url = reverse('django.contrib.messages.tests.urls.add',
1144+                              args=(level,))
1145+            self.assertRaises(MessageFailure, self.client.post, add_url,
1146+                              data, follow=True)
1147+
1148+    def test_middleware_disabled_anon_user_fail_silently(self):
1149+        """
1150+        Tests that, when the middleware is disabled and a user is not logged
1151+        in, an exception is raised when one attempts to store a message.
1152+        """
1153+        settings.MESSAGE_LEVEL = constants.DEBUG
1154+        settings.INSTALLED_APPS = list(settings.INSTALLED_APPS)
1155+        settings.INSTALLED_APPS.remove(
1156+            'django.contrib.messages',
1157+        )
1158+        settings.MIDDLEWARE_CLASSES = list(settings.MIDDLEWARE_CLASSES)
1159+        settings.MIDDLEWARE_CLASSES.remove(
1160+            'django.contrib.messages.middleware.MessageMiddleware',
1161+        )
1162+        settings.TEMPLATE_CONTEXT_PROCESSORS = \
1163+          list(settings.TEMPLATE_CONTEXT_PROCESSORS)
1164+        settings.TEMPLATE_CONTEXT_PROCESSORS.remove(
1165+            'django.contrib.messages.context_processors.messages',
1166+        )
1167+        data = {
1168+            'messages': ['Test message %d' % x for x in xrange(10)],
1169+            'fail_silently': True,
1170+        }
1171+        show_url = reverse('django.contrib.messages.tests.urls.show')
1172+        for level in ('debug', 'info', 'success', 'warning', 'error'):
1173+            add_url = reverse('django.contrib.messages.tests.urls.add',
1174+                              args=(level,))
1175+            response = self.client.post(add_url, data, follow=True)
1176+            self.assertRedirects(response, show_url)
1177+            self.assertTrue('messages' in response.context)
1178+            self.assertEqual(list(response.context['messages']), [])
1179+
1180+    def stored_messages_count(self, storage, response):
1181+        """
1182+        Returns the number of messages being stored after a
1183+        ``storage.update()`` call.
1184+        """
1185+        raise NotImplementedError('This method must be set by a subclass.')
1186+
1187+    def test_get(self):
1188+        raise NotImplementedError('This method must be set by a subclass.')
1189+
1190+    def get_existing_storage(self):
1191+        return self.get_storage([Message(constants.INFO, 'Test message 1'),
1192+                                 Message(constants.INFO, 'Test message 2',
1193+                                              extra_tags='tag')])
1194+
1195+    def test_existing_read(self):
1196+        """
1197+        Tests that reading the existing storage doesn't cause the data to be
1198+        lost.
1199+        """
1200+        storage = self.get_existing_storage()
1201+        self.assertFalse(storage.used)
1202+        # After iterating the storage engine directly, the used flag is set.
1203+        data = list(storage)
1204+        self.assert_(storage.used)
1205+        # The data does not disappear because it has been iterated.
1206+        self.assertEqual(data, list(storage))
1207+
1208+    def test_existing_add(self):
1209+        storage = self.get_existing_storage()
1210+        self.assertFalse(storage.added_new)
1211+        storage.add(constants.INFO, 'Test message 3')
1212+        self.assert_(storage.added_new)
1213+
1214+    def test_default_level(self):
1215+        storage = self.get_storage()
1216+        add_level_messages(storage)
1217+        self.assertEqual(len(storage), 5)
1218+
1219+    def test_low_level(self):
1220+        storage = self.get_storage()
1221+        storage.level = 5
1222+        add_level_messages(storage)
1223+        self.assertEqual(len(storage), 6)
1224+
1225+    def test_high_level(self):
1226+        storage = self.get_storage()
1227+        storage.level = 30
1228+        add_level_messages(storage)
1229+        self.assertEqual(len(storage), 2)
1230+
1231+    def test_settings_level(self):
1232+        settings.MESSAGE_LEVEL = 29
1233+        storage = self.get_storage()
1234+        add_level_messages(storage)
1235+        self.assertEqual(len(storage), 3)
1236+
1237+    def test_tags(self):
1238+        storage = self.get_storage()
1239+        storage.level = 0
1240+        add_level_messages(storage)
1241+        tags = [msg.tags for msg in storage]
1242+        self.assertEqual(tags,
1243+                         ['info', '', 'extra-tag debug', 'warning', 'error',
1244+                          'success'])
1245+
1246+    def test_custom_tags(self):
1247+        settings.MESSAGE_TAGS = {
1248+            constants.INFO: 'info',
1249+            constants.DEBUG: '',
1250+            constants.WARNING: '',
1251+            constants.ERROR: 'bad',
1252+            29: 'custom',
1253+        }
1254+        # LEVEL_TAGS is a constant defined in the
1255+        # django.contrib.messages.storage.base module, so after changing
1256+        # settings.MESSAGE_TAGS, we need to update that constant too.
1257+        base.LEVEL_TAGS = utils.get_level_tags()
1258+        try:
1259+            storage = self.get_storage()
1260+            storage.level = 0
1261+            add_level_messages(storage)
1262+            tags = [msg.tags for msg in storage]
1263+            self.assertEqual(tags,
1264+                         ['info', 'custom', 'extra-tag', '', 'bad', 'success'])
1265+        finally:
1266+            # Ensure the level tags constant is put back like we found it.
1267+            self.restore_setting('MESSAGE_TAGS')
1268+            base.LEVEL_TAGS = utils.get_level_tags()
1269diff -r 70e75e8cd224 django/contrib/messages/tests/cookie.py
1270--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
1271+++ b/django/contrib/messages/tests/cookie.py   Wed Dec 09 08:05:02 2009 -0500
1272@@ -0,0 +1,100 @@
1273+from django.contrib.messages import constants
1274+from django.contrib.messages.tests.base import BaseTest
1275+from django.contrib.messages.storage.cookie import CookieStorage, \
1276+                                            MessageEncoder, MessageDecoder
1277+from django.contrib.messages.storage.base import Message
1278+from django.utils import simplejson as json
1279+
1280+
1281+def set_cookie_data(storage, messages, invalid=False, encode_empty=False):
1282+    """
1283+    Sets ``request.COOKIES`` with the encoded data and removes the storage
1284+    backend's loaded data cache.
1285+    """
1286+    encoded_data = storage._encode(messages, encode_empty=encode_empty)
1287+    if invalid:
1288+        # Truncate the first character so that the hash is invalid.
1289+        encoded_data = encoded_data[1:]
1290+    storage.request.COOKIES = {CookieStorage.cookie_name: encoded_data}
1291+    if hasattr(storage, '_loaded_data'):
1292+        del storage._loaded_data
1293+
1294+
1295+def stored_cookie_messages_count(storage, response):
1296+    """
1297+    Returns an integer containing the number of messages stored.
1298+    """
1299+    # Get a list of cookies, excluding ones with a max-age of 0 (because
1300+    # they have been marked for deletion).
1301+    cookie = response.cookies.get(storage.cookie_name)
1302+    if not cookie or cookie['max-age'] == 0:
1303+        return 0
1304+    data = storage._decode(cookie.value)
1305+    if not data:
1306+        return 0
1307+    if data[-1] == CookieStorage.not_finished:
1308+        data.pop()
1309+    return len(data)
1310+
1311+
1312+class CookieTest(BaseTest):
1313+    storage_class = CookieStorage
1314+
1315+    def stored_messages_count(self, storage, response):
1316+        return stored_cookie_messages_count(storage, response)
1317+
1318+    def test_get(self):
1319+        storage = self.storage_class(self.get_request())
1320+        # Set initial data.
1321+        example_messages = ['test', 'me']
1322+        set_cookie_data(storage, example_messages)
1323+        # Test that the message actually contains what we expect.
1324+        self.assertEqual(list(storage), example_messages)
1325+
1326+    def test_get_bad_cookie(self):
1327+        request = self.get_request()
1328+        storage = self.storage_class(request)
1329+        # Set initial (invalid) data.
1330+        example_messages = ['test', 'me']
1331+        set_cookie_data(storage, example_messages, invalid=True)
1332+        # Test that the message actually contains what we expect.
1333+        self.assertEqual(list(storage), [])
1334+
1335+    def test_max_cookie_length(self):
1336+        """
1337+        Tests that, if the data exceeds what is allowed in a cookie, older
1338+        messages are removed before saving (and returned by the ``update``
1339+        method).
1340+        """
1341+        storage = self.get_storage()
1342+        response = self.get_response()
1343+
1344+        for i in range(5):
1345+            storage.add(constants.INFO, str(i) * 900)
1346+        unstored_messages = storage.update(response)
1347+
1348+        cookie_storing = self.stored_messages_count(storage, response)
1349+        self.assertEqual(cookie_storing, 4)
1350+
1351+        self.assertEqual(len(unstored_messages), 1)
1352+        self.assert_(unstored_messages[0].message == '0' * 900)
1353+
1354+    def test_json_encoder_decoder(self):
1355+        """
1356+        Tests that an complex nested data structure containing Message
1357+        instances is properly encoded/decoded by the custom JSON
1358+        encoder/decoder classes.
1359+        """
1360+        messages = [
1361+            {
1362+                'message': Message(constants.INFO, 'Test message'),
1363+                'message_list': [Message(constants.INFO, 'message %s') \
1364+                                 for x in xrange(5)] + [{'another-message': \
1365+                                 Message(constants.ERROR, 'error')}],
1366+            },
1367+            Message(constants.INFO, 'message %s'),
1368+        ]
1369+        encoder = MessageEncoder(separators=(',', ':'))
1370+        value = encoder.encode(messages)
1371+        decoded_messages = json.loads(value, cls=MessageDecoder)
1372+        self.assertEqual(messages, decoded_messages)
1373diff -r 70e75e8cd224 django/contrib/messages/tests/fallback.py
1374--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
1375+++ b/django/contrib/messages/tests/fallback.py Wed Dec 09 08:05:02 2009 -0500
1376@@ -0,0 +1,173 @@
1377+from django.contrib.messages import constants
1378+from django.contrib.messages.storage.fallback import FallbackStorage, \
1379+    CookieStorage
1380+from django.contrib.messages.tests.base import BaseTest
1381+from django.contrib.messages.tests.cookie import set_cookie_data, \
1382+    stored_cookie_messages_count
1383+from django.contrib.messages.tests.session import set_session_data, \
1384+    stored_session_messages_count
1385+
1386+
1387+class FallbackTest(BaseTest):
1388+    storage_class = FallbackStorage
1389+
1390+    def get_request(self):
1391+        self.session = {}
1392+        request = super(FallbackTest, self).get_request()
1393+        request.session = self.session
1394+        return request
1395+
1396+    def get_cookie_storage(self, storage):
1397+        return storage.storages[-2]
1398+
1399+    def get_session_storage(self, storage):
1400+        return storage.storages[-1]
1401+
1402+    def stored_cookie_messages_count(self, storage, response):
1403+        return stored_cookie_messages_count(self.get_cookie_storage(storage),
1404+                                            response)
1405+
1406+    def stored_session_messages_count(self, storage, response):
1407+        return stored_session_messages_count(self.get_session_storage(storage))
1408+
1409+    def stored_messages_count(self, storage, response):
1410+        """
1411+        Return the storage totals from both cookie and session backends.
1412+        """
1413+        total = (self.stored_cookie_messages_count(storage, response) +
1414+                 self.stored_session_messages_count(storage, response))
1415+        return total
1416+
1417+    def test_get(self):
1418+        request = self.get_request()
1419+        storage = self.storage_class(request)
1420+        cookie_storage = self.get_cookie_storage(storage)
1421+
1422+        # Set initial cookie data.
1423+        example_messages = [str(i) for i in range(5)]
1424+        set_cookie_data(cookie_storage, example_messages)
1425+
1426+        # Overwrite the _get method of the fallback storage to prove it is not
1427+        # used (it would cause a TypeError: 'NoneType' object is not callable).
1428+        self.get_session_storage(storage)._get = None
1429+
1430+        # Test that the message actually contains what we expect.
1431+        self.assertEqual(list(storage), example_messages)
1432+
1433+    def test_get_empty(self):
1434+        request = self.get_request()
1435+        storage = self.storage_class(request)
1436+
1437+        # Overwrite the _get method of the fallback storage to prove it is not
1438+        # used (it would cause a TypeError: 'NoneType' object is not callable).
1439+        self.get_session_storage(storage)._get = None
1440+
1441+        # Test that the message actually contains what we expect.
1442+        self.assertEqual(list(storage), [])
1443+
1444+    def test_get_fallback(self):
1445+        request = self.get_request()
1446+        storage = self.storage_class(request)
1447+        cookie_storage = self.get_cookie_storage(storage)
1448+        session_storage = self.get_session_storage(storage)
1449+
1450+        # Set initial cookie and session data.
1451+        example_messages = [str(i) for i in range(5)]
1452+        set_cookie_data(cookie_storage, example_messages[:4] +
1453+                        [CookieStorage.not_finished])
1454+        set_session_data(session_storage, example_messages[4:])
1455+
1456+        # Test that the message actually contains what we expect.
1457+        self.assertEqual(list(storage), example_messages)
1458+
1459+    def test_get_fallback_only(self):
1460+        request = self.get_request()
1461+        storage = self.storage_class(request)
1462+        cookie_storage = self.get_cookie_storage(storage)
1463+        session_storage = self.get_session_storage(storage)
1464+
1465+        # Set initial cookie and session data.
1466+        example_messages = [str(i) for i in range(5)]
1467+        set_cookie_data(cookie_storage, [CookieStorage.not_finished],
1468+                        encode_empty=True)
1469+        set_session_data(session_storage, example_messages)
1470+
1471+        # Test that the message actually contains what we expect.
1472+        self.assertEqual(list(storage), example_messages)
1473+
1474+    def test_flush_used_backends(self):
1475+        request = self.get_request()
1476+        storage = self.storage_class(request)
1477+        cookie_storage = self.get_cookie_storage(storage)
1478+        session_storage = self.get_session_storage(storage)
1479+
1480+        # Set initial cookie and session data.
1481+        set_cookie_data(cookie_storage, ['cookie', CookieStorage.not_finished])
1482+        set_session_data(session_storage, ['session'])
1483+
1484+        # When updating, previously used but no longer needed backends are
1485+        # flushed.
1486+        response = self.get_response()
1487+        list(storage)
1488+        storage.update(response)
1489+        session_storing = self.stored_session_messages_count(storage, response)
1490+        self.assertEqual(session_storing, 0)
1491+
1492+    def test_no_fallback(self):
1493+        """
1494+        Confirms that:
1495+
1496+        (1) A short number of messages whose data size doesn't exceed what is
1497+        allowed in a cookie will all be stored in the CookieBackend.
1498+
1499+        (2) If the CookieBackend can store all messages, the SessionBackend
1500+        won't be written to at all.
1501+        """
1502+        storage = self.get_storage()
1503+        response = self.get_response()
1504+
1505+        # Overwrite the _store method of the fallback storage to prove it isn't
1506+        # used (it would cause a TypeError: 'NoneType' object is not callable).
1507+        self.get_session_storage(storage)._store = None
1508+
1509+        for i in range(5):
1510+            storage.add(constants.INFO, str(i) * 100)
1511+        storage.update(response)
1512+
1513+        cookie_storing = self.stored_cookie_messages_count(storage, response)
1514+        self.assertEqual(cookie_storing, 5)
1515+        session_storing = self.stored_session_messages_count(storage, response)
1516+        self.assertEqual(session_storing, 0)
1517+
1518+    def test_session_fallback(self):
1519+        """
1520+        Confirms that, if the data exceeds what is allowed in a cookie, older
1521+        messages which did not "fit" are stored in the SessionBackend.
1522+        """
1523+        storage = self.get_storage()
1524+        response = self.get_response()
1525+
1526+        for i in range(5):
1527+            storage.add(constants.INFO, str(i) * 900)
1528+        storage.update(response)
1529+
1530+        cookie_storing = self.stored_cookie_messages_count(storage, response)
1531+        self.assertEqual(cookie_storing, 4)
1532+        session_storing = self.stored_session_messages_count(storage, response)
1533+        self.assertEqual(session_storing, 1)
1534+
1535+    def test_session_fallback_only(self):
1536+        """
1537+        Confirms that large messages, none of which fit in a cookie, are stored
1538+        in the SessionBackend (and nothing is stored in the CookieBackend).
1539+        """
1540+        storage = self.get_storage()
1541+        response = self.get_response()
1542+
1543+        storage.add(constants.INFO, 'x' * 5000)
1544+        storage.update(response)
1545+
1546+        cookie_storing = self.stored_cookie_messages_count(storage, response)
1547+        self.assertEqual(cookie_storing, 0)
1548+        session_storing = self.stored_session_messages_count(storage, response)
1549+        self.assertEqual(session_storing, 1)
1550diff -r 70e75e8cd224 django/contrib/messages/tests/middleware.py
1551--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
1552+++ b/django/contrib/messages/tests/middleware.py       Wed Dec 09 08:05:02 2009 -0500
1553@@ -0,0 +1,18 @@
1554+import unittest
1555+from django import http
1556+from django.contrib.messages.middleware import MessageMiddleware
1557+
1558+
1559+class MiddlewareTest(unittest.TestCase):
1560+
1561+    def setUp(self):
1562+        self.middleware = MessageMiddleware()
1563+
1564+    def test_response_without_messages(self):
1565+        """
1566+        Makes sure that the response middleware is tolerant of messages not
1567+        existing on request.
1568+        """
1569+        request = http.HttpRequest()
1570+        response = http.HttpResponse()
1571+        self.middleware.process_response(request, response)
1572diff -r 70e75e8cd224 django/contrib/messages/tests/session.py
1573--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
1574+++ b/django/contrib/messages/tests/session.py  Wed Dec 09 08:05:02 2009 -0500
1575@@ -0,0 +1,38 @@
1576+from django.contrib.messages.tests.base import BaseTest
1577+from django.contrib.messages.storage.session import SessionStorage
1578+
1579+
1580+def set_session_data(storage, messages):
1581+    """
1582+    Sets the messages into the backend request's session and remove the
1583+    backend's loaded data cache.
1584+    """
1585+    storage.request.session[storage.session_key] = messages
1586+    if hasattr(storage, '_loaded_data'):
1587+        del storage._loaded_data
1588+
1589+
1590+def stored_session_messages_count(storage):
1591+    data = storage.request.session.get(storage.session_key, [])
1592+    return len(data)
1593+
1594+
1595+class SessionTest(BaseTest):
1596+    storage_class = SessionStorage
1597+
1598+    def get_request(self):
1599+        self.session = {}
1600+        request = super(SessionTest, self).get_request()
1601+        request.session = self.session
1602+        return request
1603+
1604+    def stored_messages_count(self, storage, response):
1605+        return stored_session_messages_count(storage)
1606+
1607+    def test_get(self):
1608+        storage = self.storage_class(self.get_request())
1609+        # Set initial data.
1610+        example_messages = ['test', 'me']
1611+        set_session_data(storage, example_messages)
1612+        # Test that the message actually contains what we expect.
1613+        self.assertEqual(list(storage), example_messages)
1614diff -r 70e75e8cd224 django/contrib/messages/tests/urls.py
1615--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
1616+++ b/django/contrib/messages/tests/urls.py     Wed Dec 09 08:05:02 2009 -0500
1617@@ -0,0 +1,39 @@
1618+from django.conf.urls.defaults import *
1619+from django.contrib import messages
1620+from django.core.urlresolvers import reverse
1621+from django.http import HttpResponseRedirect, HttpResponse
1622+from django.shortcuts import render_to_response
1623+from django.template import RequestContext, Template
1624+
1625+
1626+def add(request, message_type):
1627+    # don't default to False here, because we want to test that it defaults
1628+    # to False if unspecified
1629+    fail_silently = request.POST.get('fail_silently', None)
1630+    for msg in request.POST.getlist('messages'):
1631+        if fail_silently is not None:
1632+            getattr(messages, message_type)(request, msg,
1633+                                            fail_silently=fail_silently)
1634+        else:
1635+            getattr(messages, message_type)(request, msg)
1636+    show_url = reverse('django.contrib.messages.tests.urls.show')
1637+    return HttpResponseRedirect(show_url)
1638+
1639+
1640+def show(request):
1641+    t = Template("""{% if messages %}
1642+<ul class="messages">
1643+    {% for message in messages %}
1644+    <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>
1645+        {{ message }}
1646+    </li>
1647+    {% endfor %}
1648+</ul>
1649+{% endif %}""")
1650+    return HttpResponse(t.render(RequestContext(request)))
1651+
1652+
1653+urlpatterns = patterns('',
1654+    ('^add/(debug|info|success|warning|error)/$', add),
1655+    ('^show/$', show),
1656+)
1657diff -r 70e75e8cd224 django/contrib/messages/tests/user_messages.py
1658--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
1659+++ b/django/contrib/messages/tests/user_messages.py    Wed Dec 09 08:05:02 2009 -0500
1660@@ -0,0 +1,65 @@
1661+from django import http
1662+from django.contrib.auth.models import User
1663+from django.contrib.messages.storage.user_messages import UserMessagesStorage,\
1664+    LegacyFallbackStorage
1665+from django.contrib.messages.tests.cookie import set_cookie_data
1666+from django.contrib.messages.tests.fallback import FallbackTest
1667+from django.test import TestCase
1668+
1669+
1670+class UserMessagesTest(TestCase):
1671+
1672+    def setUp(self):
1673+        self.user = User.objects.create(username='tester')
1674+
1675+    def test_add(self):
1676+        storage = UserMessagesStorage(http.HttpRequest())
1677+        self.assertRaises(NotImplementedError, storage.add, 'Test message 1')
1678+
1679+    def test_get_anonymous(self):
1680+        # Ensure that the storage still works if no user is attached to the
1681+        # request.
1682+        storage = UserMessagesStorage(http.HttpRequest())
1683+        self.assertEqual(len(storage), 0)
1684+
1685+    def test_get(self):
1686+        storage = UserMessagesStorage(http.HttpRequest())
1687+        storage.request.user = self.user
1688+        self.user.message_set.create(message='test message')
1689+
1690+        self.assertEqual(len(storage), 1)
1691+        self.assertEqual(list(storage)[0].message, 'test message')
1692+
1693+
1694+class LegacyFallbackTest(FallbackTest, TestCase):
1695+    storage_class = LegacyFallbackStorage
1696+
1697+    def setUp(self):
1698+        super(LegacyFallbackTest, self).setUp()
1699+        self.user = User.objects.create(username='tester')
1700+
1701+    def get_request(self, *args, **kwargs):
1702+        request = super(LegacyFallbackTest, self).get_request(*args, **kwargs)
1703+        request.user = self.user
1704+        return request
1705+
1706+    def test_get_legacy_only(self):
1707+        request = self.get_request()
1708+        storage = self.storage_class(request)
1709+        self.user.message_set.create(message='user message')
1710+
1711+        # Test that the message actually contains what we expect.
1712+        self.assertEqual(len(storage), 1)
1713+        self.assertEqual(list(storage)[0].message, 'user message')
1714+
1715+    def test_get_legacy(self):
1716+        request = self.get_request()
1717+        storage = self.storage_class(request)
1718+        cookie_storage = self.get_cookie_storage(storage)
1719+        self.user.message_set.create(message='user message')
1720+        set_cookie_data(cookie_storage, ['cookie'])
1721+
1722+        # Test that the message actually contains what we expect.
1723+        self.assertEqual(len(storage), 2)
1724+        self.assertEqual(list(storage)[0].message, 'user message')
1725+        self.assertEqual(list(storage)[1], 'cookie')
1726diff -r 70e75e8cd224 django/contrib/messages/utils.py
1727--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
1728+++ b/django/contrib/messages/utils.py  Wed Dec 09 08:05:02 2009 -0500
1729@@ -0,0 +1,11 @@
1730+from django.conf import settings
1731+from django.contrib.messages import constants
1732+
1733+
1734+def get_level_tags():
1735+    """
1736+    Returns the message level tags.
1737+    """
1738+    level_tags = constants.DEFAULT_TAGS.copy()
1739+    level_tags.update(getattr(settings, 'MESSAGE_TAGS', {}))
1740+    return level_tags
1741diff -r 70e75e8cd224 django/core/context_processors.py
1742--- a/django/core/context_processors.py Thu Dec 03 15:11:14 2009 +0000
1743+++ b/django/core/context_processors.py Wed Dec 09 08:05:02 2009 -0500
1744@@ -10,6 +10,7 @@
1745 from django.conf import settings
1746 from django.middleware.csrf import get_token
1747 from django.utils.functional import lazy, memoize, SimpleLazyObject
1748+from django.contrib import messages
1749 
1750 def auth(request):
1751     """
1752@@ -37,8 +38,8 @@
1753 
1754     return {
1755         'user': SimpleLazyObject(get_user),
1756-        'messages': lazy(memoize(lambda: get_user().get_and_delete_messages(), {}, 0), list)(),
1757-        'perms':  lazy(lambda: PermWrapper(get_user()), PermWrapper)(),
1758+        'messages': messages.get_messages(request),
1759+        'perms': lazy(lambda: PermWrapper(get_user()), PermWrapper)(),
1760     }
1761 
1762 def csrf(request):
1763diff -r 70e75e8cd224 django/views/generic/create_update.py
1764--- a/django/views/generic/create_update.py     Thu Dec 03 15:11:14 2009 +0000
1765+++ b/django/views/generic/create_update.py     Wed Dec 09 08:05:02 2009 -0500
1766@@ -6,6 +6,7 @@
1767 from django.utils.translation import ugettext
1768 from django.contrib.auth.views import redirect_to_login
1769 from django.views.generic import GenericViewError
1770+from django.contrib import messages
1771 
1772 
1773 def apply_extra_context(extra_context, context):
1774@@ -110,8 +111,10 @@
1775         form = form_class(request.POST, request.FILES)
1776         if form.is_valid():
1777             new_object = form.save()
1778-            if request.user.is_authenticated():
1779-                request.user.message_set.create(message=ugettext("The %(verbose_name)s was created successfully.") % {"verbose_name": model._meta.verbose_name})
1780+           
1781+            msg = ugettext("The %(verbose_name)s was created successfully.") %\
1782+                                    {"verbose_name": model._meta.verbose_name}
1783+            messages.success(request, msg, fail_silently=True)
1784             return redirect(post_save_redirect, new_object)
1785     else:
1786         form = form_class()
1787@@ -152,8 +155,9 @@
1788         form = form_class(request.POST, request.FILES, instance=obj)
1789         if form.is_valid():
1790             obj = form.save()
1791-            if request.user.is_authenticated():
1792-                request.user.message_set.create(message=ugettext("The %(verbose_name)s was updated successfully.") % {"verbose_name": model._meta.verbose_name})
1793+            msg = ugettext("The %(verbose_name)s was updated successfully.") %\
1794+                                    {"verbose_name": model._meta.verbose_name}
1795+            messages.success(request, msg, fail_silently=True)
1796             return redirect(post_save_redirect, obj)
1797     else:
1798         form = form_class(instance=obj)
1799@@ -194,8 +198,9 @@
1800 
1801     if request.method == 'POST':
1802         obj.delete()
1803-        if request.user.is_authenticated():
1804-            request.user.message_set.create(message=ugettext("The %(verbose_name)s was deleted.") % {"verbose_name": model._meta.verbose_name})
1805+        msg = ugettext("The %(verbose_name)s was deleted.") %\
1806+                                    {"verbose_name": model._meta.verbose_name}
1807+        messages.success(request, msg, fail_silently=True)
1808         return HttpResponseRedirect(post_delete_redirect)
1809     else:
1810         if not template_name:
1811diff -r 70e75e8cd224 docs/index.txt
1812--- a/docs/index.txt    Thu Dec 03 15:11:14 2009 +0000
1813+++ b/docs/index.txt    Wed Dec 09 08:05:02 2009 -0500
1814@@ -170,6 +170,7 @@
1815     * :ref:`Internationalization <topics-i18n>`
1816     * :ref:`Jython support <howto-jython>`
1817     * :ref:`"Local flavor" <ref-contrib-localflavor>`
1818+    * :ref:`Messages <ref-contrib-messages>`
1819     * :ref:`Pagination <topics-pagination>`
1820     * :ref:`Redirects <ref-contrib-redirects>`
1821     * :ref:`Serialization <topics-serialization>`
1822diff -r 70e75e8cd224 docs/internals/deprecation.txt
1823--- a/docs/internals/deprecation.txt    Thu Dec 03 15:11:14 2009 +0000
1824+++ b/docs/internals/deprecation.txt    Wed Dec 09 08:05:02 2009 -0500
1825@@ -28,6 +28,14 @@
1826         * The many to many SQL generation functions on the database backends
1827           will be removed.  These have been deprecated since the 1.2 release.
1828 
1829+        * The ``Message`` model (in ``django.contrib.auth``), its related
1830+          manager in the ``User`` model (``user.message_set``), and the
1831+          associated methods (``user.message_set.create()`` and
1832+          ``user.get_and_delete_messages()``), which have
1833+          been deprecated since the 1.2 release, will be removed.  The
1834+          :ref:`messages framework <ref-contrib-messages>` should be used
1835+          instead.
1836+
1837     * 2.0
1838         * ``django.views.defaults.shortcut()``. This function has been moved
1839           to ``django.contrib.contenttypes.views.shortcut()`` as part of the
1840diff -r 70e75e8cd224 docs/ref/contrib/index.txt
1841--- a/docs/ref/contrib/index.txt        Thu Dec 03 15:11:14 2009 +0000
1842+++ b/docs/ref/contrib/index.txt        Wed Dec 09 08:05:02 2009 -0500
1843@@ -34,6 +34,7 @@
1844    formtools/index
1845    humanize
1846    localflavor
1847+   messages
1848    redirects
1849    sitemaps
1850    sites
1851@@ -150,6 +151,17 @@
1852 .. _Markdown: http://en.wikipedia.org/wiki/Markdown
1853 .. _ReST (ReStructured Text): http://en.wikipedia.org/wiki/ReStructuredText
1854 
1855+messages
1856+========
1857+
1858+.. versionchanged:: 1.2
1859+    The messages framework was added.
1860+
1861+A framework for storing and retrieving temporary cookie- or session-based
1862+messages
1863+
1864+See the :ref:`messages documentation <ref-contrib-messages>`.
1865+
1866 redirects
1867 =========
1868 
1869diff -r 70e75e8cd224 docs/ref/contrib/messages.txt
1870--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
1871+++ b/docs/ref/contrib/messages.txt     Wed Dec 09 08:05:02 2009 -0500
1872@@ -0,0 +1,405 @@
1873+.. _ref-contrib-messages:
1874+
1875+======================
1876+The messages framework
1877+======================
1878+
1879+.. module:: django.contrib.messages
1880+   :synopsis: Provides cookie- and session-based temporary message storage.
1881+
1882+Django provides full support for cookie- and session-based messaging, for
1883+both anonymous and authenticated clients. The messages framework allows you
1884+to temporarily store messages in one request and retrieve them for display
1885+in a subsequent request (usually the next one). Every message is tagged
1886+with a specific ``level`` that determines its priority (e.g., ``info``,
1887+``warning``, or ``error``).
1888+
1889+.. versionadded:: 1.2
1890+   The messages framework was added.
1891+
1892+Enabling messages
1893+=================
1894+
1895+Messages are implemented through a :ref:`middleware <ref-middleware>`
1896+class and corresponding :ref:`context processor <ref-templates-api>`.
1897+
1898+To enable message functionality, do the following:
1899+
1900+    * Edit the :setting:`MIDDLEWARE_CLASSES` setting and make sure
1901+      it contains ``'django.contrib.messages.middleware.MessageMiddleware'``.
1902+
1903+      If you are using a :ref:`storage backend <message-storage-backends>` that
1904+      relies on :ref:`sessions <topics-http-sessions>` (the default),
1905+      ``'django.contrib.sessions.middleware.SessionMiddleware'`` must be
1906+      enabled and appear before ``MessageMiddleware`` in your
1907+      :setting:`MIDDLEWARE_CLASSES`.
1908+
1909+    * Edit the :setting:`TEMPLATE_CONTEXT_PROCESSORS` setting and make sure
1910+      it contains ``'django.contrib.messages.context_processors.messages'``.
1911+
1912+    * Add ``'django.contrib.messages'`` to your :setting:`INSTALLED_APPS`
1913+      setting
1914+
1915+The default ``settings.py`` created by ``django-admin.py startproject`` has
1916+``MessageMiddleware`` activated and the ``django.contrib.messages`` app
1917+installed.  Also, the default value for :setting:`TEMPLATE_CONTEXT_PROCESSORS`
1918+contains ``'django.contrib.messages.context_processors.messages'``.
1919+
1920+If you don't want to use messages, you can remove the
1921+``MessageMiddleware`` line from :setting:`MIDDLEWARE_CLASSES`, the ``messages``
1922+context processor from :setting:`TEMPLATE_CONTEXT_PROCESSORS` and
1923+``'django.contrib.messages'`` from your :setting:`INSTALLED_APPS`.
1924+
1925+Configuring the message engine
1926+==============================
1927+
1928+.. _message-storage-backends:
1929+
1930+Storage backends
1931+----------------
1932+
1933+The messages framework can use different backends to store temporary messages.
1934+To change which backend is being used, add a `MESSAGE_STORAGE`_ to your
1935+settings, referencing the module and class of the storage class. For
1936+example::
1937+
1938+    MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
1939+
1940+The value should be the full path of the desired storage class.
1941+
1942+Four storage classes are included:
1943+
1944+``'django.contrib.messages.storage.session.SessionStorage'``
1945+    This class stores all messages inside of the request's session. It
1946+    requires Django's ``contrib.session`` application.
1947+
1948+``'django.contrib.messages.storage.cookie.CookieStorage'``
1949+    This class stores the message data in a cookie (signed with a secret hash
1950+    to prevent manipulation) to persist notifications across requests. Old
1951+    messages are dropped if the cookie data size would exceed 4096 bytes.
1952+
1953+``'django.contrib.messages.storage.fallback.FallbackStorage'``
1954+    This class first uses CookieStorage for all messages, falling back to using
1955+    SessionStorage for the messages that could not fit in a single cookie.
1956+
1957+    Since it is uses SessionStorage, it also requires Django's
1958+    ``contrib.session`` application.
1959+
1960+``'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'``
1961+    This is the default temporary storage class.
1962+
1963+    This class extends FallbackStorage and adds compatibility methods to
1964+    to retrieve any messages stored in the user Message model by code that
1965+    has not yet been updated to use the new API. This storage is temporary
1966+    (because it makes use of code that is pending deprecation) and will be
1967+    removed in Django 1.4. At that time, the default storage will become
1968+    ``django.contrib.messages.storage.fallback.FallbackStorage``. For more
1969+    information, see `LegacyFallbackStorage`_ below.
1970+
1971+To write your own storage class, subclass the ``BaseStorage`` class in
1972+``django.contrib.messages.storage.base`` and implement the ``_get`` and
1973+``_store`` methods.
1974+
1975+LegacyFallbackStorage
1976+^^^^^^^^^^^^^^^^^^^^^
1977+
1978+The ``LegacyFallbackStorage`` is a temporary tool to facilitate the transition
1979+from the deprecated ``user.message_set`` API and will be removed in Django 1.4
1980+according to Django's standard deprecation policy.  For more information, see
1981+the full :ref:`release process documentation <internals-release-process>`.
1982+
1983+In addition to the functionality in the ``FallbackStorage``, it adds a custom,
1984+read-only storage class that retrieves messages from the user ``Message``
1985+model. Any messages that were stored in the ``Message`` model (e.g., by code
1986+that has not yet been updated to use the messages framework) will be retrieved
1987+first, followed by those stored in a cookie and in the session, if any.  Since
1988+messages stored in the ``Message`` model do not have a concept of levels, they
1989+will be assigned the ``INFO`` level by default.
1990+
1991+Message levels
1992+--------------
1993+
1994+The messages framework is based on a configurable level architecture similar
1995+to that of the Python logging module.  Message levels allow you to group
1996+messages by type so they can be filtered or displayed differently in views and
1997+templates.
1998+
1999+The built-in levels (which can be imported from ``django.contrib.messages``
2000+directly) are:
2001+
2002+=========== ========
2003+Constant    Purpose
2004+=========== ========
2005+``DEBUG``   Development-related messages that will be ignored (or removed) in a production deployment
2006+``INFO``    Informational messages for the user
2007+``SUCCESS`` An action was successful, e.g. "Your profile was updated successfully"
2008+``WARNING`` A failure did not occur but may be imminent
2009+``ERROR``   An action was **not** successful or some other failure occurred
2010+=========== ========
2011+
2012+The `MESSAGE_LEVEL`_ setting can be used to change the minimum recorded
2013+level. Attempts to add messages of a level less than this will be ignored.
2014+
2015+Message tags
2016+------------
2017+
2018+Message tags are a string representation of the message level plus any
2019+extra tags that were added directly in the view (see
2020+`Adding extra message tags`_ below for more details).  Tags are stored in a
2021+string and are separated by spaces.  Typically, message tags
2022+are used as CSS classes to customize message style based on message type. By
2023+default, each level has a single tag that's a lowercase version of its own
2024+constant:
2025+
2026+==============  ===========
2027+Level Constant  Tag
2028+==============  ===========
2029+``DEBUG``       ``debug``
2030+``INFO``        ``info``
2031+``SUCCESS``     ``success``
2032+``WARNING``     ``warning``
2033+``ERROR``       ``error``
2034+==============  ===========
2035+
2036+To change the default tags for a message level (either built-in or custom),
2037+set the `MESSAGE_TAGS`_ setting to a dictionary containing the levels
2038+you wish to change. As this extends the default tags, you only need to provide
2039+tags for the levels you wish to override::
2040+
2041+    from django.contrib.messages import constants as messages
2042+    MESSAGE_TAGS = {
2043+        messages.INFO: '',
2044+        50: 'critical',
2045+    }
2046+
2047+Using messages in views and templates
2048+=====================================
2049+
2050+Adding a message
2051+----------------
2052+
2053+To add a message, call::
2054+
2055+    from django.contrib import messages
2056+    messages.add_message(request, messages.INFO, 'Hello world.')
2057+
2058+Some shortcut methods provide a standard way to add messages with commonly
2059+used tags (which are usually represented as HTML classes for the message)::
2060+
2061+    messages.debug(request, '%s SQL statements were executed.' % count)
2062+    messages.info(request, 'Three credits remain in your account.')
2063+    messages.success(request, 'Profile details updated.')
2064+    messages.warning(request, 'Your account expires in three days.')
2065+    messages.error(request, 'Document deleted.')
2066+
2067+Displaying messages
2068+-------------------
2069+
2070+In your template, use something like::
2071+
2072+    {% if messages %}
2073+    <ul class="messages">
2074+        {% for message in messages %}
2075+        <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
2076+        {% endfor %}
2077+    </ul>
2078+    {% endif %}
2079+
2080+If you're using the context processor, your template should be rendered with a
2081+``RequestContext``. Otherwise, ensure ``messages`` is available to
2082+the template context.
2083+
2084+Creating custom message levels
2085+------------------------------
2086+
2087+Messages levels are nothing more than integers, so you can define your own
2088+level constants and use them to create more customized user feedback, e.g.::
2089+
2090+    CRITICAL = 50
2091+
2092+    def my_view(request):
2093+        messages.add_message(request, CRITICAL, 'A serious error occurred.')
2094+
2095+When creating custom message levels you should be careful to avoid overloading
2096+existing levels.  The values for the built-in levels are:
2097+
2098+.. _message-level-constants:
2099+
2100+==============  =====
2101+Level Constant  Value
2102+==============  =====
2103+``DEBUG``       10
2104+``INFO``        20
2105+``SUCCESS``     25
2106+``WARNING``     30
2107+``ERROR``       40
2108+==============  =====
2109+
2110+If you need to identify the custom levels in your HTML or CSS, you need to
2111+provide a mapping via the `MESSAGE_TAGS`_ setting.
2112+
2113+.. note::
2114+   If you are creating a reusable application, it is recommended to use
2115+   only the built-in `message levels`_ and not rely on any custom levels.
2116+
2117+Changing the minimum recorded level per-request
2118+-----------------------------------------------
2119+
2120+The minimum recorded level can be set per request by changing the ``level``
2121+attribute of the messages storage instance::
2122+
2123+    from django.contrib import messages
2124+
2125+    # Change the messages level to ensure the debug message is added.
2126+    messages.get_messages(request).level = messages.DEBUG
2127+    messages.debug(request, 'Test message...')
2128+
2129+    # In another request, record only messages with a level of WARNING and higher
2130+    messages.get_messages(request).level = messages.WARNING
2131+    messages.success(request, 'Your profile was updated.') # ignored
2132+    messages.warning(request, 'Your account is about to expire.') # recorded
2133+
2134+    # Set the messages level back to default.
2135+    messages.get_messages(request).level = None
2136+
2137+For more information on how the minimum recorded level functions, see
2138+`Message levels`_ above.
2139+
2140+Adding extra message tags
2141+-------------------------
2142+
2143+For more direct control over message tags, you can optionally provide a string
2144+containing extra tags to any of the add methods::
2145+
2146+    messages.add_message(request, messages.INFO, 'Over 9000!',
2147+                         extra_tags='dragonball')
2148+    messages.error(request, 'Email box full', extra_tags='email')
2149+
2150+Extra tags are added before the default tag for that level and are space
2151+separated.
2152+
2153+Failing silently when the message framework is disabled
2154+-------------------------------------------------------
2155+
2156+If you're writing a reusable app (or other piece of code) and want to include
2157+messaging functionality, but don't want to require your users to enable it
2158+if they don't want to, you may pass an additional keyword argument
2159+``fail_silently=True`` to any of the ``add_message`` family of methods. For
2160+example::
2161+
2162+    messages.add_message(request, messages.SUCCESS, 'Profile details updated.',
2163+                         fail_silently=True)
2164+    messages.info(request, 'Hello world.', fail_silently=True)
2165+
2166+Internally, Django uses this functionality in the create, update, and delete
2167+:ref:`generic views <topics-generic-views>` so that they work even if the
2168+message framework is disabled.
2169+
2170+.. note::
2171+   Setting ``fail_silently=True`` only hides the ``MessageFailure`` that would
2172+   otherwise occur when the messages framework disabled and one attempts to
2173+   use one of the ``add_message`` family of methods.  It does not hide failures
2174+   that may occur for other reasons.
2175+
2176+Expiration of messages
2177+======================
2178+
2179+The messages are marked to be cleared when the storage instance is iterated
2180+(and cleared when the response is processed).
2181+
2182+To avoid the messages being cleared, you can set the messages storage to
2183+``False`` after iterating::
2184+
2185+    storage = messages.get_messages(request)
2186+    for message in storage:
2187+        do_something_with(message)
2188+    storage.used = False
2189+
2190+Behavior of parallel requests
2191+=============================
2192+
2193+Due to the way cookies (and hence sessions) work, **the behavior of any
2194+backends that make use of cookies or sessions is undefined when the same
2195+client makes multiple requests that set or get messages in parallel**.  For
2196+example, if a client initiates a request that creates a message in one window
2197+(or tab) and then another that fetches any uniterated messages in another
2198+window, before the first window redirects, the message may appear in the
2199+second window instead of the first window where it may be expected.
2200+
2201+In short, when multiple simultaneous requests from the same client are
2202+involved, messages are not guaranteed to be delivered to the same window that
2203+created them nor, in some cases, at all.  Note that this is typically not a
2204+problem in most applications and will become a non-issue in HTML5, where each
2205+window/tab will have its own browsing context.
2206+
2207+Settings
2208+========
2209+
2210+A few :ref:`Django settings <ref-settings>` give you control over message
2211+behavior:
2212+
2213+MESSAGE_LEVEL
2214+-------------
2215+
2216+Default: ``messages.INFO``
2217+
2218+This sets the minimum message that will be saved in the message storage.  See
2219+`Message levels`_ above for more details.
2220+
2221+.. admonition:: Important
2222+
2223+   If you override ``MESSAGE_LEVEL`` in your settings file and rely on any of
2224+   the built-in constants, you must import the constants module directly to
2225+   avoid the potential for circular imports, e.g.::
2226+
2227+       from django.contrib.messages import constants as message_constants
2228+       MESSAGE_LEVEL = message_constants.DEBUG
2229+
2230+   If desired, you may specify the numeric values for the constants directly
2231+   according to the values in the above :ref:`constants table
2232+   <message-level-constants>`.
2233+
2234+MESSAGE_STORAGE
2235+---------------
2236+
2237+Default: ``'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'``
2238+
2239+Controls where Django stores message data. Valid values are:
2240+
2241+    * ``'django.contrib.messages.storage.fallback.FallbackStorage'``
2242+    * ``'django.contrib.messages.storage.session.SessionStorage'``
2243+    * ``'django.contrib.messages.storage.cookie.CookieStorage'``
2244+    * ``'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'``
2245+
2246+See `Storage backends`_ for more details.
2247+
2248+MESSAGE_TAGS
2249+------------
2250+
2251+Default::
2252+
2253+        {messages.DEBUG: 'debug',
2254+        messages.INFO: 'info',
2255+        messages.SUCCESS: 'success',
2256+        messages.WARNING: 'warning',
2257+        messages.ERROR: 'error',}
2258+
2259+This sets the mapping of message level to message tag, which is typically
2260+rendered as a CSS class in HTML. If you specify a value, it will extend
2261+the default. This means you only have to specify those values which you need
2262+to override. See `Displaying messages`_ above for more details.
2263+
2264+.. admonition:: Important
2265+
2266+   If you override ``MESSAGE_TAGS`` in your settings file and rely on any of
2267+   the built-in constants, you must import the ``constants`` module directly to
2268+   avoid the potential for circular imports, e.g.::
2269+
2270+       from django.contrib.messages import constants as message_constants
2271+       MESSAGE_TAGS = {message_constants.INFO: ''}
2272+
2273+   If desired, you may specify the numeric values for the constants directly
2274+   according to the values in the above :ref:`constants table
2275+   <message-level-constants>`.
2276+
2277+.. _Django settings: ../settings/
2278diff -r 70e75e8cd224 docs/ref/middleware.txt
2279--- a/docs/ref/middleware.txt   Thu Dec 03 15:11:14 2009 +0000
2280+++ b/docs/ref/middleware.txt   Wed Dec 09 08:05:02 2009 -0500
2281@@ -139,6 +139,20 @@
2282 content for each user. See the :ref:`internationalization documentation
2283 <topics-i18n>`.
2284 
2285+Message middleware
2286+------------------
2287+
2288+.. module:: django.contrib.messages.middleware
2289+   :synopsis: Message middleware.
2290+
2291+.. class:: django.contrib.messages.middleware.MessageMiddleware
2292+
2293+.. versionadded:: 1.2
2294+   ``MessageMiddleware`` was added.
2295+   
2296+Enables cookie- and session-based message support. See the
2297+:ref:`messages documentation <ref-contrib-messages>`.
2298+
2299 Session middleware
2300 ------------------
2301 
2302diff -r 70e75e8cd224 docs/ref/settings.txt
2303--- a/docs/ref/settings.txt     Thu Dec 03 15:11:14 2009 +0000
2304+++ b/docs/ref/settings.txt     Wed Dec 09 08:05:02 2009 -0500
2305@@ -812,6 +812,43 @@
2306 
2307 .. setting:: MIDDLEWARE_CLASSES
2308 
2309+MESSAGE_LEVEL
2310+-------------
2311+
2312+.. versionadded:: 1.2
2313+
2314+Default: `messages.INFO`
2315+
2316+Sets the minimum message level that will be recorded by the messages
2317+framework. See the :ref:`messages documentation <ref-contrib-messages>` for
2318+more details.
2319+
2320+MESSAGE_STORAGE
2321+---------------
2322+
2323+.. versionadded:: 1.2
2324+
2325+Default: ``'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'``
2326+
2327+Controls where Django stores message data.  See the
2328+:ref:`messages documentation <ref-contrib-messages>` for more details.
2329+
2330+MESSAGE_TAGS
2331+------------
2332+
2333+.. versionadded:: 1.2
2334+
2335+Default::
2336+
2337+        {messages.DEBUG: 'debug',
2338+        messages.INFO: 'info',
2339+        messages.SUCCESS: 'success',
2340+        messages.WARNING: 'warning',
2341+        messages.ERROR: 'error',}
2342+
2343+Sets the mapping of message levels to message tags. See the
2344+:ref:`messages documentation <ref-contrib-messages>` for more details.
2345+
2346 MIDDLEWARE_CLASSES
2347 ------------------
2348 
2349@@ -820,10 +857,16 @@
2350     ('django.middleware.common.CommonMiddleware',
2351      'django.contrib.sessions.middleware.SessionMiddleware',
2352      'django.middleware.csrf.CsrfViewMiddleware',
2353-     'django.contrib.auth.middleware.AuthenticationMiddleware',)
2354+     'django.contrib.auth.middleware.AuthenticationMiddleware',
2355+     'django.contrib.messages.middleware.MessageMiddleware',)
2356 
2357 A tuple of middleware classes to use. See :ref:`topics-http-middleware`.
2358 
2359+.. versionchanged:: 1.2
2360+   ``'django.contrib.messages.middleware.MessageMiddleware'`` was added to the
2361+   default.  For more information, see the :ref:`messages documentation
2362+   <ref-contrib-messages>`.
2363+
2364 .. setting:: MONTH_DAY_FORMAT
2365 
2366 MONTH_DAY_FORMAT
2367@@ -1059,12 +1102,18 @@
2368     ("django.core.context_processors.auth",
2369     "django.core.context_processors.debug",
2370     "django.core.context_processors.i18n",
2371-    "django.core.context_processors.media")
2372+    "django.core.context_processors.media",
2373+    "django.contrib.messages.context_processors.messages")
2374 
2375 A tuple of callables that are used to populate the context in ``RequestContext``.
2376 These callables take a request object as their argument and return a dictionary
2377 of items to be merged into the context.
2378 
2379+.. versionchanged:: 1.2
2380+   ``"django.contrib.messages.context_processors.messages"`` was added to the
2381+   default.  For more information, see the :ref:`messages documentation
2382+   <ref-contrib-messages>`.
2383+
2384 .. setting:: TEMPLATE_DEBUG
2385 
2386 TEMPLATE_DEBUG
2387diff -r 70e75e8cd224 docs/ref/templates/api.txt
2388--- a/docs/ref/templates/api.txt        Thu Dec 03 15:11:14 2009 +0000
2389+++ b/docs/ref/templates/api.txt        Wed Dec 09 08:05:02 2009 -0500
2390@@ -311,7 +311,8 @@
2391     ("django.core.context_processors.auth",
2392     "django.core.context_processors.debug",
2393     "django.core.context_processors.i18n",
2394-    "django.core.context_processors.media")
2395+    "django.core.context_processors.media",
2396+    "django.contrib.messages.context_processors.messages")
2397 
2398 .. versionadded:: 1.2
2399    In addition to these, ``RequestContext`` always uses
2400@@ -320,6 +321,10 @@
2401    in case of accidental misconfiguration, it is deliberately hardcoded in and
2402    cannot be turned off by the :setting:`TEMPLATE_CONTEXT_PROCESSORS` setting.
2403 
2404+.. versionadded:: 1.2
2405+   The ``'messages'`` context processor was added.  For more information, see
2406+   the :ref:`messages documentation <ref-contrib-messages>`.
2407+
2408 Each processor is applied in order. That means, if one processor adds a
2409 variable to the context and a second processor adds a variable with the same
2410 name, the second will override the first. The default processors are explained
2411@@ -365,17 +370,18 @@
2412       logged-in user (or an ``AnonymousUser`` instance, if the client isn't
2413       logged in).
2414 
2415-    * ``messages`` -- A list of messages (as strings) for the currently
2416-      logged-in user. Behind the scenes, this calls
2417-      ``request.user.get_and_delete_messages()`` for every request. That method
2418-      collects the user's messages and deletes them from the database.
2419-
2420-      Note that messages are set with ``user.message_set.create``.
2421+    * ``messages`` -- A list of messages (as strings) that have been set
2422+      via the :ref:`messages framework <ref-contrib-messages>`.
2423 
2424     * ``perms`` -- An instance of
2425       ``django.core.context_processors.PermWrapper``, representing the
2426       permissions that the currently logged-in user has.
2427 
2428+.. versionchanged:: 1.2
2429+   Prior to version 1.2, the ``messages`` variable was a lazy accessor for
2430+   ``user.get_and_delete_messages()``. It has been changed to include any
2431+   messages added via the :ref:`messages framework <ref-contrib-messages`.
2432+
2433 django.core.context_processors.debug
2434 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2435 
2436@@ -427,6 +433,25 @@
2437 :class:`~django.http.HttpRequest`. Note that this processor is not enabled by default;
2438 you'll have to activate it.
2439 
2440+django.contrib.messages.context_processors.messages
2441+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2442+
2443+If :setting:`TEMPLATE_CONTEXT_PROCESSORS` contains this processor, every
2444+``RequestContext`` will contain a single additional variable:
2445+
2446+    * ``messages`` -- A list of messages (as strings) that have been set
2447+      via the user model (using ``user.message_set.create``) or through
2448+      the :ref:`messages framework <ref-contrib-messages>`.
2449+
2450+.. versionadded:: 1.2
2451+   This template context variable was previously supplied by the ``'auth'``
2452+   context processor.  For backwards compatibility the ``'auth'`` context
2453+   processor will continue to supply the ``messages`` variable until Django
2454+   1.4.  If you use the ``messages`` variable, your project will work with
2455+   either (or both) context processors, but it is recommended to add
2456+   ``django.contrib.messages.context_processors.messages`` so your project
2457+   will be prepared for the future upgrade.
2458+
2459 Writing your own context processors
2460 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2461 
2462diff -r 70e75e8cd224 docs/releases/1.2.txt
2463--- a/docs/releases/1.2.txt     Thu Dec 03 15:11:14 2009 +0000
2464+++ b/docs/releases/1.2.txt     Wed Dec 09 08:05:02 2009 -0500
2465@@ -121,6 +121,38 @@
2466 :meth:`~django.core.mail.get_connection()` call::
2467 
2468     connection = get_connection('django.core.mail.backends.smtp', hostname='localhost', port=1234)
2469+   
2470+User Messages API
2471+-----------------
2472+
2473+The API for storing messages in the user ``Message`` model (via
2474+``user.message_set.create``) is now deprecated and will be removed in Django
2475+1.4 according to the standard :ref:`release process <internals-release-process>`.
2476+
2477+To upgrade your code, you need to replace any instances of::
2478+
2479+    user.message_set.create('a message')
2480+
2481+with the following::
2482+
2483+    from django.contrib import messages
2484+    messages.add_message(request, messages.INFO, 'a message')
2485+
2486+Additionally, if you make use of the method, you need to replace the
2487+following::
2488+
2489+    for message in user.get_and_delete_messages():
2490+        ...
2491+   
2492+with::
2493+
2494+    from django.contrib import messages
2495+    for message in messages.get_messages(request):
2496+        ...
2497+   
2498+For more information, see the full
2499+:ref:`messages documentation <ref-contrib-messages>`. You should begin to
2500+update your code to use the new API immediately.
2501 
2502 What's new in Django 1.2
2503 ========================
2504@@ -155,3 +187,12 @@
2505 :ref:`memory<topic-email-memory-backend>` - you can even configure all
2506 e-mail to be :ref:`thrown away<topic-email-dummy-backend>`.
2507 
2508+Messages Framework
2509+-------------------------
2510+
2511+Django now includes a robust and configurable
2512+:ref:`messages framework <ref-contrib-messages>` with built-in support for
2513+cookie- and session-based messaging, for both anonymous and authenticated
2514+clients. The messages framework replaces the deprecated user message API and
2515+allows you to temporarily store messages in one request and retrieve them for
2516+display in a subsequent request (usually the next one).
2517diff -r 70e75e8cd224 docs/topics/auth.txt
2518--- a/docs/topics/auth.txt      Thu Dec 03 15:11:14 2009 +0000
2519+++ b/docs/topics/auth.txt      Wed Dec 09 08:05:02 2009 -0500
2520@@ -23,6 +23,9 @@
2521       user.
2522     * Messages: A simple way to queue messages for given users.
2523 
2524+.. deprecated:: 1.2
2525+   The Messages component of the auth system will be removed in Django 1.4.
2526+
2527 Installation
2528 ============
2529 
2530@@ -1289,6 +1292,11 @@
2531 Messages
2532 ========
2533 
2534+.. deprecated:: 1.2
2535+   This functionality will be removed in Django 1.4.  You should use the
2536+   :ref:`messages framework <ref-contrib-messages>` for all new projects and
2537+   begin to update your existing code immediately.
2538+
2539 The message system is a lightweight way to queue messages for given users.
2540 
2541 A message is associated with a :class:`~django.contrib.auth.models.User`.
2542@@ -1334,13 +1342,16 @@
2543     </ul>
2544     {% endif %}
2545 
2546-Note that :class:`~django.template.context.RequestContext` calls
2547-:meth:`~django.contrib.auth.models.User.get_and_delete_messages` behind the
2548-scenes, so any messages will be deleted even if you don't display them.
2549+.. versionchanged:: 1.2
2550+   The ``messages`` template variable uses a backwards compatible method in the
2551+   :ref:`messages framework <ref-contrib-messages>` to retrieve messages from
2552+   both the user ``Message`` model and from the new framework.  Unlike in
2553+   previous revisions, the messages will not be erased unless they are actually
2554+   displayed.
2555 
2556 Finally, note that this messages framework only works with users in the user
2557 database. To send messages to anonymous users, use the
2558-:ref:`session framework <topics-http-sessions>`.
2559+:ref:`messages framework <ref-contrib-messages>`.
2560 
2561 .. _authentication-backends:
2562 
2563diff -r 70e75e8cd224 tests/runtests.py
2564--- a/tests/runtests.py Thu Dec 03 15:11:14 2009 +0000
2565+++ b/tests/runtests.py Wed Dec 09 08:05:02 2009 -0500
2566@@ -28,6 +28,7 @@
2567     'django.contrib.flatpages',
2568     'django.contrib.redirects',
2569     'django.contrib.sessions',
2570+    'django.contrib.messages',
2571     'django.contrib.comments',
2572     'django.contrib.admin',
2573 ]
2574@@ -107,6 +108,7 @@
2575     settings.MIDDLEWARE_CLASSES = (
2576         'django.contrib.sessions.middleware.SessionMiddleware',
2577         'django.contrib.auth.middleware.AuthenticationMiddleware',
2578+        'django.contrib.messages.middleware.MessageMiddleware',
2579         'django.middleware.common.CommonMiddleware',
2580     )
2581     settings.SITE_ID = 1