comparing with http://bitbucket.org/mirror/django-trunk/
searching for changes
diff -r ed90e13d2065 django/conf/global_settings.py
--- a/django/conf/global_settings.py	Tue Nov 17 02:13:26 2009 +0000
+++ b/django/conf/global_settings.py	Tue Nov 17 00:55:09 2009 -0500
@@ -172,6 +172,7 @@
     'django.core.context_processors.i18n',
     'django.core.context_processors.media',
 #    'django.core.context_processors.request',
+    'django.contrib.messages.context_processors.messages',
 )
 
 # Output to use in template system for invalid (e.g. misspelled) variables.
@@ -308,6 +309,7 @@
     'django.contrib.sessions.middleware.SessionMiddleware',
     'django.middleware.csrf.CsrfViewMiddleware',
     'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.contrib.messages.middleware.MessageMiddleware',
 #     'django.middleware.http.ConditionalGetMiddleware',
 #     'django.middleware.gzip.GZipMiddleware',
 )
diff -r ed90e13d2065 django/conf/project_template/settings.py
--- a/django/conf/project_template/settings.py	Tue Nov 17 02:13:26 2009 +0000
+++ b/django/conf/project_template/settings.py	Tue Nov 17 00:55:09 2009 -0500
@@ -62,6 +62,7 @@
     'django.contrib.sessions.middleware.SessionMiddleware',
     'django.middleware.csrf.CsrfViewMiddleware',
     'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.contrib.messages.middleware.MessageMiddleware',
 )
 
 ROOT_URLCONF = '{{ project_name }}.urls'
@@ -77,4 +78,5 @@
     'django.contrib.contenttypes',
     'django.contrib.sessions',
     'django.contrib.sites',
+    'django.contrib.messages',
 )
diff -r ed90e13d2065 django/contrib/admin/options.py
--- a/django/contrib/admin/options.py	Tue Nov 17 02:13:26 2009 +0000
+++ b/django/contrib/admin/options.py	Tue Nov 17 00:55:09 2009 -0500
@@ -6,6 +6,8 @@
 from django.contrib.admin import widgets
 from django.contrib.admin import helpers
 from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects, model_ngettext, model_format_dict
+from django.contrib import messages
+from django.contrib.messages.compat import compat_add_message
 from django.views.decorators.csrf import csrf_protect
 from django.core.exceptions import PermissionDenied
 from django.db import models, transaction
@@ -541,9 +543,9 @@
     def message_user(self, request, message):
         """
         Send a message to the user. The default implementation
-        posts a message using the auth Message object.
+        posts a message using the django.contrib.messages backend.
         """
-        request.user.message_set.create(message=message)
+        compat_add_message(request, messages.INFO, message)
 
     def save_form(self, request, form, change):
         """
diff -r ed90e13d2065 django/contrib/admin/views/template.py
--- a/django/contrib/admin/views/template.py	Tue Nov 17 02:13:26 2009 +0000
+++ b/django/contrib/admin/views/template.py	Tue Nov 17 00:55:09 2009 -0500
@@ -6,6 +6,8 @@
 from django.conf import settings
 from django.utils.importlib import import_module
 from django.utils.translation import ugettext_lazy as _
+from django.contrib import messages
+from django.contrib.messages.compat import compat_add_message
 
 
 def template_validator(request):
@@ -23,7 +25,7 @@
         form = TemplateValidatorForm(settings_modules, site_list,
                                      data=request.POST)
         if form.is_valid():
-            request.user.message_set.create(message='The template is valid.')
+            compat_add_message(request, messages.INFO, 'The template is valid.')
     else:
         form = TemplateValidatorForm(settings_modules, site_list)
     return render_to_response('admin/template_validator.html', {
diff -r ed90e13d2065 django/contrib/auth/admin.py
--- a/django/contrib/auth/admin.py	Tue Nov 17 02:13:26 2009 +0000
+++ b/django/contrib/auth/admin.py	Tue Nov 17 00:55:09 2009 -0500
@@ -3,6 +3,8 @@
 from django.contrib import admin
 from django.contrib.auth.forms import UserCreationForm, UserChangeForm, AdminPasswordChangeForm
 from django.contrib.auth.models import User, Group
+from django.contrib import messages
+from django.contrib.messages.compat import compat_add_message
 from django.core.exceptions import PermissionDenied
 from django.http import HttpResponseRedirect, Http404
 from django.shortcuts import render_to_response, get_object_or_404
@@ -67,12 +69,12 @@
                 msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': 'user', 'obj': new_user}
                 self.log_addition(request, new_user)
                 if "_addanother" in request.POST:
-                    request.user.message_set.create(message=msg)
+                    compat_add_message(request, messages.SUCCESS, msg)
                     return HttpResponseRedirect(request.path)
                 elif '_popup' in request.REQUEST:
                     return self.response_add(request, new_user)
                 else:
-                    request.user.message_set.create(message=msg + ' ' + ugettext("You may edit it again below."))
+                    compat_add_message(request, messages.SUCCESS, msg + ' ' + ugettext("You may edit it again below."))
                     return HttpResponseRedirect('../%s/' % new_user.id)
         else:
             form = self.add_form()
@@ -104,7 +106,7 @@
             if form.is_valid():
                 new_user = form.save()
                 msg = ugettext('Password changed successfully.')
-                request.user.message_set.create(message=msg)
+                compat_add_message(request, messages.SUCCESS, msg)
                 return HttpResponseRedirect('..')
         else:
             form = self.change_password_form(user)
diff -r ed90e13d2065 django/contrib/auth/models.py
--- a/django/contrib/auth/models.py	Tue Nov 17 02:13:26 2009 +0000
+++ b/django/contrib/auth/models.py	Tue Nov 17 00:55:09 2009 -0500
@@ -288,6 +288,14 @@
                 raise SiteProfileNotAvailable
         return self._profile_cache
 
+    def _get_message_set(self):
+        import warnings
+        warnings.warn('The user messaging API will be deprecated in Django 1.2.'
+                      ' Please update your code to use the new messaging API.',
+                      category=PendingDeprecationWarning)
+        return self._message_set
+    message_set = property(_get_message_set)
+
 class Message(models.Model):
     """
     The message system is a lightweight way to queue messages for given
