Code

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

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

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

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