Advanced deprecation of user-based messages and the LegacyFallbackStorage in contrib.messages.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@15975 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Russell Keith-Magee 2011-04-02 08:36:04 +00:00
parent c349aad412
commit 5d5149cd18
8 changed files with 2 additions and 232 deletions

View File

@ -502,7 +502,7 @@ CSRF_COOKIE_DOMAIN = None
############ ############
# Class to use as messges backend # Class to use as messges backend
MESSAGE_STORAGE = 'django.contrib.messages.storage.user_messages.LegacyFallbackStorage' MESSAGE_STORAGE = 'django.contrib.messages.storage.fallback.FallbackStorage'
# Default values of MESSAGE_LEVEL and MESSAGE_TAGS are defined within # Default values of MESSAGE_LEVEL and MESSAGE_TAGS are defined within
# django.contrib.messages to avoid imports in this settings file. # django.contrib.messages to avoid imports in this settings file.

View File

@ -345,13 +345,6 @@ class User(models.Model):
return _user_has_module_perms(self, app_label) return _user_has_module_perms(self, app_label)
def get_and_delete_messages(self):
messages = []
for m in self.message_set.all():
messages.append(m.message)
m.delete()
return messages
def email_user(self, subject, message, from_email=None): def email_user(self, subject, message, from_email=None):
"Sends an email to this User." "Sends an email to this User."
from django.core.mail import send_mail from django.core.mail import send_mail
@ -387,21 +380,6 @@ class User(models.Model):
return self._profile_cache return self._profile_cache
class Message(models.Model):
"""
The message system is a lightweight way to queue messages for given
users. A message is associated with a User instance (so it is only
applicable for registered users). There's no concept of expiration or
timestamps. Messages are created by the Django admin after successful
actions. For example, "The poll Foo was created successfully." is a
message.
"""
user = models.ForeignKey(User, related_name='_message_set')
message = models.TextField(_('message'))
def __unicode__(self):
return self.message
class AnonymousUser(object): class AnonymousUser(object):
id = None id = None
username = '' username = ''

View File