@@ -297,7 +305,7 @@
     actions. For example, "The poll Foo was created successfully." is a
     message.
     """
-    user = models.ForeignKey(User)
+    user = models.ForeignKey(User, related_name='_message_set')
     message = models.TextField(_('message'))
 
     def __unicode__(self):
diff -r ed90e13d2065 django/contrib/messages/__init__.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/contrib/messages/__init__.py	Tue Nov 17 00:55:09 2009 -0500
@@ -0,0 +1,1 @@
+from constants import *
diff -r ed90e13d2065 django/contrib/messages/compat.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/contrib/messages/compat.py	Tue Nov 17 00:55:09 2009 -0500
@@ -0,0 +1,45 @@
+"""
+Compatibility methods to assist in the transition from user.message_set to the
+messages contrib app.
+
+These methods are for use by Django internally and it is not recommended
+that they called directly from a Django project.  They will be removed in a
+future version of Django and are not subject to the normal deprecation policy.
+"""
+
+from django.utils.functional import lazy, memoize
+
+__all__ = (
+    'compat_add_message',
+    'compat_get_messages',
+)
+
+
+def compat_add_message(request, level, message):
+    """
+    Attempts to add a message to the request using the 'messages' app, falling
+    back to the user's message_set if MessageMiddleware hasn't been enabled.
+    """
+    if hasattr(request, 'messages'):
+        request.messages.add(level, message)
+    elif hasattr(request, 'user') and request.user.is_authenticated():
+        request.user.message_set.create(message=message)
+
+
+def compat_get_messages(request, get_user=None):
+    """
+    Returns the message storage on the request if it exists, otherwise returns
+    user.message_set.all() as the old auth context processor did.
+    """
+
+    def get_user():
+        if hasattr(request, 'user'):
+            return request.user
+        else:
+            from django.contrib.auth.models import AnonymousUser
+            return AnonymousUser()
+
+    if hasattr(request, 'messages'):
+        return request.messages
+    else:
+        return lazy(memoize(get_user().get_and_delete_messages, {}, 0), list)()
diff -r ed90e13d2065 django/contrib/messages/constants.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/contrib/messages/constants.py	Tue Nov 17 00:55:09 2009 -0500
@@ -0,0 +1,13 @@
+DEBUG = 10
+INFO = 20
+SUCCESS = 25
+WARNING = 30
+ERROR = 40
+ 
+DEFAULT_TAGS = {
+    DEBUG: 'debug',
+    INFO: 'info',
+    SUCCESS: 'success',
+    WARNING: 'warning',
+    ERROR: 'error',
+}
diff -r ed90e13d2065 django/contrib/messages/context_processors.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/contrib/messages/context_processors.py	Tue Nov 17 00:55:09 2009 -0500
@@ -0,0 +1,8 @@
+from django.contrib.messages.compat import compat_get_messages
+
+
+def messages(request):
+    """
+    Returns a lazy 'messages' context variable.
+    """
+    return {'messages': compat_get_messages(request)}
diff -r ed90e13d2065 django/contrib/messages/middleware.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/contrib/messages/middleware.py	Tue Nov 17 00:55:09 2009 -0500
@@ -0,0 +1,26 @@
+from django.conf import settings
+from django.contrib.messages.storage import Storage
+
+
+class MessageMiddleware(object):
+    """
+    Middleware that handles temporary messages.
+    """
+
+    def process_request(self, request):
+        request.messages = Storage(request)
+
+    def process_response(self, request, response):
+        """
+        Updates the storage backend (i.e., saves the messages).
+        
+        If not all messages could not be stored and ``DEBUG`` is ``True``, a
+        ``ValueError`` is raised.
+        """
+        # A higher middleware layer may return a request which does not contain
+        # messages storage, so make no assumption that it will be there.
+        if hasattr(request, 'messages'):
+            unstored_messages = request.messages.update(response)
+            if unstored_messages and settings.DEBUG:
+                raise ValueError('Not all temporary messages could be stored.')
+        return response
diff -r ed90e13d2065 django/contrib/messages/models.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/contrib/messages/models.py	Tue Nov 17 00:55:09 2009 -0500
@@ -0,0 +1,1 @@
+# Models module required so tests are discovered.
diff -r ed90e13d2065 django/contrib/messages/storage/__init__.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/contrib/messages/storage/__init__.py	Tue Nov 17 00:55:09 2009 -0500
@@ -0,0 +1,6 @@
+from django.conf import settings
+from django.contrib.messages.storage.base import BaseStorage
+from django.utils.importcls import get_class
+
+Storage = get_class(getattr(settings, 'MESSAGE_STORAGE',
+    'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'))
diff -r ed90e13d2065 django/contrib/messages/storage/base.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/contrib/messages/storage/base.py	Tue Nov 17 00:55:09 2009 -0500
@@ -0,0 +1,207 @@
+from django.conf import settings
+from django.utils.encoding import force_unicode, StrAndUnicode
+from django.contrib.messages import constants, utils
+
+
+LEVEL_TAGS = utils.get_level_tags()
+
+
+class Message(StrAndUnicode):
+    """
+    Represents an actual message that can be stored in any of the supported 
+    storage classes (typically session- or cookie-based) and rendered in a view 
+    or template.
+    """
+
+    def __init__(self, level, message, extra_tags=None):
+        self.level = int(level)
+        self.message = message
+        self.extra_tags = extra_tags
+
+    def _prepare(self):
+        """
+        Prepares the message for serialization by forcing the ``message``
+        and ``extra_tags`` to unicode in case they are lazy translations.
+        
+        Known "safe" types (None, int, etc.) are not converted (see Django's
+        ``force_unicode`` implementation for details).
+        """
+        self.message = force_unicode(self.message, strings_only=True)
+        self.extra_tags = force_unicode(self.extra_tags, strings_only=True)
+
+    def __unicode__(self):
+        return force_unicode(self.message)
+
+    def _get_tags(self):
+        label_tag = force_unicode(LEVEL_TAGS.get(self.level, ''),
+                                  strings_only=True)
+        extra_tags = force_unicode(self.extra_tags, strings_only=True)
+        if extra_tags:
+            if label_tag:
+                return u'%s %s' % (extra_tags, label_tag)
+            return extra_tags
+        return label_tag or ''
+
+    tags = property(_get_tags)
+
+
+class BaseStorage(object):
+    """
+    This is the base backend for temporary message storage.
+    
+    This is not a complete class; to be a usable storage backend, it must be
+    subclassed and the two methods ``_get`` and ``_store`` overridden.
+    """
+    store_serialized = True
+
+    def __init__(self, request, *args, **kwargs):
+        self.request = request
+        self._queued_messages = []
+        self.used = False
+        self.added_new = False
+        super(BaseStorage, self).__init__(*args, **kwargs)
+
+    def __len__(self):
+        return len(self._loaded_messages) + len(self._queued_messages)
+
+    def __iter__(self):
+        self.used = True
+        if self._queued_messages:
+            self._loaded_messages.extend(self._queued_messages)
+            self._queued_messages = []
+        return iter(self._loaded_messages)
+
+    def __contains__(self, item):
+        return item in self._loaded_messages or item in self._queued_messages
+
+    @property
+    def _loaded_messages(self):
+        """
+        Returns a list of loaded messages, retrieving them first if they have
+        not been loaded yet.
+        """
+        if not hasattr(self, '_loaded_data'):
+            messages, all_retrieved = self._get()
+            self._loaded_data = messages or []
+        return self._loaded_data
+
+    def _get(self, *args, **kwargs):
+        """
+        Retrieves a list of stored messages. Returns a tuple of the messages and
+        a flag indicating whether or not all the messages originally intended
+        to be stored in this storage were, in fact, stored and now retrieved.
+        
+        **This method must be implemented by a subclass.**
+        
+        If it is possible to tell if the backend was not used (as opposed to
+        just containing no messages) then ``None`` should be returned.
+        """
+        raise NotImplementedError()
+
+    def _store(self, messages, response, *args, **kwargs):
+        """
+        Stores a list of messages, returning a list of any messages which could
+        not be stored.
+        
+        One type of object must be able to be stored, ``Message``.
+        
+        **This method must be implemented by a subclass.**
+        """
+        raise NotImplementedError()
+
+    def _prepare_messages(self, messages):
+        """
+        Prepares a list of messages for storage.
+        """
+        if self.store_serialized:
+            for message in messages:
+                message._prepare()
+
+    def update(self, response, fail_silently=True):
+        """
+        Stores all unread messages.
+        
+        If the backend has yet to be iterated, previously stored messages will
+        be stored again. Otherwise, only messages added after the last
+        iteration will be stored.
+        """
+        if self.used:
+            self._prepare_messages(self._queued_messages)
+            return self._store(self._queued_messages, response)
+        elif self.added_new:
+            messages = self._loaded_messages + self._queued_messages
+            self._prepare_messages(messages)
+            return self._store(messages, response)
+
+    def add(self, level, message, extra_tags=''):
+        """
+        Queues a message to be stored.
+        
+        The message is only queued if it contained something and its level is
+        not less than the recording level (``self.level``).
+        """
+        if not message:
+            return
+        # Check that the message level is not less than the recording level.
+        level = int(level)
+        if level < self.level:
+            return
+        # Add the message.
+        self.added_new = True
+        message = Message(level, message, extra_tags=extra_tags)
+        self._queued_messages.append(message)
+
+    def debug(self, message, extra_tags=''):
+        """
+        Adds a message with the ``DEBUG`` level.
+        """
+        self.add(constants.DEBUG, message, extra_tags=extra_tags)
+
+    def info(self, message, extra_tags=''):
+        """
+        Adds a message with the ``INFO`` level.
+        """
+        self.add(constants.INFO, message, extra_tags=extra_tags)
+
+    def success(self, message, extra_tags=''):
+        """
+        Adds a message with the ``SUCCESS`` level.
+        """
+        self.add(constants.SUCCESS, message, extra_tags=extra_tags)
+
+    def warning(self, message, extra_tags=''):
+        """
+        Adds a message with the ``WARNING`` level.
+        """
+        self.add(constants.WARNING, message, extra_tags=extra_tags)
+
+    def error(self, message, extra_tags=''):
+        """
+        Adds a message with the ``ERROR`` level.
+        """
+        self.add(constants.ERROR, message, extra_tags=extra_tags)
+
+    def _get_level(self):
+        """
+        Returns the minimum recorded level.
+        
+        The default level is the ``MESSAGE_LEVEL`` setting. If this is
+        not found, the ``INFO`` level is used.
+        """
+        if not hasattr(self, '_level'):
+            self._level = getattr(settings, 'MESSAGE_LEVEL', constants.INFO)
+        return self._level
+
+    def _set_level(self, value=None):
+        """
+        Sets a custom minimum recorded level.
+        
+        If set to ``None``, the default level will be used (see the
+        ``_get_level`` method).
+        """
+        if value is None and hasattr(self, '_level'):
+            del self._level
+        else:
+            self._level = int(value)
+
+    level = property(_get_level, _set_level, _set_level)
diff -r ed90e13d2065 django/contrib/messages/storage/cookie.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/contrib/messages/storage/cookie.py	Tue Nov 17 00:55:09 2009 -0500
@@ -0,0 +1,147 @@
+import hmac
+
+from django.conf import settings
+from django.utils.hashcompat import sha_constructor
+from django.contrib.messages import constants
+from django.contrib.messages.storage.base import BaseStorage, Message
+try:
+    import json   # Available in Python 2.6.
+except ImportError:
+    # Otherwise fall back to simplejson bundled with Django.
+    from django.utils import simplejson as json
+
+
+class MessageEncoder(json.JSONEncoder):
+    """
+    Compactly serializes instances of the ``Message`` class as JSON.
+    """
+    message_key = '__json_message'
+
+    def default(self, obj):
+        if isinstance(obj, Message):
+            message = [self.message_key, obj.level, obj.message]
+            if obj.extra_tags:
+                message.append(obj.extra_tags)
+            return message
+        return super(MessageEncoder, self).default(obj)
+
+
+class MessageDecoder(json.JSONDecoder):
+    """
+    Decodes JSON that includes serialized ``Message`` instances.
+    """
+
+    def process_messages(self, obj):
+        if isinstance(obj, list) and obj:
+            if obj[0] == MessageEncoder.message_key:
+                return Message(*obj[1:])
+            return [self.process_messages(item) for item in obj]
+        if isinstance(obj, dict):
+            return dict([(key, self.process_messages(value))
+                         for key, value in obj.iteritems()])
+        return obj
+
+    def decode(self, s, **kwargs):
+        decoded = super(MessageDecoder, self).decode(s, **kwargs)
+        return self.process_messages(decoded)
+
+
+class CookieStorage(BaseStorage):
+    """
+    Stores messages in a cookie.
+    """
+    cookie_name = 'messages'
+    max_cookie_size = 4096
+    not_finished = '__messagesnotfinished__'
+    
+    def _get(self, *args, **kwargs):
+        """
+        Retrieves a list of messages from the messages cookie.  If the
+        not_finished sentinel value is found at the end of the message list,
+        remove it and return a result indicating that not all messages were
+        retrieved by this storage.
+        """
+        data = self.request.COOKIES.get(self.cookie_name)
+        messages = self._decode(data)
+        all_retrieved = not (messages and messages[-1] == self.not_finished)
+        if messages and not all_retrieved:
+            # remove the sentinel value
+            messages.pop()
+        return messages, all_retrieved
+
+    def _update_cookie(self, encoded_data, response):
+        """
+        Either sets the cookie with the encoded data if there is any data to
+        store, or deletes the cookie.
+        """
+        if encoded_data:
+            response.set_cookie(self.cookie_name, encoded_data)
+        else:
+            response.delete_cookie(self.cookie_name)
+
+    def _store(self, messages, response, remove_oldest=True, *args, **kwargs):
+        """
+        Stores the messages to a cookie, returning a list of any messages which
+        could not be stored.
+        
+        If the encoded data is larger than ``max_cookie_size``, removes
+        messages until the data fits (these are the messages which are
+        returned), and add the not_finished sentinel value to indicate as much.
+        """
+        unstored_messages = []
+        encoded_data = self._encode(messages)
+        if self.max_cookie_size:
+            while encoded_data and len(encoded_data) > self.max_cookie_size:
+                if remove_oldest:
+                    unstored_messages.append(messages.pop(0))
+                else:
+                    unstored_messages.insert(0, messages.pop())
+                encoded_data = self._encode(messages + [self.not_finished],
+                                            encode_empty=unstored_messages)
+        self._update_cookie(encoded_data, response)
+        return unstored_messages
+
+    def _hash(self, value):
+        """
+        Creates an HMAC/SHA1 hash based on the value and the project setting's 
+        SECRET_KEY, modified to make it unique for the present purpose.
+        """
+        key = 'django.contrib.messages' + settings.SECRET_KEY
+        return hmac.new(key, value, sha_constructor).hexdigest()
+    
+    def _encode(self, messages, encode_empty=False):
+        """
+        Returns an encoded version of the messages list which can be stored as
+        plain text.
+        
+        Since the data will be retrieved from the client-side, the encoded data
+        also contains a hash to ensure that the data was not tampered with.
+        """
+        if messages or encode_empty:
+            encoder = MessageEncoder(separators=(',', ':'))
+            value = encoder.encode(messages)
+            return '%s$%s' % (self._hash(value), value)
+
+    def _decode(self, data):
+        """
+        Safely decodes a encoded text stream back into a list of messages.
+        
+        If the encoded text stream contained an invalid hash or was in an
+        invalid format, ``None`` is returned.
+        """
+        if not data:
+            return None
+        bits = data.split('$', 1)
+        if len(bits) == 2:
+            hash, value = bits
+            if hash == self._hash(value):
+                try:
+                    # If we get here (and the JSON decode works), everything is
+                    # good. In any other case, drop back and return None.
+                    return json.loads(value, cls=MessageDecoder)
+                except ValueError:
+                    pass
+        # Mark the data as used (so it gets removed) since something was wrong
+        # with the data.
+        self.used = True
+        return None
diff -r ed90e13d2065 django/contrib/messages/storage/fallback.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/contrib/messages/storage/fallback.py	Tue Nov 17 00:55:09 2009 -0500
@@ -0,0 +1,62 @@
+from django.contrib.messages.storage.base import BaseStorage
+from django.contrib.messages.storage.cookie import CookieStorage
+from django.contrib.messages.storage.session import SessionStorage
+try:
+    set
+except NameError:
+    from sets import Set as set   # Python 2.3
+
+
+class FallbackStorage(BaseStorage):
+    """
+    Tries to store all messages in the first backend, storing any unstored 
+    messages in each subsequent backend backend.
+    """
+    storage_classes = (CookieStorage, SessionStorage)
+
+    def __init__(self, *args, **kwargs):
+        super(FallbackStorage, self).__init__(*args, **kwargs)
+        self.storages = [storage_class(*args, **kwargs)
+                         for storage_class in self.storage_classes]
+        self._used_storages = set()
+
+    def _get(self, *args, **kwargs):
+        """
+        Gets a single list of messages from all storage backends.
+        """
+        all_messages = []
+        for storage in self.storages:
+            messages, all_retrieved = storage._get()
+            # If the backend hasn't been used, no more retrieval is necessary.
+            if messages is None:
+                break
+            if messages:
+                self._used_storages.add(storage)
+            all_messages.extend(messages)
+            # If this storage class contained all the messages, no further
+            # retrieval is necessary
+            if all_retrieved:
+                break
+        return all_messages, all_retrieved
+
+    def _store(self, messages, response, *args, **kwargs):
+        """
+        Stores the messages, returning any unstored messages after trying all
+        backends.
+        
+        For each storage backend, any messages not stored are passed on to the
+        next backend.
+        """
+        for storage in self.storages:
+            if messages:
+                messages = storage._store(
+                    messages, 
+                    response, 
+                    remove_oldest=False,
+                )
+            # Even if there are no more messages, continue iterating to ensure
+            # storages which contained messages are flushed.
+            elif storage in self._used_storages:
+                storage._store([], response)
+                self._used_storages.remove(storage)
+        return messages
diff -r ed90e13d2065 django/contrib/messages/storage/session.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/contrib/messages/storage/session.py	Tue Nov 17 00:55:09 2009 -0500
@@ -0,0 +1,30 @@
+from django.contrib.messages.storage.base import BaseStorage
+
+
+class SessionStorage(BaseStorage):
+    """
+    Stores messages in the session (that is, django.contrib.sessions).
+    """
+    session_key = '_messages'
+
+    def __init__(self, request, *args, **kwargs):
+        assert hasattr(request, 'session'), "The session-based temporary "\
+            "message storage requires session middleware to be installed."
+        super(SessionStorage, self).__init__(request, *args, **kwargs)
+
+    def _get(self, *args, **kwargs):
+        """
+        Retrieves a list of messages from the request's session.  This storage
+        always stores everything it is given, so return True for the
+        all_retrieved flag.
+        """
+        return self.request.session.get(self.session_key), True
+
+    def _store(self, messages, response, *args, **kwargs):
+        """
+        Stores a list of messages to the request's session.
+        """
+        if messages:
+            self.request.session[self.session_key] = messages
+        else:
+            self.request.session.pop(self.session_key, None)
diff -r ed90e13d2065 django/contrib/messages/storage/user_messages.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/contrib/messages/storage/user_messages.py	Tue Nov 17 00:55:09 2009 -0500
@@ -0,0 +1,64 @@
+"""
+Storages used to assist in the deprecation of contrib.auth User messages.
+
+"""
+from django.contrib.messages import constants
+from django.contrib.messages.storage.base import BaseStorage, Message
+from django.contrib.auth.models import User
+from django.contrib.messages.storage.fallback import FallbackStorage
+
+
+class UserMessagesStorage(BaseStorage):
+    """
+    Retrieves messages from the User, using the legacy user.message_set API.
+    
+    This storage is "read-only" insofar as it can only retrieve and delete 
+    messages, not store them.
+    """
+    session_key = '_messages'
+
+    def _get_messages_queryset(self):
+        """
+        Returns the QuerySet containing all user messages (or ``None`` if
+        request.user is not a contrib.auth User).
+        """
+        user = getattr(self.request, 'user', None)
+        if isinstance(user, User):
+            return user.message_set.all()
+
+    def add(self, *args, **kwargs):
+        raise NotImplementedError('This message storage is read-only.')
+
+    def _get(self, *args, **kwargs):
+        """
+        Retrieves a list of messages assigned to the User.  This backend never
+        stores anything, so all_retrieved is assumed to be False.
+        """
+        queryset = self._get_messages_queryset()
+        if queryset is None:
+            # This is a read-only and optional storage, so to ensure other
+            # storages will also be read if used with FallbackStorage an empty
+            # list is returned rather than None.
+            return [], False
+        messages = []
+        for user_message in queryset:
+            messages.append(Message(constants.INFO, user_message.message))
+        return messages, False
+
+    def _store(self, messages, *args, **kwargs):
+        """
+        Removes any messages assigned to the User and returns the list of
+        messages (since no messages are stored in this read-only storage).
+        """
+        queryset = self._get_messages_queryset()
+        if queryset is not None:
+            queryset.delete()
+        return messages
+
+
+class LegacyFallbackStorage(FallbackStorage):
+    """
+    Works like ``FallbackStorage`` but also handles retrieving (and clearing) 
+    contrib.auth User messages.
+    """
+    storage_classes = (UserMessagesStorage,) + FallbackStorage.storage_classes
diff -r ed90e13d2065 django/contrib/messages/tests/__init__.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/contrib/messages/tests/__init__.py	Tue Nov 17 00:55:09 2009 -0500
@@ -0,0 +1,6 @@
+from django.contrib.messages.tests.cookie import CookieTest
+from django.contrib.messages.tests.fallback import FallbackTest
+from django.contrib.messages.tests.middleware import MiddlewareTest
+from django.contrib.messages.tests.session import SessionTest
+from django.contrib.messages.tests.user_messages import UserMessagesTest, \
+    LegacyFallbackTest
diff -r ed90e13d2065 django/contrib/messages/tests/base.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/contrib/messages/tests/base.py	Tue Nov 17 00:55:09 2009 -0500
@@ -0,0 +1,211 @@
+from django.contrib.messages import constants
+import unittest
+from django import http
+from django.conf import settings
+from django.utils.translation import ugettext_lazy
+from django.contrib.messages import utils
+from django.contrib.messages.storage import Storage, base
+from django.contrib.messages.storage.base import Message
+
+
+def add_level_messages(storage):
+    """
+    Adds 6 messages from different levels (including a custom one) to a storage
+    instance.
+    """
+    storage.add(constants.INFO, 'A generic info message')
+    storage.add(29, 'Some custom level')
+    storage.debug('A debugging message', extra_tags='extra-tag')
+    storage.warning('A warning')
+    storage.error('An error')
+    storage.success('This was a triumph.')
+
+
+class BaseTest(unittest.TestCase):
+    storage_class = Storage
+    restore_settings = ['MESSAGE_LEVEL', 'MESSAGE_TAGS']
+
+    def setUp(self):
+        self._remembered_settings = {}
+        for setting in self.restore_settings:
+            if hasattr(settings, setting):
+                self._remembered_settings[setting] = getattr(settings, setting)
+                delattr(settings._wrapped, setting)
+
+    def tearDown(self):
+        for setting in self.restore_settings:
+            self.restore_setting(setting)
+
+    def restore_setting(self, setting):
+        if setting in self._remembered_settings:
+            value = self._remembered_settings.pop(setting)
+            setattr(settings, setting, value)
+        elif hasattr(settings, setting):
+            delattr(settings._wrapped, setting)
+
+    def get_request(self):
+        return http.HttpRequest()
+
+    def get_response(self):
+        return http.HttpResponse()
+
+    def get_storage(self, data=None):
+        """
+        Returns the storage backend, setting its loaded data to the ``data``
+        argument.
+        
+        This method avoids the storage ``_get`` method from getting called so
+        that other parts of the storage backend can be tested independent of
+        the message retrieval logic.
+        """
+        storage = self.storage_class(self.get_request())
+        storage._loaded_data = data or []
+        return storage
+
+    def test_add(self):
+        storage = self.get_storage()
+        self.assertFalse(storage.added_new)
+        storage.add(constants.INFO, 'Test message 1')
+        self.assert_(storage.added_new)
+        storage.add(constants.INFO, 'Test message 2', extra_tags='tag')
+        self.assertEqual(len(storage), 2)
+
+    def test_add_lazy_translation(self):
+        storage = self.get_storage()
+        response = self.get_response()
+
+        storage.add(constants.INFO, ugettext_lazy('lazy message'))
+        storage.update(response)
+
+        storing = self.stored_messages_count(storage, response)
+        self.assertEqual(storing, 1)
+
+    def test_no_update(self):
+        storage = self.get_storage()
+        response = self.get_response()
+        storage.update(response)
+        storing = self.stored_messages_count(storage, response)
+        self.assertEqual(storing, 0)
+
+    def test_add_update(self):
+        storage = self.get_storage()
+        response = self.get_response()
+
+        storage.add(constants.INFO, 'Test message 1')
+        storage.add(constants.INFO, 'Test message 1', extra_tags='tag')
+        storage.update(response)
+
+        storing = self.stored_messages_count(storage, response)
+        self.assertEqual(storing, 2)
+
+    def test_existing_add_read_update(self):
+        storage = self.get_existing_storage()
+        response = self.get_response()
+
+        storage.add(constants.INFO, 'Test message 3')
+        list(storage)   # Simulates a read
+        storage.update(response)
+
+        storing = self.stored_messages_count(storage, response)
+        self.assertEqual(storing, 0)
+
+    def test_existing_read_add_update(self):
+        storage = self.get_existing_storage()
+        response = self.get_response()
+
+        list(storage)   # Simulates a read
+        storage.add(constants.INFO, 'Test message 3')
+        storage.update(response)
+
+        storing = self.stored_messages_count(storage, response)
+        self.assertEqual(storing, 1)
+
+    def stored_messages_count(self, storage, response):
+        """
+        Returns the number of messages being stored after a
+        ``storage.update()`` call.
+        """
+        raise NotImplementedError('This method must be set by a subclass.')
+
+    def test_get(self):
+        raise NotImplementedError('This method must be set by a subclass.')
+
+    def get_existing_storage(self):
+        return self.get_storage([Message(constants.INFO, 'Test message 1'),
+                                 Message(constants.INFO, 'Test message 2',
+                                              extra_tags='tag')])
+
+    def test_existing_read(self):
+        """
+        Tests that reading the existing storage doesn't cause the data to be 
+        lost.
+        """
+        storage = self.get_existing_storage()
+        self.assertFalse(storage.used)
+        # After iterating the storage engine directly, the used flag is set.
+        data = list(storage)
+        self.assert_(storage.used)
+        # The data does not disappear because it has been iterated.
+        self.assertEqual(data, list(storage))
+
+    def test_existing_add(self):
+        storage = self.get_existing_storage()
+        self.assertFalse(storage.added_new)
+        storage.add(constants.INFO, 'Test message 3')
+        self.assert_(storage.added_new)
+
+    def test_default_level(self):
+        storage = self.get_storage()
+        add_level_messages(storage)
+        self.assertEqual(len(storage), 5)
+
+    def test_low_level(self):
+        storage = self.get_storage()
+        storage.level = 5
+        add_level_messages(storage)
+        self.assertEqual(len(storage), 6)
+
+    def test_high_level(self):
+        storage = self.get_storage()
+        storage.level = 30
+        add_level_messages(storage)
+        self.assertEqual(len(storage), 2)
+
+    def test_settings_level(self):
+        settings.MESSAGE_LEVEL = 29
+        storage = self.get_storage()
+        add_level_messages(storage)
+        self.assertEqual(len(storage), 3)
+
+    def test_tags(self):
+        storage = self.get_storage()
+        storage.level = 0
+        add_level_messages(storage)
+        tags = [msg.tags for msg in storage]
+        self.assertEqual(tags,
+                         ['info', '', 'extra-tag debug', 'warning', 'error',
+                          'success'])
+
+    def test_custom_tags(self):
+        settings.MESSAGE_TAGS = {
+            constants.INFO: 'info',
+            constants.DEBUG: '',
+            constants.WARNING: '',
+            constants.ERROR: 'bad',
+            29: 'custom',
+        }
+        # LEVEL_TAGS is a constant defined in the
+        # django.contrib.messages.storage.base module, so after changing
+        # settings.MESSAGE_TAGS, we need to update that constant too.
+        base.LEVEL_TAGS = utils.get_level_tags()
+        try:
+            storage = self.get_storage()
+            storage.level = 0
+            add_level_messages(storage)
+            tags = [msg.tags for msg in storage]
+            self.assertEqual(tags,
+                         ['info', 'custom', 'extra-tag', '', 'bad', 'success'])
+        finally:
+            # Ensure the level tags constant is put back like we found it.
+            self.restore_setting('MESSAGE_TAGS')
+            base.LEVEL_TAGS = utils.get_level_tags()
diff -r ed90e13d2065 django/contrib/messages/tests/cookie.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/contrib/messages/tests/cookie.py	Tue Nov 17 00:55:09 2009 -0500
@@ -0,0 +1,77 @@
+from django.contrib.messages import constants
+from django.contrib.messages.tests.base import BaseTest
+from django.contrib.messages.storage.cookie import CookieStorage
+
+
+def set_cookie_data(storage, messages, invalid=False, encode_empty=False):
+    """
+    Sets ``request.COOKIES`` with the encoded data and removes the storage
+    backend's loaded data cache.
+    """
+    encoded_data = storage._encode(messages, encode_empty=encode_empty)
+    if invalid:
+        # Truncate the first character so that the hash is invalid.
+        encoded_data = encoded_data[1:]
+    storage.request.COOKIES = {CookieStorage.cookie_name: encoded_data}
+    if hasattr(storage, '_loaded_data'):
+        del storage._loaded_data
+
+
+def stored_cookie_messages_count(storage, response):
+    """
+    Returns an integer containing the number of messages stored.
+    """
+    # Get a list of cookies, excluding ones with a max-age of 0 (because
+    # they have been marked for deletion).
+    cookie = response.cookies.get(storage.cookie_name)
+    if not cookie or cookie['max-age'] == 0:
+        return 0
+    data = storage._decode(cookie.value)
+    if not data:
+        return 0
+    if data[-1] == CookieStorage.not_finished:
+        data.pop()
+    return len(data)
+
+
+class CookieTest(BaseTest):
+    storage_class = CookieStorage
+
+    def stored_messages_count(self, storage, response):
+        return stored_cookie_messages_count(storage, response)
+
+    def test_get(self):
+        storage = self.storage_class(self.get_request())
+        # Set initial data.
+        example_messages = ['test', 'me']
+        set_cookie_data(storage, example_messages)
+        # Test that the message actually contains what we expect.
+        self.assertEqual(list(storage), example_messages)
+
+    def test_get_bad_cookie(self):
+        request = self.get_request()
+        storage = self.storage_class(request)
+        # Set initial (invalid) data.
+        example_messages = ['test', 'me']
+        set_cookie_data(storage, example_messages, invalid=True)
+        # Test that the message actually contains what we expect.
+        self.assertEqual(list(storage), [])
+
+    def test_max_cookie_length(self):
+        """
+        Tests that, if the data exceeds what is allowed in a cookie, older 
+        messages are removed before saving (and returned by the ``update`` 
+        method).
+        """
+        storage = self.get_storage()
+        response = self.get_response()
+
+        for i in range(5):
+            storage.add(constants.INFO, str(i) * 900)
+        unstored_messages = storage.update(response)
+
+        cookie_storing = self.stored_messages_count(storage, response)
+        self.assertEqual(cookie_storing, 4)
+
+        self.assertEqual(len(unstored_messages), 1)
+        self.assert_(unstored_messages[0].message == '0' * 900)
diff -r ed90e13d2065 django/contrib/messages/tests/fallback.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/contrib/messages/tests/fallback.py	Tue Nov 17 00:55:09 2009 -0500
@@ -0,0 +1,173 @@
+from django.contrib.messages import constants
+from django.contrib.messages.storage.fallback import FallbackStorage, \
+    CookieStorage
+from django.contrib.messages.tests.base import BaseTest
+from django.contrib.messages.tests.cookie import set_cookie_data, \
+    stored_cookie_messages_count
+from django.contrib.messages.tests.session import set_session_data, \
+    stored_session_messages_count
+
+
+class FallbackTest(BaseTest):
+    storage_class = FallbackStorage
+
+    def get_request(self):
+        self.session = {}
+        request = super(FallbackTest, self).get_request()
+        request.session = self.session
+        return request
+
+    def get_cookie_storage(self, storage):
+        return storage.storages[-2]
+
+    def get_session_storage(self, storage):
+        return storage.storages[-1]
+
+    def stored_cookie_messages_count(self, storage, response):
+        return stored_cookie_messages_count(self.get_cookie_storage(storage),
+                                            response)
+
+    def stored_session_messages_count(self, storage, response):
+        return stored_session_messages_count(self.get_session_storage(storage))
+
+    def stored_messages_count(self, storage, response):
+        """
+        Return the storage totals from both cookie and session backends.
+        """
+        total = (self.stored_cookie_messages_count(storage, response) +
+                 self.stored_session_messages_count(storage, response))
+        return total
+
+    def test_get(self):
+        request = self.get_request()
+        storage = self.storage_class(request)
+        cookie_storage = self.get_cookie_storage(storage)
+
+        # Set initial cookie data.
+        example_messages = [str(i) for i in range(5)]
+        set_cookie_data(cookie_storage, example_messages)
+
+        # Overwrite the _get method of the fallback storage to prove it is not
+        # used (it would cause a TypeError: 'NoneType' object is not callable).
+        self.get_session_storage(storage)._get = None
+
+        # Test that the message actually contains what we expect.
+        self.assertEqual(list(storage), example_messages)
+
+    def test_get_empty(self):
+        request = self.get_request()
+        storage = self.storage_class(request)
+
+        # Overwrite the _get method of the fallback storage to prove it is not
+        # used (it would cause a TypeError: 'NoneType' object is not callable).
+        self.get_session_storage(storage)._get = None
+
+        # Test that the message actually contains what we expect.
+        self.assertEqual(list(storage), [])
+
+    def test_get_fallback(self):
+        request = self.get_request()
+        storage = self.storage_class(request)
+        cookie_storage = self.get_cookie_storage(storage)
+        session_storage = self.get_session_storage(storage)
+
+        # Set initial cookie and session data.
+        example_messages = [str(i) for i in range(5)]
+        set_cookie_data(cookie_storage, example_messages[:4] + 
+                        [CookieStorage.not_finished])
+        set_session_data(session_storage, example_messages[4:])
+
+        # Test that the message actually contains what we expect.
+        self.assertEqual(list(storage), example_messages)
+
+    def test_get_fallback_only(self):
+        request = self.get_request()
+        storage = self.storage_class(request)
+        cookie_storage = self.get_cookie_storage(storage)
+        session_storage = self.get_session_storage(storage)
+
+        # Set initial cookie and session data.
+        example_messages = [str(i) for i in range(5)]
+        set_cookie_data(cookie_storage, [CookieStorage.not_finished], 
+                        encode_empty=True)
+        set_session_data(session_storage, example_messages)
+
+        # Test that the message actually contains what we expect.
+        self.assertEqual(list(storage), example_messages)
+
+    def test_flush_used_backends(self):
+        request = self.get_request()
+        storage = self.storage_class(request)
+        cookie_storage = self.get_cookie_storage(storage)
+        session_storage = self.get_session_storage(storage)
+
+        # Set initial cookie and session data.
+        set_cookie_data(cookie_storage, ['cookie', CookieStorage.not_finished])
+        set_session_data(session_storage, ['session'])
+
+        # When updating, previously used but no longer needed backends are
+        # flushed.
+        response = self.get_response()
+        list(storage)
+        storage.update(response)
+        session_storing = self.stored_session_messages_count(storage, response)
+        self.assertEqual(session_storing, 0)
+
+    def test_no_fallback(self):
+        """
+        Confirms that:
+        
+        (1) A short number of messages whose data size doesn't exceed what is
+        allowed in a cookie will all be stored in the CookieBackend.
+        
+        (2) If the CookieBackend can store all messages, the SessionBackend 
+        won't be written to at all.
+        """
+        storage = self.get_storage()
+        response = self.get_response()
+
+        # Overwrite the _store method of the fallback storage to prove it isn't
+        # used (it would cause a TypeError: 'NoneType' object is not callable).
+        self.get_session_storage(storage)._store = None
+
+        for i in range(5):
+            storage.add(constants.INFO, str(i) * 100)
+        storage.update(response)
+
+        cookie_storing = self.stored_cookie_messages_count(storage, response)
+        self.assertEqual(cookie_storing, 5)
+        session_storing = self.stored_session_messages_count(storage, response)
+        self.assertEqual(session_storing, 0)
+
+    def test_session_fallback(self):
+        """
+        Confirms that, if the data exceeds what is allowed in a cookie, older 
+        messages which did not "fit" are stored in the SessionBackend.
+        """
+        storage = self.get_storage()
+        response = self.get_response()
+
+        for i in range(5):
+            storage.add(constants.INFO, str(i) * 900)
+        storage.update(response)
+
+        cookie_storing = self.stored_cookie_messages_count(storage, response)
+        self.assertEqual(cookie_storing, 4)
+        session_storing = self.stored_session_messages_count(storage, response)
+        self.assertEqual(session_storing, 1)
+
+    def test_session_fallback_only(self):
+        """
+        Confirms that large messages, none of which fit in a cookie, are stored 
+        in the SessionBackend (and nothing is stored in the CookieBackend).
+        """
+        storage = self.get_storage()
+        response = self.get_response()
+
+        storage.add(constants.INFO, 'x' * 5000)
+        storage.update(response)
+
+        cookie_storing = self.stored_cookie_messages_count(storage, response)
+        self.assertEqual(cookie_storing, 0)
+        session_storing = self.stored_session_messages_count(storage, response)
+        self.assertEqual(session_storing, 1)
diff -r ed90e13d2065 django/contrib/messages/tests/middleware.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/contrib/messages/tests/middleware.py	Tue Nov 17 00:55:09 2009 -0500
@@ -0,0 +1,18 @@
+import unittest
+from django import http
+from django.contrib.messages.middleware import MessageMiddleware
+
+
+class MiddlewareTest(unittest.TestCase):
+
+    def setUp(self):
+        self.middleware = MessageMiddleware()
+
+    def test_response_without_messages(self):
+        """
+        Makes sure that the response middleware is tolerant of messages not 
+        existing on request.
+        """
+        request = http.HttpRequest()
+        response = http.HttpResponse()
+        self.middleware.process_response(request, response)
diff -r ed90e13d2065 django/contrib/messages/tests/session.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/contrib/messages/tests/session.py	Tue Nov 17 00:55:09 2009 -0500
@@ -0,0 +1,38 @@
+from django.contrib.messages.tests.base import BaseTest
+from django.contrib.messages.storage.session import SessionStorage
+
+
+def set_session_data(storage, messages):
+    """
+    Sets the messages into the backend request's session and remove the
+    backend's loaded data cache.
+    """
+    storage.request.session[storage.session_key] = messages
+    if hasattr(storage, '_loaded_data'):
+        del storage._loaded_data
+
+
+def stored_session_messages_count(storage):
+    data = storage.request.session.get(storage.session_key, [])
+    return len(data)
+
+
+class SessionTest(BaseTest):
+    storage_class = SessionStorage
+
+    def get_request(self):
+        self.session = {}
+        request = super(SessionTest, self).get_request()
+        request.session = self.session
+        return request
+
+    def stored_messages_count(self, storage, response):
+        return stored_session_messages_count(storage)
+
+    def test_get(self):
+        storage = self.storage_class(self.get_request())
+        # Set initial data.
+        example_messages = ['test', 'me']
+        set_session_data(storage, example_messages)
+        # Test that the message actually contains what we expect.
+        self.assertEqual(list(storage), example_messages)
diff -r ed90e13d2065 django/contrib/messages/tests/user_messages.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/contrib/messages/tests/user_messages.py	Tue Nov 17 00:55:09 2009 -0500
@@ -0,0 +1,65 @@
+from django import http
+from django.contrib.auth.models import User
+from django.contrib.messages.storage.user_messages import UserMessagesStorage,\
+    LegacyFallbackStorage
+from django.contrib.messages.tests.cookie import set_cookie_data
+from django.contrib.messages.tests.fallback import FallbackTest
+from django.test import TestCase
+
+
+class UserMessagesTest(TestCase):
+
+    def setUp(self):
+        self.user = User.objects.create(username='tester')
+    
+    def test_add(self):
+        storage = UserMessagesStorage(http.HttpRequest())
+        self.assertRaises(NotImplementedError, storage.add, 'Test message 1')
+
+    def test_get_anonymous(self):
+        # Ensure that the storage still works if no user is attached to the
+        # request.
+        storage = UserMessagesStorage(http.HttpRequest())
+        self.assertEqual(len(storage), 0)
+
+    def test_get(self):
+        storage = UserMessagesStorage(http.HttpRequest())
+        storage.request.user = self.user
+        self.user.message_set.create(message='test message')
+
+        self.assertEqual(len(storage), 1)
+        self.assertEqual(list(storage)[0].message, 'test message')
+
+
+class LegacyFallbackTest(FallbackTest, TestCase):
+    storage_class = LegacyFallbackStorage
+
+    def setUp(self):
+        super(LegacyFallbackTest, self).setUp()
+        self.user = User.objects.create(username='tester')
+        
+    def get_request(self, *args, **kwargs):
+        request = super(LegacyFallbackTest, self).get_request(*args, **kwargs)
+        request.user = self.user
+        return request
+
+    def test_get_legacy_only(self):
+        request = self.get_request()
+        storage = self.storage_class(request)
+        self.user.message_set.create(message='user message')
+
+        # Test that the message actually contains what we expect.
+        self.assertEqual(len(storage), 1)
+        self.assertEqual(list(storage)[0].message, 'user message')
+
+    def test_get_legacy(self):
+        request = self.get_request()
+        storage = self.storage_class(request)
+        cookie_storage = self.get_cookie_storage(storage)
+        self.user.message_set.create(message='user message')
+        set_cookie_data(cookie_storage, ['cookie'])
+
+        # Test that the message actually contains what we expect.
+        self.assertEqual(len(storage), 2)
+        self.assertEqual(list(storage)[0].message, 'user message')
+        self.assertEqual(list(storage)[1], 'cookie')
diff -r ed90e13d2065 django/contrib/messages/utils.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/contrib/messages/utils.py	Tue Nov 17 00:55:09 2009 -0500
@@ -0,0 +1,11 @@
+from django.conf import settings
+from django.contrib.messages import constants 
+
+
+def get_level_tags():
+    """
+    Returns the message level tags.
+    """
+    level_tags = constants.DEFAULT_TAGS.copy()
+    level_tags.update(getattr(settings, 'MESSAGE_TAGS', {}))
+    return level_tags
diff -r ed90e13d2065 django/core/context_processors.py
--- a/django/core/context_processors.py	Tue Nov 17 02:13:26 2009 +0000
+++ b/django/core/context_processors.py	Tue Nov 17 00:55:09 2009 -0500
@@ -10,6 +10,7 @@
 from django.conf import settings
 from django.middleware.csrf import get_token
 from django.utils.functional import lazy, memoize, SimpleLazyObject
+from django.contrib.messages.compat import compat_get_messages
 
 def auth(request):
     """
@@ -37,7 +38,7 @@
 
     return {
         'user': SimpleLazyObject(get_user),
-        'messages': lazy(memoize(lambda: get_user().get_and_delete_messages(), {}, 0), list)(),
+        'messages': compat_get_messages(request),
         'perms':  lazy(lambda: PermWrapper(get_user()), PermWrapper)(),
     }
 
diff -r ed90e13d2065 django/core/files/storage.py
--- a/django/core/files/storage.py	Tue Nov 17 02:13:26 2009 +0000
+++ b/django/core/files/storage.py	Tue Nov 17 00:55:09 2009 -0500
@@ -8,7 +8,7 @@
 from django.core.files.move import file_move_safe
 from django.utils.encoding import force_unicode, smart_str
 from django.utils.functional import LazyObject
-from django.utils.importlib import import_module
+from django.utils.importcls import get_class
 from django.utils.text import get_valid_filename
 from django.utils._os import safe_join
 
@@ -221,19 +221,7 @@
 def get_storage_class(import_path=None):
     if import_path is None:
         import_path = settings.DEFAULT_FILE_STORAGE
-    try:
-        dot = import_path.rindex('.')
-    except ValueError:
-        raise ImproperlyConfigured("%s isn't a storage module." % import_path)
-    module, classname = import_path[:dot], import_path[dot+1:]
-    try:
-        mod = import_module(module)
-    except ImportError, e:
-        raise ImproperlyConfigured('Error importing storage module %s: "%s"' % (module, e))
-    try:
-        return getattr(mod, classname)
-    except AttributeError:
-        raise ImproperlyConfigured('Storage module "%s" does not define a "%s" class.' % (module, classname))
+    return get_class(import_path)
 
 class DefaultStorage(LazyObject):
     def _setup(self):