@ -20,8 +20,6 @@ def add_message(request, level, message, extra_tags='', fail_silently=False):
""" """
if hasattr(request, '_messages'): if hasattr(request, '_messages'):
return request._messages.add(level, message, extra_tags) return request._messages.add(level, message, extra_tags)
if hasattr(request, 'user') and request.user.is_authenticated():
return request.user.message_set.create(message=message)
if not fail_silently: if not fail_silently:
raise MessageFailure('Without the django.contrib.messages ' raise MessageFailure('Without the django.contrib.messages '
'middleware, messages can only be added to ' 'middleware, messages can only be added to '

View File

@ -1,64 +0,0 @@
"""
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

View File

@ -2,5 +2,3 @@ from django.contrib.messages.tests.cookie import CookieTest
from django.contrib.messages.tests.fallback import FallbackTest from django.contrib.messages.tests.fallback import FallbackTest
from django.contrib.messages.tests.middleware import MiddlewareTest from django.contrib.messages.tests.middleware import MiddlewareTest
from django.contrib.messages.tests.session import SessionTest from django.contrib.messages.tests.session import SessionTest
from django.contrib.messages.tests.user_messages import \
UserMessagesTest, LegacyFallbackTest

View File

@ -60,9 +60,6 @@ class BaseTest(TestCase):
self.storage_class.__name__) self.storage_class.__name__)
self.old_TEMPLATE_DIRS = settings.TEMPLATE_DIRS self.old_TEMPLATE_DIRS = settings.TEMPLATE_DIRS
settings.TEMPLATE_DIRS = () settings.TEMPLATE_DIRS = ()
self.save_warnings_state()
warnings.filterwarnings('ignore', category=DeprecationWarning,
module='django.contrib.auth.models')
def tearDown(self): def tearDown(self):
for setting in self.restore_settings: for setting in self.restore_settings:
@ -74,7 +71,6 @@ class BaseTest(TestCase):
settings.INSTALLED_APPS = self._installed_apps settings.INSTALLED_APPS = self._installed_apps
settings.MESSAGE_STORAGE = self._message_storage settings.MESSAGE_STORAGE = self._message_storage
settings.TEMPLATE_DIRS = self.old_TEMPLATE_DIRS settings.TEMPLATE_DIRS = self.old_TEMPLATE_DIRS
self.restore_warnings_state()
def restore_setting(self, setting): def restore_setting(self, setting):
if setting in self._remembered_settings: if setting in self._remembered_settings:
@ -226,44 +222,6 @@ class BaseTest(TestCase):
for msg in data['messages']: for msg in data['messages']:
self.assertContains(response, msg) self.assertContains(response, msg)
@skipUnlessAuthIsInstalled
def test_middleware_disabled_auth_user(self):
"""
Tests that the messages API successfully falls back to using
user.message_set to store messages directly when the middleware is
disabled.
"""
settings.MESSAGE_LEVEL = constants.DEBUG
user = User.objects.create_user('test', 'test@example.com', 'test')
self.client.login(username='test', password='test')
settings.INSTALLED_APPS = list(settings.INSTALLED_APPS)
settings.INSTALLED_APPS.remove(
'django.contrib.messages',
)
settings.MIDDLEWARE_CLASSES = list(settings.MIDDLEWARE_CLASSES)
settings.MIDDLEWARE_CLASSES.remove(
'django.contrib.messages.middleware.MessageMiddleware',
)
settings.TEMPLATE_CONTEXT_PROCESSORS = \
list(settings.TEMPLATE_CONTEXT_PROCESSORS)
settings.TEMPLATE_CONTEXT_PROCESSORS.remove(
'django.contrib.messages.context_processors.messages',
)
data = {
'messages': ['Test message %d' % x for x in xrange(10)],
}
show_url = reverse('django.contrib.messages.tests.urls.show')
for level in ('debug', 'info', 'success', 'warning', 'error'):
add_url = reverse('django.contrib.messages.tests.urls.add',
args=(level,))
response = self.client.post(add_url, data, follow=True)
self.assertRedirects(response, show_url)
self.assertTrue('messages' in response.context)
context_messages = list(response.context['messages'])
for msg in data['messages']:
self.assertTrue(msg in context_messages)
self.assertContains(response, msg)
def test_middleware_disabled_anon_user(self): def test_middleware_disabled_anon_user(self):
""" """
Tests that, when the middleware is disabled and a user is not logged Tests that, when the middleware is disabled and a user is not logged

View File

@ -1,70 +0,0 @@
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.base import skipUnlessAuthIsInstalled
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')
UserMessagesTest = skipUnlessAuthIsInstalled(UserMessagesTest)
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')
LegacyFallbackTest = skipUnlessAuthIsInstalled(LegacyFallbackTest)

View File

@ -83,37 +83,10 @@ Four storage classes are included:
Since it is uses SessionStorage, it also requires Django's Since it is uses SessionStorage, it also requires Django's
``contrib.sessions`` application. ``contrib.sessions`` application.
``'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'``
This is the default temporary storage class.
This class extends FallbackStorage and adds compatibility methods
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 To write your own storage class, subclass the ``BaseStorage`` class in
``django.contrib.messages.storage.base`` and implement the ``_get`` and ``django.contrib.messages.storage.base`` and implement the ``_get`` and
``_store`` methods. ``_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
according to Django's standard deprecation policy. For more information, see
the full :doc:`release process documentation </internals/release-process>`.
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 by those stored in a cookie and in the 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.
Message levels Message levels
-------------- --------------
@ -368,14 +341,13 @@ This sets the minimum message that will be saved in the message storage. See
MESSAGE_STORAGE MESSAGE_STORAGE
--------------- ---------------
Default: ``'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'`` Default: ``'django.contrib.messages.storage.user_messages.FallbackStorage'``
Controls where Django stores message data. Valid values are: Controls where Django stores message data. Valid values are:
* ``'django.contrib.messages.storage.fallback.FallbackStorage'`` * ``'django.contrib.messages.storage.fallback.FallbackStorage'``
* ``'django.contrib.messages.storage.session.SessionStorage'`` * ``'django.contrib.messages.storage.session.SessionStorage'``
* ``'django.contrib.messages.storage.cookie.CookieStorage'`` * ``'django.contrib.messages.storage.cookie.CookieStorage'``
* ``'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'``
See `Storage backends`_ for more details. See `Storage backends`_ for more details.