diff -r ed90e13d2065 django/utils/importcls.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/utils/importcls.py	Tue Nov 17 00:55:09 2009 -0500
@@ -0,0 +1,21 @@
+from django.utils.importlib import import_module
+
+
+def get_class(import_path):
+    """
+    Imports the class described by import_path, where import_path is the full 
+    Python path to the class.
+    """
+    try:
+        dot = import_path.rindex('.')
+    except ValueError:
+        raise ImproperlyConfigured("%s isn't a Python path." % import_path)
+    module, classname = import_path[:dot], import_path[dot+1:]
+    try:
+        mod = import_module(module)
+    except ImportError, e:
+        raise ImproperlyConfigured('Error importing module %s: "%s"' % (module, e))
+    try:
+        return getattr(mod, classname)
+    except AttributeError:
+        raise ImproperlyConfigured('Module "%s" does not define a "%s" class.' % (module, classname))
diff -r ed90e13d2065 django/views/generic/create_update.py
--- a/django/views/generic/create_update.py	Tue Nov 17 02:13:26 2009 +0000
+++ b/django/views/generic/create_update.py	Tue Nov 17 00:55:09 2009 -0500
@@ -6,6 +6,8 @@
 from django.utils.translation import ugettext
 from django.contrib.auth.views import redirect_to_login
 from django.views.generic import GenericViewError
+from django.contrib import messages
+from django.contrib.messages.compat import compat_add_message
 
 
 def apply_extra_context(extra_context, context):
@@ -110,8 +112,8 @@
         form = form_class(request.POST, request.FILES)
         if form.is_valid():
             new_object = form.save()
-            if request.user.is_authenticated():
-                request.user.message_set.create(message=ugettext("The %(verbose_name)s was created successfully.") % {"verbose_name": model._meta.verbose_name})
+            
+            compat_add_message(request, messages.SUCCESS, ugettext("The %(verbose_name)s was created successfully.") % {"verbose_name": model._meta.verbose_name})
             return redirect(post_save_redirect, new_object)
     else:
         form = form_class()
@@ -152,8 +154,7 @@
         form = form_class(request.POST, request.FILES, instance=obj)
         if form.is_valid():
             obj = form.save()
-            if request.user.is_authenticated():
-                request.user.message_set.create(message=ugettext("The %(verbose_name)s was updated successfully.") % {"verbose_name": model._meta.verbose_name})
+            compat_add_message(request, messages.SUCCESS, ugettext("The %(verbose_name)s was updated successfully.") % {"verbose_name": model._meta.verbose_name})
             return redirect(post_save_redirect, obj)
     else:
         form = form_class(instance=obj)
@@ -194,8 +195,7 @@
 
     if request.method == 'POST':
         obj.delete()
-        if request.user.is_authenticated():
-            request.user.message_set.create(message=ugettext("The %(verbose_name)s was deleted.") % {"verbose_name": model._meta.verbose_name})
+        compat_add_message(request, messages.SUCCESS, ugettext("The %(verbose_name)s was deleted.") % {"verbose_name": model._meta.verbose_name})
         return HttpResponseRedirect(post_delete_redirect)
     else:
         if not template_name:
diff -r ed90e13d2065 docs/index.txt
--- a/docs/index.txt	Tue Nov 17 02:13:26 2009 +0000
+++ b/docs/index.txt	Tue Nov 17 00:55:09 2009 -0500
@@ -170,6 +170,7 @@
     * :ref:`Internationalization <topics-i18n>`
     * :ref:`Jython support <howto-jython>`
     * :ref:`"Local flavor" <ref-contrib-localflavor>`
+    * :ref:`Messages <ref-contrib-messages>`
     * :ref:`Pagination <topics-pagination>`
     * :ref:`Redirects <ref-contrib-redirects>`
     * :ref:`Serialization <topics-serialization>`
diff -r ed90e13d2065 docs/internals/deprecation.txt
--- a/docs/internals/deprecation.txt	Tue Nov 17 02:13:26 2009 +0000
+++ b/docs/internals/deprecation.txt	Tue Nov 17 00:55:09 2009 -0500
@@ -28,6 +28,12 @@
         * The many to many SQL generation functions on the database backends
           will be removed.  These have been deprecated since the 1.2 release.
 
+        * The ``Message`` model (in ``django.contrib.auth``) and the 
+          associated methods (such as ``user.message_set.create``), which have
+          been deprecated since the 1.2 release, will be removed.  The 
+          :ref:`messages framework <ref-contrib-messages>` should be used 
+          instead.
+
     * 2.0
         * ``django.views.defaults.shortcut()``. This function has been moved
           to ``django.contrib.contenttypes.views.shortcut()`` as part of the
diff -r ed90e13d2065 docs/ref/contrib/index.txt
--- a/docs/ref/contrib/index.txt	Tue Nov 17 02:13:26 2009 +0000
+++ b/docs/ref/contrib/index.txt	Tue Nov 17 00:55:09 2009 -0500
@@ -34,6 +34,7 @@
    formtools/index
    humanize
    localflavor
+   messages
    redirects
    sitemaps
    sites
@@ -150,6 +151,17 @@
 .. _Markdown: http://en.wikipedia.org/wiki/Markdown
 .. _ReST (ReStructured Text): http://en.wikipedia.org/wiki/ReStructuredText
 
+messages
+========
+
+.. versionchanged:: 1.2
+    The messages framework was added.
+
+A framework for storing and retrieving temporary cookie- or session-based
+messages
+
+See the :ref:`messages documentation <ref-contrib-messages>`.
+
 redirects
 =========
 
diff -r ed90e13d2065 docs/ref/contrib/messages.txt
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/ref/contrib/messages.txt	Tue Nov 17 00:55:09 2009 -0500
@@ -0,0 +1,339 @@
+.. _ref-contrib-messages:
+
+======================
+The messages framework
+======================
+
+.. module:: django.contrib.messages
+   :synopsis: Provides cookie- and session-based temporary message storage.
+
+Django provides full support for cookie- and session-based messaging, for
+both anonymous and authenticated clients. The message framework allows you
+to temporarily store messages in one request and retrieve them for display
+in a subsequent request (usually the next one). Every message is tagged
+with a specific ``level`` that determines its priority (e.g., ``info``, 
+``warning``, or ``error``).
+
+.. versionadded:: 1.2
+   The messages framework was added.
+
+Enabling messages
+=================
+
+Messages are implemented through a :ref:`middleware <ref-middleware>`
+class and corresponding :ref:`context processor <ref-templates-api>`.
+
+To enable message functionality, do the following:
+
+    * Edit the :setting:`MIDDLEWARE_CLASSES` setting and make sure
+      :setting:`MIDDLEWARE_CLASSES` contains ``'django.contrib.messages.middleware.MessageMiddleware'``.
+
+    * Edit the :setting:`TEMPLATE_CONTEXT_PROCESSORS` setting and make sure
+      :setting:`TEMPLATE_CONTEXT_PROCESSORS` contains ``'django.contrib.messages.context_processors.messages'``.
+
+    * Add ``'django.contrib.messages'`` to your :setting:`INSTALLED_APPS` setting
+
+The default ``settings.py`` created by ``django-admin.py startproject`` has
+``MessageMiddleware`` activated and the ``django.contrib.messages`` app
+installed.  Also, the default value for :setting:`TEMPLATE_CONTEXT_PROCESSORS` 
+contains ``'django.contrib.messages.context_processors.messages'``.
+
+If you don't want to use messages, you can remove the
+``MessageMiddleware`` line from :setting:`MIDDLEWARE_CLASSES`, the ``messages`` 
+context processor from :setting:`TEMPLATE_CONTEXT_PROCESSORS` and 
+``'django.contrib.messages'`` from your :setting:`INSTALLED_APPS`. 
+
+Configuring the message engine
+==============================
+
+By default, Django first tries to store messages in a lightweight cookie,
+and falls back to storing any messages that don't fit in a session variable.
+
+Storage backends
+----------------
+
+The messages framework can use different backends to store temporary messages. 
+To change which backend is being used, add a `MESSAGE_STORAGE`_ to your 
+settings, referencing the module and class of the storage class. For 
+example::
+
+    MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
+
+The value should be the full path of the desired storage class.
+
+Four storage classes are included:
+
+``'django.contrib.messages.storage.session.SessionStorage'``
+    This class stores all messages inside of the request's session. It
+    requires Django's ``contrib.session`` application.
+
+``'django.contrib.messages.storage.cookie.CookieStorage'``
+    This class stores the message data in a cookie (signed with a secret hash to 
+    prevent manipulation) to persist notifications across requests. Old messages 
+    are dropped if the cookie data size would exceed 4096 bytes.
+
+``'django.contrib.messages.storage.fallback.FallbackStorage'``
+    This class first uses CookieStorage for all messages, falling back to using 
+    SessionStorage for the messages that could not fit in a single cookie.
+
+    Since it is uses SessionStorage, it also requires Django's
+    ``contrib.session`` application. 
+
+``'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'``
+    This is the default temporary storage class.
+
+    This class extends FallbackStorage and adds compatibility methods to
+    to retrieve any messages stored in the user Message model by code that
+    has not yet been updated to use the new API. This storage is temporary
+    (because it makes use of code that is pending deprecation) and will be 
+    removed in Django 1.4. At that time, the default storage will become
+    ``django.contrib.messages.storage.fallback.FallbackStorage``. For more
+    information, see `LegacyFallbackStorage`_ below.
+
+To write your own storage class, subclass the ``BaseStorage`` class in
+``django.contrib.messages.storage.base`` and implement the ``_get`` and 
+``_store`` methods.
+
+LegacyFallbackStorage
+^^^^^^^^^^^^^^^^^^^^^
+
+The ``LegacyFallbackStorage`` is a temporary tool to facilitate the transition
+from the deprecated ``user.message_set`` API and will be removed in Django 1.4.
+
+In addition to the functionality in the ``FallbackStorage``, it adds a custom,
+read-only storage class that retrieves messages from the user ``Message``
+model.  Any messages that were stored in the ``Message`` model (e.g., by code
+that has not yet been updated to use the messages framework) will be
+retrieved first, followed those stored in a cookie and session, if any.
+Since messages stored in the ``Message`` model do not have a concept of levels,
+they will be assigned the ``INFO`` level by default.
+
+Existing coding that calls ``user.message_set.create`` will continue to work
+in Django 1.2 and Django 1.3.  In Django 1.2, the method raises a
+``PendingDeprecationWarning`` (which is not shown by default).  In Django 1.3,
+the method will raise a louder ``DeprecationWarning`` stating that the code at 
+fault needs to be updated.  Any code that uses ``user.message_set`` must be 
+updated to use the messages framework before Django 1.4, when the old API will 
+be removed.
+
+
+Message levels
+--------------
+
+The messages framework is based on a configurable level architecture similar
+to that of the Python logging module.  Message levels allow you to group 
+messages by type so they can be filtered or displayed differently in views and 
+templates.
+
+The built-in levels (which can be imported from ``django.contrib.messages`` 
+directly) are:
+
+=========== ========
+Constant    Purpose
+=========== ========
+``DEBUG``   Development-related messages that will be ignored (or removed) in a production deployment 
+``INFO``    Informational messages for the user
+``SUCCESS`` An action was successful, e.g. "Your profile was updated successfully"
+``WARNING`` A failure did not occur but may be imminent
+``ERROR``   An action was **not** successful or some other failure occurred
+=========== ========
+
+The `MESSAGE_LEVEL`_ setting can be used to change the minimum recorded
+level. Attempts to add messages of a level less than this will be ignored.
+
+Message tags
+------------
+
+Message tags are a string representation of the message level plus any
+extra tags that were added directly in the view (see 
+`Adding extra message tags`_ below for more details).  Tags are stored in a
+string and are separated by spaces.  Typically, message tags
+are used as CSS classes to customize message style based on message type. By 
+default, each level has a single tag that's a lowercase version of its own
+constant:
+
+==============  ===========
+Level Constant  Tag
+==============  ===========
+``DEBUG``       ``debug``
+``INFO``        ``info``
+``SUCCESS``     ``success``
+``WARNING``     ``warning``
+``ERROR``       ``error``
+==============  ===========
+
+To change the default tags for a message level (either built-in or custom), 
+set the `MESSAGE_TAGS`_ setting to a dictionary containing the levels 
+you wish to change. As this extends the default tags, you only need to provide 
+tags for the levels you wish to override::
+
+    from django.contrib.messages import constants as messages
+    MESSAGE_TAGS = {
+        messages.INFO: '',
+        50: 'critical',
+    }
+
+Using messages in views and templates
+=====================================
+
+Adding a message
+----------------
+
+The middleware attaches an instance of a temporary storage class called
+``messages`` to your ``request``. To add a message, call::
+
+    request.messages.add(messages.INFO, 'Hello world.')
+
+Some other methods provide a standard way to add messages with commonly
+used tags (which are usually represented as HTML classes for the message)::
+
+    request.messages.debug('%s SQL statements were executed.' % count)
+    request.messages.info('Three credits remain in your account.')
+    request.messages.success('Profile details updated.')
+    request.messages.warning('Your account expires in three days.')
+    request.messages.error('Document deleted.')
+
+Displaying messages
+-------------------
+
+In your template, use something like::
+
+	{% if messages %}
+	<ul class="messages">
+		{% for message in messages %}
+		<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
+		{% endfor %}
+	</ul>
+	{% endif %}
+
+If you're using the context processor, your template should be rendered with a
+``RequestContext``. Otherwise, ensure ``request.messages`` is available to
+the template context.
+
+Creating custom message levels
+------------------------------
+
+Messages levels are nothing more than integers, so you can define your own
+level constants and use them to create more customized user feedback, e.g.::
+
+    CRITICAL = 50
+    
+    def my_view(request):
+        request.messages.add(CRITICAL, 'A serious error occured.')
+
+When creating custom message levels you should be careful to avoid overloading
+existing levels.  The values for the built-in levels are:
+
+==============  =====
+Level Constant  Value
+==============  =====
+``DEBUG``       10
+``INFO``        20
+``SUCCESS``     25
+``WARNING``     30
+``ERROR``       40
+==============  =====
+
+If you need to identify the custom levels in your HTML or CSS, you need to 
+provide a mapping via the `MESSAGE_TAGS`_ setting.
+
+**Note:** If you are creating a reusable application, it is recommended to use
+only the built-in `message levels`_ and not rely on any custom levels.
+
+Changing the minimum recorded level per-request
+-----------------------------------------------
+
+The minimum recorded level can be set per request by changing the ``level`` 
+attribute of the messages storage instance::
+
+    from django.contrib import messages
+    
+    # Change the messages level to ensure the debug message is added.
+    request.messages.level = messages.DEBUG
+    request.messages.debug('Test message...')
+    
+    # In another request, record only messages with a level of WARNING and higher
+    request.messages.level = messages.WARNING
+    request.messages.success('Your profile was updated.') # ignored
+    request.messages.warning('Your account is about to expire.') # recorded
+    
+    # Set the messages level back to default.
+    request.messages.level = None
+
+For more information, on how the minimum recorded level functions, see 
+`Message levels`_ above.
+
+Adding extra message tags
+-------------------------
+
+For more direct control over message tags, you can optionally provide a string
+containing extra tags to any of the add methods::
+
+    request.messages.add(messages.INFO, 'Over 9000!', extra_tags='dragonball')
+    request.messages.error('Email box full', extra_tags='email')
+
+Extra tags are added before the default tag for that level and are space 
+separated.
+
+Expiration of messages
+======================
+
+The messages are marked to be cleared when the storage instance is iterated 
+(and cleared when the response is processed).
+
+To avoid the messages being cleared, you can set
+``request.messages.used = False`` after iterating.
+
+Settings
+========
+
+A few :ref:`Django settings <ref-settings>` give you control over message behavior:
+
+MESSAGE_LEVEL
+-------------
+
+Default: ``messages.INFO``
+
+Important: If you override this in your settings file and rely on any of the built-in 
+constants, you must import the constants module directly to avoid the potential
+for circular imports.
+
+This sets the minimum message that will be saved in the message storage.  See
+`Message levels`_ above for more details.
+
+MESSAGE_STORAGE
+---------------
+
+Default: ``'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'``
+
+Controls where Django stores message data. Valid values are:
+
+    * ``'django.contrib.messages.storage.fallback.FallbackStorage'``
+    * ``'django.contrib.messages.storage.session.SessionStorage'``
+    * ``'django.contrib.messages.storage.cookie.CookieStorage'``
+    * ``'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'``
+
+See `Storage backends`_ for more details.
+
+MESSAGE_TAGS
+------------
+
+Default::
+    
+        {messages.DEBUG: 'debug',
+        messages.INFO: 'info',
+        messages.SUCCESS: 'success',
+        messages.WARNING: 'warning',
+        messages.ERROR: 'error',}
+
+Important: If you override this in your settings file and rely on any of the built-in 
+constants, you must import the constants module directly to avoid the potential
+for circular imports.
+
+This sets the mapping of message level to message tag, which is typically
+rendered as a CSS class in HTML. If you specify a value, it will extend
+the default. This means you only have to specify those values which you need
+to override. See `Displaying messages`_ above for more details.
+
+.. _Django settings: ../settings/
+
diff -r ed90e13d2065 docs/ref/settings.txt
--- a/docs/ref/settings.txt	Tue Nov 17 02:13:26 2009 +0000
+++ b/docs/ref/settings.txt	Tue Nov 17 00:55:09 2009 -0500
@@ -812,6 +812,43 @@
 
 .. setting:: MIDDLEWARE_CLASSES
 
+MESSAGE_LEVEL
+-------------
+
+.. versionadded:: 1.2
+
+Default: `messages.INFO`
+
+Sets the minimum message level that will be recorded by the messages 
+framework. See the :ref:`messages documentation <ref-contrib-messages>` for
+more details.
+
+MESSAGE_STORAGE
+---------------
+
+.. versionadded:: 1.2
+
+Default: ``'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'``
+
+Controls where Django stores message data.  See the
+:ref:`messages documentation <ref-contrib-messages>` for more details.
+
+MESSAGE_TAGS
+------------
+
+.. versionadded:: 1.2
+
+Default::
+
+        {messages.DEBUG: 'debug',
+        messages.INFO: 'info',
+        messages.SUCCESS: 'success',
+        messages.WARNING: 'warning',
+        messages.ERROR: 'error',}
+
+Sets the mapping of message levels to message tags. See the
+:ref:`messages documentation <ref-contrib-messages>` for more details.
+
 MIDDLEWARE_CLASSES
 ------------------
 
@@ -820,7 +857,8 @@
     ('django.middleware.common.CommonMiddleware',
      'django.contrib.sessions.middleware.SessionMiddleware',
      'django.middleware.csrf.CsrfViewMiddleware',
-     'django.contrib.auth.middleware.AuthenticationMiddleware',)
+     'django.contrib.auth.middleware.AuthenticationMiddleware',
+     'django.contrib.messages.middleware.MessageMiddleware',)
 
 A tuple of middleware classes to use. See :ref:`topics-http-middleware`.
 
@@ -1059,7 +1097,8 @@
     ("django.core.context_processors.auth",
     "django.core.context_processors.debug",
     "django.core.context_processors.i18n",
-    "django.core.context_processors.media")
+    "django.core.context_processors.media",
+    "django.contrib.messages.context_processors.messages")
 
 A tuple of callables that are used to populate the context in ``RequestContext``.
 These callables take a request object as their argument and return a dictionary
diff -r ed90e13d2065 docs/ref/templates/api.txt
--- a/docs/ref/templates/api.txt	Tue Nov 17 02:13:26 2009 +0000
+++ b/docs/ref/templates/api.txt	Tue Nov 17 00:55:09 2009 -0500
@@ -311,7 +311,8 @@
     ("django.core.context_processors.auth",
     "django.core.context_processors.debug",
     "django.core.context_processors.i18n",
-    "django.core.context_processors.media")
+    "django.core.context_processors.media",
+    "django.contrib.messages.context_processors.messages")
 
 .. versionadded:: 1.2
    In addition to these, ``RequestContext`` always uses
@@ -320,6 +321,10 @@
    in case of accidental misconfiguration, it is deliberately hardcoded in and
    cannot be turned off by the :setting:`TEMPLATE_CONTEXT_PROCESSORS` setting.
 
+.. versionadded:: 1.2
+   The ``'messages'`` context processor was added.  For more information, see 
+   the :ref:`messages documentation <ref-contrib-messages>`.
+
 Each processor is applied in order. That means, if one processor adds a
 variable to the context and a second processor adds a variable with the same
 name, the second will override the first. The default processors are explained
@@ -365,17 +370,18 @@
       logged-in user (or an ``AnonymousUser`` instance, if the client isn't
       logged in).
 
-    * ``messages`` -- A list of messages (as strings) for the currently
-      logged-in user. Behind the scenes, this calls
-      ``request.user.get_and_delete_messages()`` for every request. That method
-      collects the user's messages and deletes them from the database.
-
-      Note that messages are set with ``user.message_set.create``.
+    * ``messages`` -- A list of messages (as strings) that have been set
+      via the user model (using ``user.message_set.create``) or through
+      the :ref:`messages framework <ref-contrib-messages>`.
 
     * ``perms`` -- An instance of
       ``django.core.context_processors.PermWrapper``, representing the
       permissions that the currently logged-in user has.
 
+.. versionchanged:: 1.2
+   The ``messages`` variable was changed to include any messages added
+   via the :ref:`messages framework <ref-contrib-messages`.
+
 django.core.context_processors.debug
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -427,6 +433,25 @@
 :class:`~django.http.HttpRequest`. Note that this processor is not enabled by default;
 you'll have to activate it.
 
+django.contrib.messages.context_processors.messages
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If :setting:`TEMPLATE_CONTEXT_PROCESSORS` contains this processor, every
+``RequestContext`` will contain a single additional variable:
+
+    * ``messages`` -- A list of messages (as strings) that have been set
+      via the user model (using ``user.message_set.create``) or through
+      the :ref:`messages framework <ref-contrib-messages>`.
+
+.. versionadded:: 1.2
+   This template context variable was previously supplied by the ``'auth'``
+   context processor.  For backwards compatibility the ``'auth'`` context
+   processor will continue to supply the ``messages`` variable until Django
+   1.4.  If you use the ``messages`` variable, your project will work with
+   either (or both) context processors, but it is recommended to add 
+   ``django.contrib.messages.context_processors.messages`` so your project
+   will be prepared for the future upgrade.
+
 Writing your own context processors
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
diff -r ed90e13d2065 docs/topics/auth.txt
--- a/docs/topics/auth.txt	Tue Nov 17 02:13:26 2009 +0000
+++ b/docs/topics/auth.txt	Tue Nov 17 00:55:09 2009 -0500
@@ -1289,6 +1289,11 @@
 Messages
 ========
 
+**NOTE:** This functionality is pending deprecation and will be removed in 
+Django 1.4.  It is recommended to use the 
+:ref:`messages framework <ref-contrib-messages>` for new projects whenever 
+possible.
+
 The message system is a lightweight way to queue messages for given users.
 
 A message is associated with a :class:`~django.contrib.auth.models.User`.
@@ -1334,13 +1339,16 @@
     </ul>
     {% endif %}
 
-Note that :class:`~django.template.context.RequestContext` calls
-:meth:`~django.contrib.auth.models.User.get_and_delete_messages` behind the
-scenes, so any messages will be deleted even if you don't display them.
+.. versionchanged:: 1.2
+   The ``messages`` template variable uses a backwards compatible method in the
+   :ref:`messages framework <ref-contrib-messages>` to retrieve messages from
+   both the user ``Message`` model and from the new framework.  Unlike in
+   previous revisions, the messages will not be erased unless they are actually
+   displayed.
 
 Finally, note that this messages framework only works with users in the user
 database. To send messages to anonymous users, use the
-:ref:`session framework <topics-http-sessions>`.
+:ref:`messages framework <ref-contrib-messages>`.
 
 .. _authentication-backends:
 
diff -r ed90e13d2065 tests/runtests.py
--- a/tests/runtests.py	Tue Nov 17 02:13:26 2009 +0000
+++ b/tests/runtests.py	Tue Nov 17 00:55:09 2009 -0500
@@ -28,6 +28,7 @@
     'django.contrib.flatpages',
     'django.contrib.redirects',
     'django.contrib.sessions',
+    'django.contrib.messages',
     'django.contrib.comments',
     'django.contrib.admin',
 ]
@@ -107,6 +108,7 @@
     settings.MIDDLEWARE_CLASSES = (
         'django.contrib.sessions.middleware.SessionMiddleware',
         'django.contrib.auth.middleware.AuthenticationMiddleware',
+        'django.contrib.messages.middleware.MessageMiddleware',
         'django.middleware.common.CommonMiddleware',
     )
     settings.SITE_ID = 1
