Ticket #4604: django-contrib-messages-e4da706e1152.diff
File django-contrib-messages-e4da706e1152.diff, 96.5 KB (added by , 15 years ago) |
---|
-
django/conf/global_settings.py
diff -r 1fb6476dce9d django/conf/global_settings.py
a b 172 172 'django.core.context_processors.i18n', 173 173 'django.core.context_processors.media', 174 174 # 'django.core.context_processors.request', 175 'django.contrib.messages.context_processors.messages', 175 176 ) 176 177 177 178 # Output to use in template system for invalid (e.g. misspelled) variables. … … 308 309 'django.contrib.sessions.middleware.SessionMiddleware', 309 310 'django.middleware.csrf.CsrfViewMiddleware', 310 311 'django.contrib.auth.middleware.AuthenticationMiddleware', 312 'django.contrib.messages.middleware.MessageMiddleware', 311 313 # 'django.middleware.http.ConditionalGetMiddleware', 312 314 # 'django.middleware.gzip.GZipMiddleware', 313 315 ) -
django/conf/project_template/settings.py
diff -r 1fb6476dce9d django/conf/project_template/settings.py
a b 62 62 'django.contrib.sessions.middleware.SessionMiddleware', 63 63 'django.middleware.csrf.CsrfViewMiddleware', 64 64 'django.contrib.auth.middleware.AuthenticationMiddleware', 65 'django.contrib.messages.middleware.MessageMiddleware', 65 66 ) 66 67 67 68 ROOT_URLCONF = '{{ project_name }}.urls' … … 77 78 'django.contrib.contenttypes', 78 79 'django.contrib.sessions', 79 80 'django.contrib.sites', 81 'django.contrib.messages', 80 82 ) -
django/contrib/admin/options.py
diff -r 1fb6476dce9d django/contrib/admin/options.py
a b 6 6 from django.contrib.admin import widgets 7 7 from django.contrib.admin import helpers 8 8 from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects, model_ngettext, model_format_dict 9 from django.contrib import messages 9 10 from django.views.decorators.csrf import csrf_protect 10 11 from django.core.exceptions import PermissionDenied 11 12 from django.db import models, transaction … … 541 542 def message_user(self, request, message): 542 543 """ 543 544 Send a message to the user. The default implementation 544 posts a message using the auth Message object.545 posts a message using the django.contrib.messages backend. 545 546 """ 546 request.user.message_set.create(message=message)547 messages.info(request, message) 547 548 548 549 def save_form(self, request, form, change): 549 550 """ -
django/contrib/admin/views/template.py
diff -r 1fb6476dce9d django/contrib/admin/views/template.py
a b 6 6 from django.conf import settings 7 7 from django.utils.importlib import import_module 8 8 from django.utils.translation import ugettext_lazy as _ 9 from django.contrib import messages 9 10 10 11 11 12 def template_validator(request): … … 23 24 form = TemplateValidatorForm(settings_modules, site_list, 24 25 data=request.POST) 25 26 if form.is_valid(): 26 request.user.message_set.create(message='The template is valid.')27 messages.info(request, 'The template is valid.') 27 28 else: 28 29 form = TemplateValidatorForm(settings_modules, site_list) 29 30 return render_to_response('admin/template_validator.html', { -
django/contrib/auth/admin.py
diff -r 1fb6476dce9d django/contrib/auth/admin.py
a b 3 3 from django.contrib import admin 4 4 from django.contrib.auth.forms import UserCreationForm, UserChangeForm, AdminPasswordChangeForm 5 5 from django.contrib.auth.models import User, Group 6 from django.contrib import messages 6 7 from django.core.exceptions import PermissionDenied 7 8 from django.http import HttpResponseRedirect, Http404 8 9 from django.shortcuts import render_to_response, get_object_or_404 … … 67 68 msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': 'user', 'obj': new_user} 68 69 self.log_addition(request, new_user) 69 70 if "_addanother" in request.POST: 70 request.user.message_set.create(message=msg)71 messages.success(request, msg) 71 72 return HttpResponseRedirect(request.path) 72 73 elif '_popup' in request.REQUEST: 73 74 return self.response_add(request, new_user) 74 75 else: 75 request.user.message_set.create(message=msg + ' ' + ugettext("You may edit it again below.")) 76 messages.success(request, msg + ' ' + 77 ugettext("You may edit it again below.")) 76 78 return HttpResponseRedirect('../%s/' % new_user.id) 77 79 else: 78 80 form = self.add_form() … … 104 106 if form.is_valid(): 105 107 new_user = form.save() 106 108 msg = ugettext('Password changed successfully.') 107 request.user.message_set.create(message=msg)109 messages.success(request, msg) 108 110 return HttpResponseRedirect('..') 109 111 else: 110 112 form = self.change_password_form(user) -
django/contrib/auth/models.py
diff -r 1fb6476dce9d django/contrib/auth/models.py
a b 288 288 raise SiteProfileNotAvailable 289 289 return self._profile_cache 290 290 291 def _get_message_set(self): 292 import warnings 293 warnings.warn('The user messaging API is deprecated. Please update' 294 ' your code to use the new messages framework.', 295 category=PendingDeprecationWarning) 296 return self._message_set 297 message_set = property(_get_message_set) 298 291 299 class Message(models.Model): 292 300 """ 293 301 The message system is a lightweight way to queue messages for given … … 297 305 actions. For example, "The poll Foo was created successfully." is a 298 306 message. 299 307 """ 300 user = models.ForeignKey(User )308 user = models.ForeignKey(User, related_name='_message_set') 301 309 message = models.TextField(_('message')) 302 310 303 311 def __unicode__(self): -
new file django/contrib/messages/__init__.py
diff -r 1fb6476dce9d django/contrib/messages/__init__.py
- + 1 from api import * 2 from constants import * -
new file django/contrib/messages/api.py
diff -r 1fb6476dce9d django/contrib/messages/api.py
- + 1 from django.contrib.messages import constants 2 from django.utils.functional import lazy, memoize 3 4 __all__ = ( 5 'add_message', 'get_messages', 6 'debug', 'info', 'success', 'warning', 'error', 7 ) 8 9 10 class AddMessageFailure(Exception): 11 pass 12 13 14 def add_message(request, level, message, extra_tags='', fail_silently=False): 15 """ 16 Attempts to add a message to the request using the 'messages' app, falling 17 back to the user's message_set if MessageMiddleware hasn't been enabled. 18 """ 19 if hasattr(request, '_messages'): 20 return request._messages.add(level, message, extra_tags) 21 if hasattr(request, 'user') and request.user.is_authenticated(): 22 return request.user.message_set.create(message=message) 23 if not fail_silently: 24 raise AddMessageFailure('Without the django.contrib.messages ' 25 'middleware, messages can only be added to ' 26 'authenticated users.') 27 28 29 def get_messages(request): 30 """ 31 Returns the message storage on the request if it exists, otherwise returns 32 user.message_set.all() as the old auth context processor did. 33 """ 34 if hasattr(request, '_messages'): 35 return request._messages 36 37 def get_user(): 38 if hasattr(request, 'user'): 39 return request.user 40 else: 41 from django.contrib.auth.models import AnonymousUser 42 return AnonymousUser() 43 44 return lazy(memoize(get_user().get_and_delete_messages, {}, 0), list)() 45 46 47 def debug(request, message, extra_tags='', fail_silently=False): 48 """ 49 Adds a message with the ``DEBUG`` level. 50 """ 51 add_message(request, constants.DEBUG, message, extra_tags=extra_tags, 52 fail_silently=fail_silently) 53 54 55 def info(request, message, extra_tags='', fail_silently=False): 56 """ 57 Adds a message with the ``INFO`` level. 58 """ 59 add_message(request, constants.INFO, message, extra_tags=extra_tags, 60 fail_silently=fail_silently) 61 62 63 def success(request, message, extra_tags='', fail_silently=False): 64 """ 65 Adds a message with the ``SUCCESS`` level. 66 """ 67 add_message(request, constants.SUCCESS, message, extra_tags=extra_tags, 68 fail_silently=fail_silently) 69 70 71 def warning(request, message, extra_tags='', fail_silently=False): 72 """ 73 Adds a message with the ``WARNING`` level. 74 """ 75 add_message(request, constants.WARNING, message, extra_tags=extra_tags, 76 fail_silently=fail_silently) 77 78 79 def error(request, message, extra_tags='', fail_silently=False): 80 """ 81 Adds a message with the ``ERROR`` level. 82 """ 83 add_message(request, constants.ERROR, message, extra_tags=extra_tags, 84 fail_silently=fail_silently) -
new file django/contrib/messages/constants.py
diff -r 1fb6476dce9d django/contrib/messages/constants.py
- + 1 DEBUG = 10 2 INFO = 20 3 SUCCESS = 25 4 WARNING = 30 5 ERROR = 40 6 7 DEFAULT_TAGS = { 8 DEBUG: 'debug', 9 INFO: 'info', 10 SUCCESS: 'success', 11 WARNING: 'warning', 12 ERROR: 'error', 13 } -
new file django/contrib/messages/context_processors.py
diff -r 1fb6476dce9d django/contrib/messages/context_processors.py
- + 1 from django.contrib.messages.api import get_messages 2 3 4 def messages(request): 5 """ 6 Returns a lazy 'messages' context variable. 7 """ 8 return {'messages': get_messages(request)} -
new file django/contrib/messages/middleware.py
diff -r 1fb6476dce9d django/contrib/messages/middleware.py
- + 1 from django.conf import settings 2 from django.contrib.messages.storage import Storage 3 4 5 class MessageMiddleware(object): 6 """ 7 Middleware that handles temporary messages. 8 """ 9 10 def process_request(self, request): 11 request._messages = Storage(request) 12 13 def process_response(self, request, response): 14 """ 15 Updates the storage backend (i.e., saves the messages). 16 17 If not all messages could not be stored and ``DEBUG`` is ``True``, a 18 ``ValueError`` is raised. 19 """ 20 # A higher middleware layer may return a request which does not contain 21 # messages storage, so make no assumption that it will be there. 22 if hasattr(request, '_messages'): 23 unstored_messages = request._messages.update(response) 24 if unstored_messages and settings.DEBUG: 25 raise ValueError('Not all temporary messages could be stored.') 26 return response -
new file django/contrib/messages/models.py
diff -r 1fb6476dce9d django/contrib/messages/models.py
- + 1 # Models module required so tests are discovered. -
new file django/contrib/messages/storage/__init__.py
diff -r 1fb6476dce9d django/contrib/messages/storage/__init__.py
- + 1 from django.conf import settings 2 from django.utils.importlib import import_module 3 4 5 def get_storage(import_path): 6 """ 7 Imports the message storage class described by import_path, where 8 import_path is the full Python path to the class. 9 """ 10 try: 11 dot = import_path.rindex('.') 12 except ValueError: 13 raise ImproperlyConfigured("%s isn't a Python path." % import_path) 14 module, classname = import_path[:dot], import_path[dot+1:] 15 try: 16 mod = import_module(module) 17 except ImportError, e: 18 raise ImproperlyConfigured('Error importing module %s: "%s"' % (module, e)) 19 try: 20 return getattr(mod, classname) 21 except AttributeError: 22 raise ImproperlyConfigured('Module "%s" does not define a "%s" class.' % (module, classname)) 23 24 25 Storage = get_storage(getattr(settings, 'MESSAGE_STORAGE', 26 'django.contrib.messages.storage.user_messages.LegacyFallbackStorage')) -
new file django/contrib/messages/storage/base.py
diff -r 1fb6476dce9d django/contrib/messages/storage/base.py
- + 1 from django.conf import settings 2 from django.utils.encoding import force_unicode, StrAndUnicode 3 from django.contrib.messages import constants, utils 4 5 6 LEVEL_TAGS = utils.get_level_tags() 7 8 9 class Message(StrAndUnicode): 10 """ 11 Represents an actual message that can be stored in any of the supported 12 storage classes (typically session- or cookie-based) and rendered in a view 13 or template. 14 """ 15 16 def __init__(self, level, message, extra_tags=None): 17 self.level = int(level) 18 self.message = message 19 self.extra_tags = extra_tags 20 21 def _prepare(self): 22 """ 23 Prepares the message for serialization by forcing the ``message`` 24 and ``extra_tags`` to unicode in case they are lazy translations. 25 26 Known "safe" types (None, int, etc.) are not converted (see Django's 27 ``force_unicode`` implementation for details). 28 """ 29 self.message = force_unicode(self.message, strings_only=True) 30 self.extra_tags = force_unicode(self.extra_tags, strings_only=True) 31 32 def __eq__(self, other): 33 return isinstance(other, Message) and self.level == other.level and \ 34 self.message == other.message 35 36 def __unicode__(self): 37 return force_unicode(self.message) 38 39 def _get_tags(self): 40 label_tag = force_unicode(LEVEL_TAGS.get(self.level, ''), 41 strings_only=True) 42 extra_tags = force_unicode(self.extra_tags, strings_only=True) 43 if extra_tags and label_tag: 44 return u' '.join([extra_tags, label_tag]) 45 elif extra_tags: 46 return extra_tags 47 elif label_tag: 48 return label_tag 49 return '' 50 tags = property(_get_tags) 51 52 53 class BaseStorage(object): 54 """ 55 This is the base backend for temporary message storage. 56 57 This is not a complete class; to be a usable storage backend, it must be 58 subclassed and the two methods ``_get`` and ``_store`` overridden. 59 """ 60 61 def __init__(self, request, *args, **kwargs): 62 self.request = request 63 self._queued_messages = [] 64 self.used = False 65 self.added_new = False 66 super(BaseStorage, self).__init__(*args, **kwargs) 67 68 def __len__(self): 69 return len(self._loaded_messages) + len(self._queued_messages) 70 71 def __iter__(self): 72 self.used = True 73 if self._queued_messages: 74 self._loaded_messages.extend(self._queued_messages) 75 self._queued_messages = [] 76 return iter(self._loaded_messages) 77 78 def __contains__(self, item): 79 return item in self._loaded_messages or item in self._queued_messages 80 81 @property 82 def _loaded_messages(self): 83 """ 84 Returns a list of loaded messages, retrieving them first if they have 85 not been loaded yet. 86 """ 87 if not hasattr(self, '_loaded_data'): 88 messages, all_retrieved = self._get() 89 self._loaded_data = messages or [] 90 return self._loaded_data 91 92 def _get(self, *args, **kwargs): 93 """ 94 Retrieves a list of stored messages. Returns a tuple of the messages and 95 a flag indicating whether or not all the messages originally intended 96 to be stored in this storage were, in fact, stored and now retrieved. 97 98 **This method must be implemented by a subclass.** 99 100 If it is possible to tell if the backend was not used (as opposed to 101 just containing no messages) then ``None`` should be returned. 102 """ 103 raise NotImplementedError() 104 105 def _store(self, messages, response, *args, **kwargs): 106 """ 107 Stores a list of messages, returning a list of any messages which could 108 not be stored. 109 110 One type of object must be able to be stored, ``Message``. 111 112 **This method must be implemented by a subclass.** 113 """ 114 raise NotImplementedError() 115 116 def _prepare_messages(self, messages): 117 """ 118 Prepares a list of messages for storage. 119 """ 120 for message in messages: 121 message._prepare() 122 123 def update(self, response): 124 """ 125 Stores all unread messages. 126 127 If the backend has yet to be iterated, previously stored messages will 128 be stored again. Otherwise, only messages added after the last 129 iteration will be stored. 130 """ 131 self._prepare_messages(self._queued_messages) 132 if self.used: 133 return self._store(self._queued_messages, response) 134 elif self.added_new: 135 messages = self._loaded_messages + self._queued_messages 136 return self._store(messages, response) 137 138 def add(self, level, message, extra_tags=''): 139 """ 140 Queues a message to be stored. 141 142 The message is only queued if it contained something and its level is 143 not less than the recording level (``self.level``). 144 """ 145 if not message: 146 return 147 # Check that the message level is not less than the recording level. 148 level = int(level) 149 if level < self.level: 150 return 151 # Add the message. 152 self.added_new = True 153 message = Message(level, message, extra_tags=extra_tags) 154 self._queued_messages.append(message) 155 156 def _get_level(self): 157 """ 158 Returns the minimum recorded level. 159 160 The default level is the ``MESSAGE_LEVEL`` setting. If this is 161 not found, the ``INFO`` level is used. 162 """ 163 if not hasattr(self, '_level'): 164 self._level = getattr(settings, 'MESSAGE_LEVEL', constants.INFO) 165 return self._level 166 167 def _set_level(self, value=None): 168 """ 169 Sets a custom minimum recorded level. 170 171 If set to ``None``, the default level will be used (see the 172 ``_get_level`` method). 173 """ 174 if value is None and hasattr(self, '_level'): 175 del self._level 176 else: 177 self._level = int(value) 178 179 level = property(_get_level, _set_level, _set_level) -
new file django/contrib/messages/storage/cookie.py
diff -r 1fb6476dce9d django/contrib/messages/storage/cookie.py
- + 1 import hmac 2 3 from django.conf import settings 4 from django.utils.hashcompat import sha_constructor 5 from django.contrib.messages import constants 6 from django.contrib.messages.storage.base import BaseStorage, Message 7 try: 8 import json # Available in Python 2.6. 9 except ImportError: 10 # Otherwise fall back to simplejson bundled with Django. 11 from django.utils import simplejson as json 12 13 14 class MessageEncoder(json.JSONEncoder): 15 """ 16 Compactly serializes instances of the ``Message`` class as JSON. 17 """ 18 message_key = '__json_message' 19 20 def default(self, obj): 21 if isinstance(obj, Message): 22 message = [self.message_key, obj.level, obj.message] 23 if obj.extra_tags: 24 message.append(obj.extra_tags) 25 return message 26 return super(MessageEncoder, self).default(obj) 27 28 29 class MessageDecoder(json.JSONDecoder): 30 """ 31 Decodes JSON that includes serialized ``Message`` instances. 32 """ 33 34 def process_messages(self, obj): 35 if isinstance(obj, list) and obj: 36 if obj[0] == MessageEncoder.message_key: 37 return Message(*obj[1:]) 38 return [self.process_messages(item) for item in obj] 39 if isinstance(obj, dict): 40 return dict([(key, self.process_messages(value)) 41 for key, value in obj.iteritems()]) 42 return obj 43 44 def decode(self, s, **kwargs): 45 decoded = super(MessageDecoder, self).decode(s, **kwargs) 46 return self.process_messages(decoded) 47 48 49 class CookieStorage(BaseStorage): 50 """ 51 Stores messages in a cookie. 52 """ 53 cookie_name = 'messages' 54 max_cookie_size = 4096 55 not_finished = '__messagesnotfinished__' 56 57 def _get(self, *args, **kwargs): 58 """ 59 Retrieves a list of messages from the messages cookie. If the 60 not_finished sentinel value is found at the end of the message list, 61 remove it and return a result indicating that not all messages were 62 retrieved by this storage. 63 """ 64 data = self.request.COOKIES.get(self.cookie_name) 65 messages = self._decode(data) 66 all_retrieved = not (messages and messages[-1] == self.not_finished) 67 if messages and not all_retrieved: 68 # remove the sentinel value 69 messages.pop() 70 return messages, all_retrieved 71 72 def _update_cookie(self, encoded_data, response): 73 """ 74 Either sets the cookie with the encoded data if there is any data to 75 store, or deletes the cookie. 76 """ 77 if encoded_data: 78 response.set_cookie(self.cookie_name, encoded_data) 79 else: 80 response.delete_cookie(self.cookie_name) 81 82 def _store(self, messages, response, remove_oldest=True, *args, **kwargs): 83 """ 84 Stores the messages to a cookie, returning a list of any messages which 85 could not be stored. 86 87 If the encoded data is larger than ``max_cookie_size``, removes 88 messages until the data fits (these are the messages which are 89 returned), and add the not_finished sentinel value to indicate as much. 90 """ 91 unstored_messages = [] 92 encoded_data = self._encode(messages) 93 if self.max_cookie_size: 94 while encoded_data and len(encoded_data) > self.max_cookie_size: 95 if remove_oldest: 96 unstored_messages.append(messages.pop(0)) 97 else: 98 unstored_messages.insert(0, messages.pop()) 99 encoded_data = self._encode(messages + [self.not_finished], 100 encode_empty=unstored_messages) 101 self._update_cookie(encoded_data, response) 102 return unstored_messages 103 104 def _hash(self, value): 105 """ 106 Creates an HMAC/SHA1 hash based on the value and the project setting's 107 SECRET_KEY, modified to make it unique for the present purpose. 108 """ 109 key = 'django.contrib.messages' + settings.SECRET_KEY 110 return hmac.new(key, value, sha_constructor).hexdigest() 111 112 def _encode(self, messages, encode_empty=False): 113 """ 114 Returns an encoded version of the messages list which can be stored as 115 plain text. 116 117 Since the data will be retrieved from the client-side, the encoded data 118 also contains a hash to ensure that the data was not tampered with. 119 """ 120 if messages or encode_empty: 121 encoder = MessageEncoder(separators=(',', ':')) 122 value = encoder.encode(messages) 123 return '%s$%s' % (self._hash(value), value) 124 125 def _decode(self, data): 126 """ 127 Safely decodes a encoded text stream back into a list of messages. 128 129 If the encoded text stream contained an invalid hash or was in an 130 invalid format, ``None`` is returned. 131 """ 132 if not data: 133 return None 134 bits = data.split('$', 1) 135 if len(bits) == 2: 136 hash, value = bits 137 if hash == self._hash(value): 138 try: 139 # If we get here (and the JSON decode works), everything is 140 # good. In any other case, drop back and return None. 141 return json.loads(value, cls=MessageDecoder) 142 except ValueError: 143 pass 144 # Mark the data as used (so it gets removed) since something was wrong 145 # with the data. 146 self.used = True 147 return None -
new file django/contrib/messages/storage/fallback.py
diff -r 1fb6476dce9d django/contrib/messages/storage/fallback.py
- + 1 from django.contrib.messages.storage.base import BaseStorage 2 from django.contrib.messages.storage.cookie import CookieStorage 3 from django.contrib.messages.storage.session import SessionStorage 4 try: 5 set 6 except NameError: 7 from sets import Set as set # Python 2.3 8 9 10 class FallbackStorage(BaseStorage): 11 """ 12 Tries to store all messages in the first backend, storing any unstored 13 messages in each subsequent backend backend. 14 """ 15 storage_classes = (CookieStorage, SessionStorage) 16 17 def __init__(self, *args, **kwargs): 18 super(FallbackStorage, self).__init__(*args, **kwargs) 19 self.storages = [storage_class(*args, **kwargs) 20 for storage_class in self.storage_classes] 21 self._used_storages = set() 22 23 def _get(self, *args, **kwargs): 24 """ 25 Gets a single list of messages from all storage backends. 26 """ 27 all_messages = [] 28 for storage in self.storages: 29 messages, all_retrieved = storage._get() 30 # If the backend hasn't been used, no more retrieval is necessary. 31 if messages is None: 32 break 33 if messages: 34 self._used_storages.add(storage) 35 all_messages.extend(messages) 36 # If this storage class contained all the messages, no further 37 # retrieval is necessary 38 if all_retrieved: 39 break 40 return all_messages, all_retrieved 41 42 def _store(self, messages, response, *args, **kwargs): 43 """ 44 Stores the messages, returning any unstored messages after trying all 45 backends. 46 47 For each storage backend, any messages not stored are passed on to the 48 next backend. 49 """ 50 for storage in self.storages: 51 if messages: 52 messages = storage._store( 53 messages, 54 response, 55 remove_oldest=False, 56 ) 57 # Even if there are no more messages, continue iterating to ensure 58 # storages which contained messages are flushed. 59 elif storage in self._used_storages: 60 storage._store([], response) 61 self._used_storages.remove(storage) 62 return messages -
new file django/contrib/messages/storage/session.py
diff -r 1fb6476dce9d django/contrib/messages/storage/session.py
- + 1 from django.contrib.messages.storage.base import BaseStorage 2 3 4 class SessionStorage(BaseStorage): 5 """ 6 Stores messages in the session (that is, django.contrib.sessions). 7 """ 8 session_key = '_messages' 9 10 def __init__(self, request, *args, **kwargs): 11 assert hasattr(request, 'session'), "The session-based temporary "\ 12 "message storage requires session middleware to be installed, "\ 13 "and come before the message middleware in the MIDDLEWARE_CLASSES "\ 14 "list." 15 super(SessionStorage, self).__init__(request, *args, **kwargs) 16 17 def _get(self, *args, **kwargs): 18 """ 19 Retrieves a list of messages from the request's session. This storage 20 always stores everything it is given, so return True for the 21 all_retrieved flag. 22 """ 23 return self.request.session.get(self.session_key), True 24 25 def _store(self, messages, response, *args, **kwargs): 26 """ 27 Stores a list of messages to the request's session. 28 """ 29 if messages: 30 self.request.session[self.session_key] = messages 31 else: 32 self.request.session.pop(self.session_key, None) 33 return [] -
new file django/contrib/messages/storage/user_messages.py
diff -r 1fb6476dce9d django/contrib/messages/storage/user_messages.py
- + 1 """ 2 Storages used to assist in the deprecation of contrib.auth User messages. 3 4 """ 5 from django.contrib.messages import constants 6 from django.contrib.messages.storage.base import BaseStorage, Message 7 from django.contrib.auth.models import User 8 from django.contrib.messages.storage.fallback import FallbackStorage 9 10 11 class UserMessagesStorage(BaseStorage): 12 """ 13 Retrieves messages from the User, using the legacy user.message_set API. 14 15 This storage is "read-only" insofar as it can only retrieve and delete 16 messages, not store them. 17 """ 18 session_key = '_messages' 19 20 def _get_messages_queryset(self): 21 """ 22 Returns the QuerySet containing all user messages (or ``None`` if 23 request.user is not a contrib.auth User). 24 """ 25 user = getattr(self.request, 'user', None) 26 if isinstance(user, User): 27 return user._message_set.all() 28 29 def add(self, *args, **kwargs): 30 raise NotImplementedError('This message storage is read-only.') 31 32 def _get(self, *args, **kwargs): 33 """ 34 Retrieves a list of messages assigned to the User. This backend never 35 stores anything, so all_retrieved is assumed to be False. 36 """ 37 queryset = self._get_messages_queryset() 38 if queryset is None: 39 # This is a read-only and optional storage, so to ensure other 40 # storages will also be read if used with FallbackStorage an empty 41 # list is returned rather than None. 42 return [], False 43 messages = [] 44 for user_message in queryset: 45 messages.append(Message(constants.INFO, user_message.message)) 46 return messages, False 47 48 def _store(self, messages, *args, **kwargs): 49 """ 50 Removes any messages assigned to the User and returns the list of 51 messages (since no messages are stored in this read-only storage). 52 """ 53 queryset = self._get_messages_queryset() 54 if queryset is not None: 55 queryset.delete() 56 return messages 57 58 59 class LegacyFallbackStorage(FallbackStorage): 60 """ 61 Works like ``FallbackStorage`` but also handles retrieving (and clearing) 62 contrib.auth User messages. 63 """ 64 storage_classes = (UserMessagesStorage,) + FallbackStorage.storage_classes -
new file django/contrib/messages/utils.py
diff -r 1fb6476dce9d django/contrib/messages/utils.py
- + 1 from django.conf import settings 2 from django.contrib.messages import constants 3 4 5 def get_level_tags(): 6 """ 7 Returns the message level tags. 8 """ 9 level_tags = constants.DEFAULT_TAGS.copy() 10 level_tags.update(getattr(settings, 'MESSAGE_TAGS', {})) 11 return level_tags -
django/core/context_processors.py
diff -r 1fb6476dce9d django/core/context_processors.py
a b 10 10 from django.conf import settings 11 11 from django.middleware.csrf import get_token 12 12 from django.utils.functional import lazy, memoize, SimpleLazyObject 13 from django.contrib import messages 13 14 14 15 def auth(request): 15 16 """ … … 37 38 38 39 return { 39 40 'user': SimpleLazyObject(get_user), 40 'messages': lazy(memoize(lambda: get_user().get_and_delete_messages(), {}, 0), list)(),41 'perms': 41 'messages': messages.get_messages(request), 42 'perms': lazy(lambda: PermWrapper(get_user()), PermWrapper)(), 42 43 } 43 44 44 45 def csrf(request): -
django/views/generic/create_update.py
diff -r 1fb6476dce9d django/views/generic/create_update.py
a b 6 6 from django.utils.translation import ugettext 7 7 from django.contrib.auth.views import redirect_to_login 8 8 from django.views.generic import GenericViewError 9 from django.contrib import messages 9 10 10 11 11 12 def apply_extra_context(extra_context, context): … … 110 111 form = form_class(request.POST, request.FILES) 111 112 if form.is_valid(): 112 113 new_object = form.save() 113 if request.user.is_authenticated(): 114 request.user.message_set.create(message=ugettext("The %(verbose_name)s was created successfully.") % {"verbose_name": model._meta.verbose_name}) 114 115 msg = ugettext("The %(verbose_name)s was created successfully.") %\ 116 {"verbose_name": model._meta.verbose_name} 117 messages.success(request, msg, fail_silently=True) 115 118 return redirect(post_save_redirect, new_object) 116 119 else: 117 120 form = form_class() … … 152 155 form = form_class(request.POST, request.FILES, instance=obj) 153 156 if form.is_valid(): 154 157 obj = form.save() 155 if request.user.is_authenticated(): 156 request.user.message_set.create(message=ugettext("The %(verbose_name)s was updated successfully.") % {"verbose_name": model._meta.verbose_name}) 158 msg = ugettext("The %(verbose_name)s was updated successfully.") %\ 159 {"verbose_name": model._meta.verbose_name} 160 messages.success(request, msg, fail_silently=True) 157 161 return redirect(post_save_redirect, obj) 158 162 else: 159 163 form = form_class(instance=obj) … … 194 198 195 199 if request.method == 'POST': 196 200 obj.delete() 197 if request.user.is_authenticated(): 198 request.user.message_set.create(message=ugettext("The %(verbose_name)s was deleted.") % {"verbose_name": model._meta.verbose_name}) 201 msg = ugettext("The %(verbose_name)s was deleted.") %\ 202 {"verbose_name": model._meta.verbose_name} 203 messages.success(request, msg, fail_silently=True) 199 204 return HttpResponseRedirect(post_delete_redirect) 200 205 else: 201 206 if not template_name: -
docs/index.txt
diff -r 1fb6476dce9d docs/index.txt
a b 170 170 * :ref:`Internationalization <topics-i18n>` 171 171 * :ref:`Jython support <howto-jython>` 172 172 * :ref:`"Local flavor" <ref-contrib-localflavor>` 173 * :ref:`Messages <ref-contrib-messages>` 173 174 * :ref:`Pagination <topics-pagination>` 174 175 * :ref:`Redirects <ref-contrib-redirects>` 175 176 * :ref:`Serialization <topics-serialization>` -
docs/internals/deprecation.txt
diff -r 1fb6476dce9d docs/internals/deprecation.txt
a b 28 28 * The many to many SQL generation functions on the database backends 29 29 will be removed. These have been deprecated since the 1.2 release. 30 30 31 * The ``Message`` model (in ``django.contrib.auth``), its related 32 manager in the ``User`` model (``user.message_set``), and the 33 associated methods (``user.message_set.create()`` and 34 ``user.get_and_delete_messages()``), which have 35 been deprecated since the 1.2 release, will be removed. The 36 :ref:`messages framework <ref-contrib-messages>` should be used 37 instead. 38 31 39 * 2.0 32 40 * ``django.views.defaults.shortcut()``. This function has been moved 33 41 to ``django.contrib.contenttypes.views.shortcut()`` as part of the -
docs/ref/contrib/index.txt
diff -r 1fb6476dce9d docs/ref/contrib/index.txt
a b 34 34 formtools/index 35 35 humanize 36 36 localflavor 37 messages 37 38 redirects 38 39 sitemaps 39 40 sites … … 150 151 .. _Markdown: http://en.wikipedia.org/wiki/Markdown 151 152 .. _ReST (ReStructured Text): http://en.wikipedia.org/wiki/ReStructuredText 152 153 154 messages 155 ======== 156 157 .. versionchanged:: 1.2 158 The messages framework was added. 159 160 A framework for storing and retrieving temporary cookie- or session-based 161 messages 162 163 See the :ref:`messages documentation <ref-contrib-messages>`. 164 153 165 redirects 154 166 ========= 155 167 -
new file docs/ref/contrib/messages.txt
diff -r 1fb6476dce9d docs/ref/contrib/messages.txt
- + 1 .. _ref-contrib-messages: 2 3 ====================== 4 The messages framework 5 ====================== 6 7 .. module:: django.contrib.messages 8 :synopsis: Provides cookie- and session-based temporary message storage. 9 10 Django provides full support for cookie- and session-based messaging, for 11 both anonymous and authenticated clients. The messages framework allows you 12 to temporarily store messages in one request and retrieve them for display 13 in a subsequent request (usually the next one). Every message is tagged 14 with a specific ``level`` that determines its priority (e.g., ``info``, 15 ``warning``, or ``error``). 16 17 .. versionadded:: 1.2 18 The messages framework was added. 19 20 Enabling messages 21 ================= 22 23 Messages are implemented through a :ref:`middleware <ref-middleware>` 24 class and corresponding :ref:`context processor <ref-templates-api>`. 25 26 To enable message functionality, do the following: 27 28 * Edit the :setting:`MIDDLEWARE_CLASSES` setting and make sure 29 it contains ``'django.contrib.messages.middleware.MessageMiddleware'``. 30 31 If you are using a :ref:`storage backend <storage-backends>` that relies 32 on :ref:`sessions <topics-http-sessions>` (the default), 33 ``'django.contrib.sessions.middleware.SessionMiddleware'`` must be 34 enabled and appear before ``MessageMiddleware`` in your 35 :setting:`MIDDLEWARE_CLASSES`. 36 37 * Edit the :setting:`TEMPLATE_CONTEXT_PROCESSORS` setting and make sure 38 it contains ``'django.contrib.messages.context_processors.messages'``. 39 40 * Add ``'django.contrib.messages'`` to your :setting:`INSTALLED_APPS` 41 setting 42 43 The default ``settings.py`` created by ``django-admin.py startproject`` has 44 ``MessageMiddleware`` activated and the ``django.contrib.messages`` app 45 installed. Also, the default value for :setting:`TEMPLATE_CONTEXT_PROCESSORS` 46 contains ``'django.contrib.messages.context_processors.messages'``. 47 48 If you don't want to use messages, you can remove the 49 ``MessageMiddleware`` line from :setting:`MIDDLEWARE_CLASSES`, the ``messages`` 50 context processor from :setting:`TEMPLATE_CONTEXT_PROCESSORS` and 51 ``'django.contrib.messages'`` from your :setting:`INSTALLED_APPS`. 52 53 Configuring the message engine 54 ============================== 55 56 By default, Django first tries to store messages in a lightweight cookie, 57 and falls back to storing any messages that don't fit in the cookie in a 58 session variable. 59 60 .. _storage-backends: 61 62 Storage backends 63 ---------------- 64 65 The messages framework can use different backends to store temporary messages. 66 To change which backend is being used, add a `MESSAGE_STORAGE`_ to your 67 settings, referencing the module and class of the storage class. For 68 example:: 69 70 MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage' 71 72 The value should be the full path of the desired storage class. 73 74 Four storage classes are included: 75 76 ``'django.contrib.messages.storage.session.SessionStorage'`` 77 This class stores all messages inside of the request's session. It 78 requires Django's ``contrib.session`` application. 79 80 ``'django.contrib.messages.storage.cookie.CookieStorage'`` 81 This class stores the message data in a cookie (signed with a secret hash to 82 prevent manipulation) to persist notifications across requests. Old messages 83 are dropped if the cookie data size would exceed 4096 bytes. 84 85 ``'django.contrib.messages.storage.fallback.FallbackStorage'`` 86 This class first uses CookieStorage for all messages, falling back to using 87 SessionStorage for the messages that could not fit in a single cookie. 88 89 Since it is uses SessionStorage, it also requires Django's 90 ``contrib.session`` application. 91 92 ``'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'`` 93 This is the default temporary storage class. 94 95 This class extends FallbackStorage and adds compatibility methods to 96 to retrieve any messages stored in the user Message model by code that 97 has not yet been updated to use the new API. This storage is temporary 98 (because it makes use of code that is pending deprecation) and will be 99 removed in Django 1.4. At that time, the default storage will become 100 ``django.contrib.messages.storage.fallback.FallbackStorage``. For more 101 information, see `LegacyFallbackStorage`_ below. 102 103 To write your own storage class, subclass the ``BaseStorage`` class in 104 ``django.contrib.messages.storage.base`` and implement the ``_get`` and 105 ``_store`` methods. 106 107 LegacyFallbackStorage 108 ^^^^^^^^^^^^^^^^^^^^^ 109 110 The ``LegacyFallbackStorage`` is a temporary tool to facilitate the transition 111 from the deprecated ``user.message_set`` API and will be removed in Django 1.4 112 according to Django's standard deprecation policy. For more information, see 113 the full :ref:`release process documentation <internals-release-process>`. 114 115 In addition to the functionality in the ``FallbackStorage``, it adds a custom, 116 read-only storage class that retrieves messages from the user ``Message`` model. 117 Any messages that were stored in the ``Message`` model (e.g., by code that has 118 not yet been updated to use the messages framework) will be retrieved first, 119 followed by those stored in a cookie and in the session, if any. Since messages 120 stored in the ``Message`` model do not have a concept of levels, they will be 121 assigned the ``INFO`` level by default. 122 123 Message levels 124 -------------- 125 126 The messages framework is based on a configurable level architecture similar 127 to that of the Python logging module. Message levels allow you to group 128 messages by type so they can be filtered or displayed differently in views and 129 templates. 130 131 The built-in levels (which can be imported from ``django.contrib.messages`` 132 directly) are: 133 134 =========== ======== 135 Constant Purpose 136 =========== ======== 137 ``DEBUG`` Development-related messages that will be ignored (or removed) in a production deployment 138 ``INFO`` Informational messages for the user 139 ``SUCCESS`` An action was successful, e.g. "Your profile was updated successfully" 140 ``WARNING`` A failure did not occur but may be imminent 141 ``ERROR`` An action was **not** successful or some other failure occurred 142 =========== ======== 143 144 The `MESSAGE_LEVEL`_ setting can be used to change the minimum recorded 145 level. Attempts to add messages of a level less than this will be ignored. 146 147 Message tags 148 ------------ 149 150 Message tags are a string representation of the message level plus any 151 extra tags that were added directly in the view (see 152 `Adding extra message tags`_ below for more details). Tags are stored in a 153 string and are separated by spaces. Typically, message tags 154 are used as CSS classes to customize message style based on message type. By 155 default, each level has a single tag that's a lowercase version of its own 156 constant: 157 158 ============== =========== 159 Level Constant Tag 160 ============== =========== 161 ``DEBUG`` ``debug`` 162 ``INFO`` ``info`` 163 ``SUCCESS`` ``success`` 164 ``WARNING`` ``warning`` 165 ``ERROR`` ``error`` 166 ============== =========== 167 168 To change the default tags for a message level (either built-in or custom), 169 set the `MESSAGE_TAGS`_ setting to a dictionary containing the levels 170 you wish to change. As this extends the default tags, you only need to provide 171 tags for the levels you wish to override:: 172 173 from django.contrib.messages import constants as messages 174 MESSAGE_TAGS = { 175 messages.INFO: '', 176 50: 'critical', 177 } 178 179 Using messages in views and templates 180 ===================================== 181 182 Adding a message 183 ---------------- 184 185 To add a message, call:: 186 187 from django.contrib import messages 188 messages.add_message(request, messages.INFO, 'Hello world.') 189 190 Some shortcut methods provide a standard way to add messages with commonly 191 used tags (which are usually represented as HTML classes for the message):: 192 193 messages.debug(request, '%s SQL statements were executed.' % count) 194 messages.info(request, 'Three credits remain in your account.') 195 messages.success(request, 'Profile details updated.') 196 messages.warning(request, 'Your account expires in three days.') 197 messages.error(request, 'Document deleted.') 198 199 Displaying messages 200 ------------------- 201 202 In your template, use something like:: 203 204 {% if messages %} 205 <ul class="messages"> 206 {% for message in messages %} 207 <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li> 208 {% endfor %} 209 </ul> 210 {% endif %} 211 212 If you're using the context processor, your template should be rendered with a 213 ``RequestContext``. Otherwise, ensure ``messages`` is available to 214 the template context. 215 216 Creating custom message levels 217 ------------------------------ 218 219 Messages levels are nothing more than integers, so you can define your own 220 level constants and use them to create more customized user feedback, e.g.:: 221 222 CRITICAL = 50 223 224 def my_view(request): 225 messages.add_message(request, CRITICAL, 'A serious error occurred.') 226 227 When creating custom message levels you should be careful to avoid overloading 228 existing levels. The values for the built-in levels are: 229 230 ============== ===== 231 Level Constant Value 232 ============== ===== 233 ``DEBUG`` 10 234 ``INFO`` 20 235 ``SUCCESS`` 25 236 ``WARNING`` 30 237 ``ERROR`` 40 238 ============== ===== 239 240 If you need to identify the custom levels in your HTML or CSS, you need to 241 provide a mapping via the `MESSAGE_TAGS`_ setting. 242 243 **Note:** If you are creating a reusable application, it is recommended to use 244 only the built-in `message levels`_ and not rely on any custom levels. 245 246 Changing the minimum recorded level per-request 247 ----------------------------------------------- 248 249 The minimum recorded level can be set per request by changing the ``level`` 250 attribute of the messages storage instance:: 251 252 from django.contrib import messages 253 254 # Change the messages level to ensure the debug message is added. 255 messages.get_messages(request).level = messages.DEBUG 256 messages.debug(request, 'Test message...') 257 258 # In another request, record only messages with a level of WARNING and higher 259 messages.get_messages(request).level = messages.WARNING 260 messages.success(request, 'Your profile was updated.') # ignored 261 messages.warning(request, 'Your account is about to expire.') # recorded 262 263 # Set the messages level back to default. 264 messages.get_messages(request).level = None 265 266 For more information on how the minimum recorded level functions, see 267 `Message levels`_ above. 268 269 Adding extra message tags 270 ------------------------- 271 272 For more direct control over message tags, you can optionally provide a string 273 containing extra tags to any of the add methods:: 274 275 messages.add_message(request, messages.INFO, 'Over 9000!', 276 extra_tags='dragonball') 277 messages.error(request, 'Email box full', extra_tags='email') 278 279 Extra tags are added before the default tag for that level and are space 280 separated. 281 282 Failing silently when the message framework is disabled 283 ------------------------------------------------------- 284 285 If you're writing a reusable app (or other piece of code) and want to include 286 messaging functionality, but don't want to require your users to enable it 287 if they don't want to, you may pass an additional keyword argument 288 ``fail_silently=True`` to any of the ``add_message`` family of methods. For 289 example:: 290 291 messages.add_message(request, messages.SUCCESS, 'Profile details updated.', 292 fail_silently=True) 293 messages.info(request, 'Hello world.', fail_silently=True) 294 295 Internally, Django uses this functionality in the create, update, and delete 296 :ref:`generic views <topics-generic-views>` so that they work even if the 297 message framework is disabled. 298 299 Expiration of messages 300 ====================== 301 302 The messages are marked to be cleared when the storage instance is iterated 303 (and cleared when the response is processed). 304 305 To avoid the messages being cleared, you can set the messages storage to 306 ``False`` after iterating:: 307 308 storage = messages.get_messages(request) 309 for message in storage: 310 do_something_with(message) 311 storage.used = False 312 313 Behavior of parallel requests 314 ============================= 315 316 Due to the way cookies (and hence sessions) work, **the behavior of any 317 backends that make use of cookies or sessions is undefined when the same 318 client makes multiple requests that set or get messages in parallel**. For 319 example, if a client initiates a request that creates a message in one window 320 (or tab) and then another that fetches any uniterated messages in another 321 window, before the first window redirects, the message may appear in the 322 second window instead of the first window where it may be expected. 323 324 In short, when multiple simultaneous requests from the same client are 325 involved, messages are not guaranteed to be delivered to the same window that 326 created them nor, in some cases, at all. Note that this is typically not a 327 problem in most applications and will become a non-issue in HTML5, where each 328 window/tab will have its own browsing context. 329 330 Settings 331 ======== 332 333 A few :ref:`Django settings <ref-settings>` give you control over message behavior: 334 335 MESSAGE_LEVEL 336 ------------- 337 338 Default: ``messages.INFO`` 339 340 Important: If you override this in your settings file and rely on any of the built-in 341 constants, you must import the constants module directly to avoid the potential 342 for circular imports. 343 344 This sets the minimum message that will be saved in the message storage. See 345 `Message levels`_ above for more details. 346 347 MESSAGE_STORAGE 348 --------------- 349 350 Default: ``'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'`` 351 352 Controls where Django stores message data. Valid values are: 353 354 * ``'django.contrib.messages.storage.fallback.FallbackStorage'`` 355 * ``'django.contrib.messages.storage.session.SessionStorage'`` 356 * ``'django.contrib.messages.storage.cookie.CookieStorage'`` 357 * ``'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'`` 358 359 See `Storage backends`_ for more details. 360 361 MESSAGE_TAGS 362 ------------ 363 364 Default:: 365 366 {messages.DEBUG: 'debug', 367 messages.INFO: 'info', 368 messages.SUCCESS: 'success', 369 messages.WARNING: 'warning', 370 messages.ERROR: 'error',} 371 372 Important: If you override this in your settings file and rely on any of the built-in 373 constants, you must import the constants module directly to avoid the potential 374 for circular imports. 375 376 This sets the mapping of message level to message tag, which is typically 377 rendered as a CSS class in HTML. If you specify a value, it will extend 378 the default. This means you only have to specify those values which you need 379 to override. See `Displaying messages`_ above for more details. 380 381 .. _Django settings: ../settings/ -
docs/ref/middleware.txt
diff -r 1fb6476dce9d docs/ref/middleware.txt
a b 139 139 content for each user. See the :ref:`internationalization documentation 140 140 <topics-i18n>`. 141 141 142 Message middleware 143 ------------------ 144 145 .. module:: django.contrib.messages.middleware 146 :synopsis: Message middleware. 147 148 .. class:: django.contrib.messages.middleware.MessageMiddleware 149 150 Enables cookie- and session-based message support. See the 151 :ref:`messages documentation <ref-contrib-messages>`. 152 142 153 Session middleware 143 154 ------------------ 144 155 -
docs/ref/settings.txt
diff -r 1fb6476dce9d docs/ref/settings.txt
a b 812 812 813 813 .. setting:: MIDDLEWARE_CLASSES 814 814 815 MESSAGE_LEVEL 816 ------------- 817 818 .. versionadded:: 1.2 819 820 Default: `messages.INFO` 821 822 Sets the minimum message level that will be recorded by the messages 823 framework. See the :ref:`messages documentation <ref-contrib-messages>` for 824 more details. 825 826 MESSAGE_STORAGE 827 --------------- 828 829 .. versionadded:: 1.2 830 831 Default: ``'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'`` 832 833 Controls where Django stores message data. See the 834 :ref:`messages documentation <ref-contrib-messages>` for more details. 835 836 MESSAGE_TAGS 837 ------------ 838 839 .. versionadded:: 1.2 840 841 Default:: 842 843 {messages.DEBUG: 'debug', 844 messages.INFO: 'info', 845 messages.SUCCESS: 'success', 846 messages.WARNING: 'warning', 847 messages.ERROR: 'error',} 848 849 Sets the mapping of message levels to message tags. See the 850 :ref:`messages documentation <ref-contrib-messages>` for more details. 851 815 852 MIDDLEWARE_CLASSES 816 853 ------------------ 817 854 … … 820 857 ('django.middleware.common.CommonMiddleware', 821 858 'django.contrib.sessions.middleware.SessionMiddleware', 822 859 'django.middleware.csrf.CsrfViewMiddleware', 823 'django.contrib.auth.middleware.AuthenticationMiddleware',) 860 'django.contrib.auth.middleware.AuthenticationMiddleware', 861 'django.contrib.messages.middleware.MessageMiddleware',) 824 862 825 863 A tuple of middleware classes to use. See :ref:`topics-http-middleware`. 826 864 … … 1059 1097 ("django.core.context_processors.auth", 1060 1098 "django.core.context_processors.debug", 1061 1099 "django.core.context_processors.i18n", 1062 "django.core.context_processors.media") 1100 "django.core.context_processors.media", 1101 "django.contrib.messages.context_processors.messages") 1063 1102 1064 1103 A tuple of callables that are used to populate the context in ``RequestContext``. 1065 1104 These callables take a request object as their argument and return a dictionary -
docs/ref/templates/api.txt
diff -r 1fb6476dce9d docs/ref/templates/api.txt
a b 311 311 ("django.core.context_processors.auth", 312 312 "django.core.context_processors.debug", 313 313 "django.core.context_processors.i18n", 314 "django.core.context_processors.media") 314 "django.core.context_processors.media", 315 "django.contrib.messages.context_processors.messages") 315 316 316 317 .. versionadded:: 1.2 317 318 In addition to these, ``RequestContext`` always uses … … 320 321 in case of accidental misconfiguration, it is deliberately hardcoded in and 321 322 cannot be turned off by the :setting:`TEMPLATE_CONTEXT_PROCESSORS` setting. 322 323 324 .. versionadded:: 1.2 325 The ``'messages'`` context processor was added. For more information, see 326 the :ref:`messages documentation <ref-contrib-messages>`. 327 323 328 Each processor is applied in order. That means, if one processor adds a 324 329 variable to the context and a second processor adds a variable with the same 325 330 name, the second will override the first. The default processors are explained … … 365 370 logged-in user (or an ``AnonymousUser`` instance, if the client isn't 366 371 logged in). 367 372 368 * ``messages`` -- A list of messages (as strings) for the currently 369 logged-in user. Behind the scenes, this calls 370 ``request.user.get_and_delete_messages()`` for every request. That method 371 collects the user's messages and deletes them from the database. 372 373 Note that messages are set with ``user.message_set.create``. 373 * ``messages`` -- A list of messages (as strings) that have been set 374 via the :ref:`messages framework <ref-contrib-messages>`. 374 375 375 376 * ``perms`` -- An instance of 376 377 ``django.core.context_processors.PermWrapper``, representing the 377 378 permissions that the currently logged-in user has. 378 379 380 .. versionchanged:: 1.2 381 Prior to version 1.2, the ``messages`` variable was a lazy accessor for 382 ``user.get_and_delete_messages()``. It has been changed to include any 383 messages added via the :ref:`messages framework <ref-contrib-messages`. 384 379 385 django.core.context_processors.debug 380 386 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 381 387 … … 427 433 :class:`~django.http.HttpRequest`. Note that this processor is not enabled by default; 428 434 you'll have to activate it. 429 435 436 django.contrib.messages.context_processors.messages 437 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 438 439 If :setting:`TEMPLATE_CONTEXT_PROCESSORS` contains this processor, every 440 ``RequestContext`` will contain a single additional variable: 441 442 * ``messages`` -- A list of messages (as strings) that have been set 443 via the user model (using ``user.message_set.create``) or through 444 the :ref:`messages framework <ref-contrib-messages>`. 445 446 .. versionadded:: 1.2 447 This template context variable was previously supplied by the ``'auth'`` 448 context processor. For backwards compatibility the ``'auth'`` context 449 processor will continue to supply the ``messages`` variable until Django 450 1.4. If you use the ``messages`` variable, your project will work with 451 either (or both) context processors, but it is recommended to add 452 ``django.contrib.messages.context_processors.messages`` so your project 453 will be prepared for the future upgrade. 454 430 455 Writing your own context processors 431 456 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 432 457 -
docs/releases/1.2.txt
diff -r 1fb6476dce9d docs/releases/1.2.txt
a b 72 72 Features deprecated in 1.2 73 73 ========================== 74 74 75 None. 75 User Messages API 76 ----------------- 77 78 The API for storing messages in the user ``Message`` model (via 79 ``user.message_set.create``) is now deprecated and will be removed in Django 80 1.4 according to the standard :ref:`release process <internals-release-process>`. 81 82 To upgrade your code, you need to replace any instances of:: 83 84 user.message_set.create('a message') 85 86 with the following:: 87 88 from django.contrib import messages 89 messages.add_message(request, messages.INFO, 'a message') 90 91 Additionally, if you make use of the method, you need to replace the 92 following:: 93 94 for message in user.get_and_delete_messages(): 95 ... 96 97 with:: 98 99 from django.contrib import messages 100 for message in messages.get_messages(request): 101 ... 102 103 For more information, see the full 104 :ref:`messages documentation <ref-contrib-messages>`. You should begin to 105 update your code to use the new API immediately. 76 106 77 107 What's new in Django 1.2 78 108 ======================== … … 107 137 :ref:`memory<topic-email-memory-backend>` - you can even configure all 108 138 email to be :ref:`thrown away<topic-email-console-backend>`. 109 139 140 Messages Framework 141 ------------------------- 142 143 Django now includes a robust and configurable 144 :ref:`messages framework <ref-contrib-messages>` with built-in support for 145 cookie- and session-based messaging, for both anonymous and authenticated 146 clients. The messages framework replaces the deprecated user message API and 147 allows you to temporarily store messages in one request and retrieve them for 148 display in a subsequent request (usually the next one). -
docs/topics/auth.txt
diff -r 1fb6476dce9d docs/topics/auth.txt
a b 23 23 user. 24 24 * Messages: A simple way to queue messages for given users. 25 25 26 .. deprecated:: 1.2 27 The Messages component of the auth system will be removed in Django 1.4. 28 26 29 Installation 27 30 ============ 28 31 … … 1289 1292 Messages 1290 1293 ======== 1291 1294 1295 .. deprecated:: 1.2 1296 This functionality will be removed in Django 1.4. It is recommended to use 1297 the :ref:`messages framework <ref-contrib-messages>` for new projects 1298 whenever possible. 1299 1292 1300 The message system is a lightweight way to queue messages for given users. 1293 1301 1294 1302 A message is associated with a :class:`~django.contrib.auth.models.User`. … … 1334 1342 </ul> 1335 1343 {% endif %} 1336 1344 1337 Note that :class:`~django.template.context.RequestContext` calls 1338 :meth:`~django.contrib.auth.models.User.get_and_delete_messages` behind the 1339 scenes, so any messages will be deleted even if you don't display them. 1345 .. versionchanged:: 1.2 1346 The ``messages`` template variable uses a backwards compatible method in the 1347 :ref:`messages framework <ref-contrib-messages>` to retrieve messages from 1348 both the user ``Message`` model and from the new framework. Unlike in 1349 previous revisions, the messages will not be erased unless they are actually 1350 displayed. 1340 1351 1341 1352 Finally, note that this messages framework only works with users in the user 1342 1353 database. To send messages to anonymous users, use the 1343 :ref:` session framework <topics-http-sessions>`.1354 :ref:`messages framework <ref-contrib-messages>`. 1344 1355 1345 1356 .. _authentication-backends: 1346 1357 -
new file tests/regressiontests/message_tests/models.py
diff -r 1fb6476dce9d tests/regressiontests/message_tests/models.py
- + 1 # empty models so tests are discovered 2 No newline at end of file -
new file tests/regressiontests/message_tests/templates/message_tests/show.html
diff -r 1fb6476dce9d tests/regressiontests/message_tests/templates/message_tests/show.html
- + 1 {% if messages %} 2 <ul class="messages"> 3 {% for message in messages %} 4 <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li> 5 {% endfor %} 6 </ul> 7 {% endif %} 8 No newline at end of file -
new file tests/regressiontests/message_tests/tests/__init__.py
diff -r 1fb6476dce9d tests/regressiontests/message_tests/tests/__init__.py
- + 1 from regressiontests.message_tests.tests.cookie import CookieTest 2 from regressiontests.message_tests.tests.fallback import FallbackTest 3 from regressiontests.message_tests.tests.middleware import MiddlewareTest 4 from regressiontests.message_tests.tests.session import SessionTest 5 from regressiontests.message_tests.tests.user_messages import UserMessagesTest, \ 6 LegacyFallbackTest -
new file tests/regressiontests/message_tests/tests/base.py
diff -r 1fb6476dce9d tests/regressiontests/message_tests/tests/base.py
- + 1 from django import http 2 from django.test import TestCase 3 from django.conf import settings 4 from django.utils.translation import ugettext_lazy 5 from django.contrib.messages import constants, utils 6 from django.contrib.messages.storage import Storage, base 7 from django.contrib.messages.storage.base import Message 8 from django.core.urlresolvers import reverse 9 from django.contrib.auth.models import User 10 from django.contrib.messages.api import AddMessageFailure 11 12 13 def add_level_messages(storage): 14 """ 15 Adds 6 messages from different levels (including a custom one) to a storage 16 instance. 17 """ 18 storage.add(constants.INFO, 'A generic info message') 19 storage.add(29, 'Some custom level') 20 storage.add(constants.DEBUG, 'A debugging message', extra_tags='extra-tag') 21 storage.add(constants.WARNING, 'A warning') 22 storage.add(constants.ERROR, 'An error') 23 storage.add(constants.SUCCESS, 'This was a triumph.') 24 25 26 class BaseTest(TestCase): 27 storage_class = Storage 28 restore_settings = ['MESSAGE_LEVEL', 'MESSAGE_TAGS'] 29 urls = 'regressiontests.message_tests.urls' 30 levels = { 31 'debug': constants.DEBUG, 32 'info': constants.INFO, 33 'success': constants.SUCCESS, 34 'warning': constants.WARNING, 35 'error': constants.ERROR, 36 } 37 def setUp(self): 38 self._remembered_settings = {} 39 for setting in self.restore_settings: 40 if hasattr(settings, setting): 41 self._remembered_settings[setting] = getattr(settings, setting) 42 delattr(settings._wrapped, setting) 43 # backup these manually because we do not want them deleted 44 self._middleware_classes = settings.MIDDLEWARE_CLASSES 45 self._template_context_processors = settings.TEMPLATE_CONTEXT_PROCESSORS 46 self._installed_apps = settings.INSTALLED_APPS 47 48 def tearDown(self): 49 for setting in self.restore_settings: 50 self.restore_setting(setting) 51 # restore these manually (see above) 52 settings.MIDDLEWARE_CLASSES = self._middleware_classes 53 settings.TEMPLATE_CONTEXT_PROCESSORS = self._template_context_processors 54 settings.INSTALLED_APPS = self._installed_apps 55 56 def restore_setting(self, setting): 57 if setting in self._remembered_settings: 58 value = self._remembered_settings.pop(setting) 59 setattr(settings, setting, value) 60 elif hasattr(settings, setting): 61 delattr(settings._wrapped, setting) 62 63 def get_request(self): 64 return http.HttpRequest() 65 66 def get_response(self): 67 return http.HttpResponse() 68 69 def get_storage(self, data=None): 70 """ 71 Returns the storage backend, setting its loaded data to the ``data`` 72 argument. 73 74 This method avoids the storage ``_get`` method from getting called so 75 that other parts of the storage backend can be tested independent of 76 the message retrieval logic. 77 """ 78 storage = self.storage_class(self.get_request()) 79 storage._loaded_data = data or [] 80 return storage 81 82 def test_add(self): 83 storage = self.get_storage() 84 self.assertFalse(storage.added_new) 85 storage.add(constants.INFO, 'Test message 1') 86 self.assert_(storage.added_new) 87 storage.add(constants.INFO, 'Test message 2', extra_tags='tag') 88 self.assertEqual(len(storage), 2) 89 90 def test_add_lazy_translation(self): 91 storage = self.get_storage() 92 response = self.get_response() 93 94 storage.add(constants.INFO, ugettext_lazy('lazy message')) 95 storage.update(response) 96 97 storing = self.stored_messages_count(storage, response) 98 self.assertEqual(storing, 1) 99 100 def test_no_update(self): 101 storage = self.get_storage() 102 response = self.get_response() 103 storage.update(response) 104 storing = self.stored_messages_count(storage, response) 105 self.assertEqual(storing, 0) 106 107 def test_add_update(self): 108 storage = self.get_storage() 109 response = self.get_response() 110 111 storage.add(constants.INFO, 'Test message 1') 112 storage.add(constants.INFO, 'Test message 1', extra_tags='tag') 113 storage.update(response) 114 115 storing = self.stored_messages_count(storage, response) 116 self.assertEqual(storing, 2) 117 118 def test_existing_add_read_update(self): 119 storage = self.get_existing_storage() 120 response = self.get_response() 121 122 storage.add(constants.INFO, 'Test message 3') 123 list(storage) # Simulates a read 124 storage.update(response) 125 126 storing = self.stored_messages_count(storage, response) 127 self.assertEqual(storing, 0) 128 129 def test_existing_read_add_update(self): 130 storage = self.get_existing_storage() 131 response = self.get_response() 132 133 list(storage) # Simulates a read 134 storage.add(constants.INFO, 'Test message 3') 135 storage.update(response) 136 137 storing = self.stored_messages_count(storage, response) 138 self.assertEqual(storing, 1) 139 140 def test_full_request_response_cycle(self): 141 """ 142 With the message middleware enabled, tests that messages are properly 143 stored and then retrieved across the full request/redirect/response 144 cycle. 145 """ 146 settings.MESSAGE_LEVEL = constants.DEBUG 147 data = { 148 'messages': ['Test message %d' % x for x in xrange(10)], 149 } 150 show_url = reverse('regressiontests.message_tests.views.show') 151 for level in ('debug', 'info', 'success', 'warning', 'error'): 152 add_url = reverse('regressiontests.message_tests.views.add', 153 args=(level,)) 154 response = self.client.post(add_url, data, follow=True) 155 self.assertRedirects(response, show_url) 156 self.assertTrue('messages' in response.context) 157 messages = [ 158 Message(self.levels[level], msg) for msg in data['messages'] 159 ] 160 self.assertEqual(list(response.context['messages']), messages) 161 for msg in data['messages']: 162 self.assertContains(response, msg) 163 164 def test_multiple_posts(self): 165 """ 166 Tests that messages persist properly when multiple POSTs are made 167 before a GET. 168 """ 169 settings.MESSAGE_LEVEL = constants.DEBUG 170 data = { 171 'messages': ['Test message %d' % x for x in xrange(10)], 172 } 173 show_url = reverse('regressiontests.message_tests.views.show') 174 messages = [] 175 for level in ('debug', 'info', 'success', 'warning', 'error'): 176 messages.extend( 177 [Message(self.levels[level], msg) for msg in data['messages']] 178 ) 179 add_url = reverse('regressiontests.message_tests.views.add', 180 args=(level,)) 181 self.client.post(add_url, data) 182 response = self.client.get(show_url) 183 self.assertTrue('messages' in response.context) 184 self.assertEqual(list(response.context['messages']), messages) 185 for msg in data['messages']: 186 self.assertContains(response, msg) 187 188 def test_middleware_disabled_auth_user(self): 189 """ 190 Tests that the messages API successfully falls back to using 191 user.message_set to store messages directly when the middleware is 192 disabled. 193 """ 194 settings.MESSAGE_LEVEL = constants.DEBUG 195 user = User.objects.create_user('test', 'test@example.com', 'test') 196 self.client.login(username='test', password='test') 197 settings.INSTALLED_APPS = list(settings.INSTALLED_APPS) 198 settings.INSTALLED_APPS.remove( 199 'django.contrib.messages', 200 ) 201 settings.MIDDLEWARE_CLASSES = list(settings.MIDDLEWARE_CLASSES) 202 settings.MIDDLEWARE_CLASSES.remove( 203 'django.contrib.messages.middleware.MessageMiddleware', 204 ) 205 settings.TEMPLATE_CONTEXT_PROCESSORS = \ 206 list(settings.TEMPLATE_CONTEXT_PROCESSORS) 207 settings.TEMPLATE_CONTEXT_PROCESSORS.remove( 208 'django.contrib.messages.context_processors.messages', 209 ) 210 data = { 211 'messages': ['Test message %d' % x for x in xrange(10)], 212 } 213 show_url = reverse('regressiontests.message_tests.views.show') 214 for level in ('debug', 'info', 'success', 'warning', 'error'): 215 add_url = reverse('regressiontests.message_tests.views.add', 216 args=(level,)) 217 response = self.client.post(add_url, data, follow=True) 218 self.assertRedirects(response, show_url) 219 self.assertTrue('messages' in response.context) 220 self.assertEqual(list(response.context['messages']), data['messages']) 221 for msg in data['messages']: 222 self.assertContains(response, msg) 223 224 def test_middleware_disabled_anon_user(self): 225 """ 226 Tests that, when the middleware is disabled and a user is not logged 227 in, an exception is raised when one attempts to store a message. 228 """ 229 settings.MESSAGE_LEVEL = constants.DEBUG 230 settings.INSTALLED_APPS = list(settings.INSTALLED_APPS) 231 settings.INSTALLED_APPS.remove( 232 'django.contrib.messages', 233 ) 234 settings.MIDDLEWARE_CLASSES = list(settings.MIDDLEWARE_CLASSES) 235 settings.MIDDLEWARE_CLASSES.remove( 236 'django.contrib.messages.middleware.MessageMiddleware', 237 ) 238 settings.TEMPLATE_CONTEXT_PROCESSORS = \ 239 list(settings.TEMPLATE_CONTEXT_PROCESSORS) 240 settings.TEMPLATE_CONTEXT_PROCESSORS.remove( 241 'django.contrib.messages.context_processors.messages', 242 ) 243 data = { 244 'messages': ['Test message %d' % x for x in xrange(10)], 245 } 246 show_url = reverse('regressiontests.message_tests.views.show') 247 for level in ('debug', 'info', 'success', 'warning', 'error'): 248 add_url = reverse('regressiontests.message_tests.views.add', 249 args=(level,)) 250 self.assertRaises(AddMessageFailure, self.client.post, add_url, data, follow=True) 251 252 def test_middleware_disabled_anon_user_fail_silently(self): 253 """ 254 Tests that, when the middleware is disabled and a user is not logged 255 in, an exception is raised when one attempts to store a message. 256 """ 257 settings.MESSAGE_LEVEL = constants.DEBUG 258 settings.INSTALLED_APPS = list(settings.INSTALLED_APPS) 259 settings.INSTALLED_APPS.remove( 260 'django.contrib.messages', 261 ) 262 settings.MIDDLEWARE_CLASSES = list(settings.MIDDLEWARE_CLASSES) 263 settings.MIDDLEWARE_CLASSES.remove( 264 'django.contrib.messages.middleware.MessageMiddleware', 265 ) 266 settings.TEMPLATE_CONTEXT_PROCESSORS = \ 267 list(settings.TEMPLATE_CONTEXT_PROCESSORS) 268 settings.TEMPLATE_CONTEXT_PROCESSORS.remove( 269 'django.contrib.messages.context_processors.messages', 270 ) 271 data = { 272 'messages': ['Test message %d' % x for x in xrange(10)], 273 'fail_silently': True, 274 } 275 show_url = reverse('regressiontests.message_tests.views.show') 276 for level in ('debug', 'info', 'success', 'warning', 'error'): 277 add_url = reverse('regressiontests.message_tests.views.add', 278 args=(level,)) 279 response = self.client.post(add_url, data, follow=True) 280 self.assertRedirects(response, show_url) 281 self.assertTrue('messages' in response.context) 282 self.assertEqual(list(response.context['messages']), []) 283 284 def stored_messages_count(self, storage, response): 285 """ 286 Returns the number of messages being stored after a 287 ``storage.update()`` call. 288 """ 289 raise NotImplementedError('This method must be set by a subclass.') 290 291 def test_get(self): 292 raise NotImplementedError('This method must be set by a subclass.') 293 294 def get_existing_storage(self): 295 return self.get_storage([Message(constants.INFO, 'Test message 1'), 296 Message(constants.INFO, 'Test message 2', 297 extra_tags='tag')]) 298 299 def test_existing_read(self): 300 """ 301 Tests that reading the existing storage doesn't cause the data to be 302 lost. 303 """ 304 storage = self.get_existing_storage() 305 self.assertFalse(storage.used) 306 # After iterating the storage engine directly, the used flag is set. 307 data = list(storage) 308 self.assert_(storage.used) 309 # The data does not disappear because it has been iterated. 310 self.assertEqual(data, list(storage)) 311 312 def test_existing_add(self): 313 storage = self.get_existing_storage() 314 self.assertFalse(storage.added_new) 315 storage.add(constants.INFO, 'Test message 3') 316 self.assert_(storage.added_new) 317 318 def test_default_level(self): 319 storage = self.get_storage() 320 add_level_messages(storage) 321 self.assertEqual(len(storage), 5) 322 323 def test_low_level(self): 324 storage = self.get_storage() 325 storage.level = 5 326 add_level_messages(storage) 327 self.assertEqual(len(storage), 6) 328 329 def test_high_level(self): 330 storage = self.get_storage() 331 storage.level = 30 332 add_level_messages(storage) 333 self.assertEqual(len(storage), 2) 334 335 def test_settings_level(self): 336 settings.MESSAGE_LEVEL = 29 337 storage = self.get_storage() 338 add_level_messages(storage) 339 self.assertEqual(len(storage), 3) 340 341 def test_tags(self): 342 storage = self.get_storage() 343 storage.level = 0 344 add_level_messages(storage) 345 tags = [msg.tags for msg in storage] 346 self.assertEqual(tags, 347 ['info', '', 'extra-tag debug', 'warning', 'error', 348 'success']) 349 350 def test_custom_tags(self): 351 settings.MESSAGE_TAGS = { 352 constants.INFO: 'info', 353 constants.DEBUG: '', 354 constants.WARNING: '', 355 constants.ERROR: 'bad', 356 29: 'custom', 357 } 358 # LEVEL_TAGS is a constant defined in the 359 # django.contrib.messages.storage.base module, so after changing 360 # settings.MESSAGE_TAGS, we need to update that constant too. 361 base.LEVEL_TAGS = utils.get_level_tags() 362 try: 363 storage = self.get_storage() 364 storage.level = 0 365 add_level_messages(storage) 366 tags = [msg.tags for msg in storage] 367 self.assertEqual(tags, 368 ['info', 'custom', 'extra-tag', '', 'bad', 'success']) 369 finally: 370 # Ensure the level tags constant is put back like we found it. 371 self.restore_setting('MESSAGE_TAGS') 372 base.LEVEL_TAGS = utils.get_level_tags() -
new file tests/regressiontests/message_tests/tests/cookie.py
diff -r 1fb6476dce9d tests/regressiontests/message_tests/tests/cookie.py
- + 1 from django.contrib.messages import constants 2 from regressiontests.message_tests.tests.base import BaseTest 3 from django.contrib.messages.storage.cookie import CookieStorage, \ 4 MessageEncoder, MessageDecoder 5 from django.contrib.messages.storage.base import Message 6 from django.utils import simplejson as json 7 8 def set_cookie_data(storage, messages, invalid=False, encode_empty=False): 9 """ 10 Sets ``request.COOKIES`` with the encoded data and removes the storage 11 backend's loaded data cache. 12 """ 13 encoded_data = storage._encode(messages, encode_empty=encode_empty) 14 if invalid: 15 # Truncate the first character so that the hash is invalid. 16 encoded_data = encoded_data[1:] 17 storage.request.COOKIES = {CookieStorage.cookie_name: encoded_data} 18 if hasattr(storage, '_loaded_data'): 19 del storage._loaded_data 20 21 22 def stored_cookie_messages_count(storage, response): 23 """ 24 Returns an integer containing the number of messages stored. 25 """ 26 # Get a list of cookies, excluding ones with a max-age of 0 (because 27 # they have been marked for deletion). 28 cookie = response.cookies.get(storage.cookie_name) 29 if not cookie or cookie['max-age'] == 0: 30 return 0 31 data = storage._decode(cookie.value) 32 if not data: 33 return 0 34 if data[-1] == CookieStorage.not_finished: 35 data.pop() 36 return len(data) 37 38 39 class CookieTest(BaseTest): 40 storage_class = CookieStorage 41 42 def stored_messages_count(self, storage, response): 43 return stored_cookie_messages_count(storage, response) 44 45 def test_get(self): 46 storage = self.storage_class(self.get_request()) 47 # Set initial data. 48 example_messages = ['test', 'me'] 49 set_cookie_data(storage, example_messages) 50 # Test that the message actually contains what we expect. 51 self.assertEqual(list(storage), example_messages) 52 53 def test_get_bad_cookie(self): 54 request = self.get_request() 55 storage = self.storage_class(request) 56 # Set initial (invalid) data. 57 example_messages = ['test', 'me'] 58 set_cookie_data(storage, example_messages, invalid=True) 59 # Test that the message actually contains what we expect. 60 self.assertEqual(list(storage), []) 61 62 def test_max_cookie_length(self): 63 """ 64 Tests that, if the data exceeds what is allowed in a cookie, older 65 messages are removed before saving (and returned by the ``update`` 66 method). 67 """ 68 storage = self.get_storage() 69 response = self.get_response() 70 71 for i in range(5): 72 storage.add(constants.INFO, str(i) * 900) 73 unstored_messages = storage.update(response) 74 75 cookie_storing = self.stored_messages_count(storage, response) 76 self.assertEqual(cookie_storing, 4) 77 78 self.assertEqual(len(unstored_messages), 1) 79 self.assert_(unstored_messages[0].message == '0' * 900) 80 81 def test_json_encoder_decoder(self): 82 """ 83 Tests that an complex nested data structure containing Message 84 instances is properly encoded/decoded by the custom JSON 85 encoder/decoder classes. 86 """ 87 messages = [ 88 { 89 'message': Message(constants.INFO, 'Test message'), 90 'message_list': [ 91 Message(constants.INFO, 'message %s') for x in xrange(5) 92 ] + [{'another-message': Message(constants.ERROR, 'error')}], 93 }, 94 Message(constants.INFO, 'message %s'), 95 ] 96 encoder = MessageEncoder(separators=(',', ':')) 97 value = encoder.encode(messages) 98 decoded_messages = json.loads(value, cls=MessageDecoder) 99 self.assertEqual(messages, decoded_messages) -
new file tests/regressiontests/message_tests/tests/fallback.py
diff -r 1fb6476dce9d tests/regressiontests/message_tests/tests/fallback.py
- + 1 from django.contrib.messages import constants 2 from django.contrib.messages.storage.fallback import FallbackStorage, \ 3 CookieStorage 4 from regressiontests.message_tests.tests.base import BaseTest 5 from regressiontests.message_tests.tests.cookie import set_cookie_data, \ 6 stored_cookie_messages_count 7 from regressiontests.message_tests.tests.session import set_session_data, \ 8 stored_session_messages_count 9 10 11 class FallbackTest(BaseTest): 12 storage_class = FallbackStorage 13 14 def get_request(self): 15 self.session = {} 16 request = super(FallbackTest, self).get_request() 17 request.session = self.session 18 return request 19 20 def get_cookie_storage(self, storage): 21 return storage.storages[-2] 22 23 def get_session_storage(self, storage): 24 return storage.storages[-1] 25 26 def stored_cookie_messages_count(self, storage, response): 27 return stored_cookie_messages_count(self.get_cookie_storage(storage), 28 response) 29 30 def stored_session_messages_count(self, storage, response): 31 return stored_session_messages_count(self.get_session_storage(storage)) 32 33 def stored_messages_count(self, storage, response): 34 """ 35 Return the storage totals from both cookie and session backends. 36 """ 37 total = (self.stored_cookie_messages_count(storage, response) + 38 self.stored_session_messages_count(storage, response)) 39 return total 40 41 def test_get(self): 42 request = self.get_request() 43 storage = self.storage_class(request) 44 cookie_storage = self.get_cookie_storage(storage) 45 46 # Set initial cookie data. 47 example_messages = [str(i) for i in range(5)] 48 set_cookie_data(cookie_storage, example_messages) 49 50 # Overwrite the _get method of the fallback storage to prove it is not 51 # used (it would cause a TypeError: 'NoneType' object is not callable). 52 self.get_session_storage(storage)._get = None 53 54 # Test that the message actually contains what we expect. 55 self.assertEqual(list(storage), example_messages) 56 57 def test_get_empty(self): 58 request = self.get_request() 59 storage = self.storage_class(request) 60 61 # Overwrite the _get method of the fallback storage to prove it is not 62 # used (it would cause a TypeError: 'NoneType' object is not callable). 63 self.get_session_storage(storage)._get = None 64 65 # Test that the message actually contains what we expect. 66 self.assertEqual(list(storage), []) 67 68 def test_get_fallback(self): 69 request = self.get_request() 70 storage = self.storage_class(request) 71 cookie_storage = self.get_cookie_storage(storage) 72 session_storage = self.get_session_storage(storage) 73 74 # Set initial cookie and session data. 75 example_messages = [str(i) for i in range(5)] 76 set_cookie_data(cookie_storage, example_messages[:4] + 77 [CookieStorage.not_finished]) 78 set_session_data(session_storage, example_messages[4:]) 79 80 # Test that the message actually contains what we expect. 81 self.assertEqual(list(storage), example_messages) 82 83 def test_get_fallback_only(self): 84 request = self.get_request() 85 storage = self.storage_class(request) 86 cookie_storage = self.get_cookie_storage(storage) 87 session_storage = self.get_session_storage(storage) 88 89 # Set initial cookie and session data. 90 example_messages = [str(i) for i in range(5)] 91 set_cookie_data(cookie_storage, [CookieStorage.not_finished], 92 encode_empty=True) 93 set_session_data(session_storage, example_messages) 94 95 # Test that the message actually contains what we expect. 96 self.assertEqual(list(storage), example_messages) 97 98 def test_flush_used_backends(self): 99 request = self.get_request() 100 storage = self.storage_class(request) 101 cookie_storage = self.get_cookie_storage(storage) 102 session_storage = self.get_session_storage(storage) 103 104 # Set initial cookie and session data. 105 set_cookie_data(cookie_storage, ['cookie', CookieStorage.not_finished]) 106 set_session_data(session_storage, ['session']) 107 108 # When updating, previously used but no longer needed backends are 109 # flushed. 110 response = self.get_response() 111 list(storage) 112 storage.update(response) 113 session_storing = self.stored_session_messages_count(storage, response) 114 self.assertEqual(session_storing, 0) 115 116 def test_no_fallback(self): 117 """ 118 Confirms that: 119 120 (1) A short number of messages whose data size doesn't exceed what is 121 allowed in a cookie will all be stored in the CookieBackend. 122 123 (2) If the CookieBackend can store all messages, the SessionBackend 124 won't be written to at all. 125 """ 126 storage = self.get_storage() 127 response = self.get_response() 128 129 # Overwrite the _store method of the fallback storage to prove it isn't 130 # used (it would cause a TypeError: 'NoneType' object is not callable). 131 self.get_session_storage(storage)._store = None 132 133 for i in range(5): 134 storage.add(constants.INFO, str(i) * 100) 135 storage.update(response) 136 137 cookie_storing = self.stored_cookie_messages_count(storage, response) 138 self.assertEqual(cookie_storing, 5) 139 session_storing = self.stored_session_messages_count(storage, response) 140 self.assertEqual(session_storing, 0) 141 142 def test_session_fallback(self): 143 """ 144 Confirms that, if the data exceeds what is allowed in a cookie, older 145 messages which did not "fit" are stored in the SessionBackend. 146 """ 147 storage = self.get_storage() 148 response = self.get_response() 149 150 for i in range(5): 151 storage.add(constants.INFO, str(i) * 900) 152 storage.update(response) 153 154 cookie_storing = self.stored_cookie_messages_count(storage, response) 155 self.assertEqual(cookie_storing, 4) 156 session_storing = self.stored_session_messages_count(storage, response) 157 self.assertEqual(session_storing, 1) 158 159 def test_session_fallback_only(self): 160 """ 161 Confirms that large messages, none of which fit in a cookie, are stored 162 in the SessionBackend (and nothing is stored in the CookieBackend). 163 """ 164 storage = self.get_storage() 165 response = self.get_response() 166 167 storage.add(constants.INFO, 'x' * 5000) 168 storage.update(response) 169 170 cookie_storing = self.stored_cookie_messages_count(storage, response) 171 self.assertEqual(cookie_storing, 0) 172 session_storing = self.stored_session_messages_count(storage, response) 173 self.assertEqual(session_storing, 1) -
new file tests/regressiontests/message_tests/tests/middleware.py
diff -r 1fb6476dce9d tests/regressiontests/message_tests/tests/middleware.py
- + 1 import unittest 2 from django import http 3 from django.contrib.messages.middleware import MessageMiddleware 4 5 6 class MiddlewareTest(unittest.TestCase): 7 8 def setUp(self): 9 self.middleware = MessageMiddleware() 10 11 def test_response_without_messages(self): 12 """ 13 Makes sure that the response middleware is tolerant of messages not 14 existing on request. 15 """ 16 request = http.HttpRequest() 17 response = http.HttpResponse() 18 self.middleware.process_response(request, response) -
new file tests/regressiontests/message_tests/tests/session.py
diff -r 1fb6476dce9d tests/regressiontests/message_tests/tests/session.py
- + 1 from regressiontests.message_tests.tests.base import BaseTest 2 from django.contrib.messages.storage.session import SessionStorage 3 4 5 def set_session_data(storage, messages): 6 """ 7 Sets the messages into the backend request's session and remove the 8 backend's loaded data cache. 9 """ 10 storage.request.session[storage.session_key] = messages 11 if hasattr(storage, '_loaded_data'): 12 del storage._loaded_data 13 14 15 def stored_session_messages_count(storage): 16 data = storage.request.session.get(storage.session_key, []) 17 return len(data) 18 19 20 class SessionTest(BaseTest): 21 storage_class = SessionStorage 22 23 def get_request(self): 24 self.session = {} 25 request = super(SessionTest, self).get_request() 26 request.session = self.session 27 return request 28 29 def stored_messages_count(self, storage, response): 30 return stored_session_messages_count(storage) 31 32 def test_get(self): 33 storage = self.storage_class(self.get_request()) 34 # Set initial data. 35 example_messages = ['test', 'me'] 36 set_session_data(storage, example_messages) 37 # Test that the message actually contains what we expect. 38 self.assertEqual(list(storage), example_messages) -
new file tests/regressiontests/message_tests/tests/user_messages.py
diff -r 1fb6476dce9d tests/regressiontests/message_tests/tests/user_messages.py
- + 1 from django import http 2 from django.contrib.auth.models import User 3 from django.contrib.messages.storage.user_messages import UserMessagesStorage,\ 4 LegacyFallbackStorage 5 from regressiontests.message_tests.tests.cookie import set_cookie_data 6 from regressiontests.message_tests.tests.fallback import FallbackTest 7 from django.test import TestCase 8 9 10 class UserMessagesTest(TestCase): 11 12 def setUp(self): 13 self.user = User.objects.create(username='tester') 14 15 def test_add(self): 16 storage = UserMessagesStorage(http.HttpRequest()) 17 self.assertRaises(NotImplementedError, storage.add, 'Test message 1') 18 19 def test_get_anonymous(self): 20 # Ensure that the storage still works if no user is attached to the 21 # request. 22 storage = UserMessagesStorage(http.HttpRequest()) 23 self.assertEqual(len(storage), 0) 24 25 def test_get(self): 26 storage = UserMessagesStorage(http.HttpRequest()) 27 storage.request.user = self.user 28 self.user.message_set.create(message='test message') 29 30 self.assertEqual(len(storage), 1) 31 self.assertEqual(list(storage)[0].message, 'test message') 32 33 34 class LegacyFallbackTest(FallbackTest, TestCase): 35 storage_class = LegacyFallbackStorage 36 37 def setUp(self): 38 super(LegacyFallbackTest, self).setUp() 39 self.user = User.objects.create(username='tester') 40 41 def get_request(self, *args, **kwargs): 42 request = super(LegacyFallbackTest, self).get_request(*args, **kwargs) 43 request.user = self.user 44 return request 45 46 def test_get_legacy_only(self): 47 request = self.get_request() 48 storage = self.storage_class(request) 49 self.user.message_set.create(message='user message') 50 51 # Test that the message actually contains what we expect. 52 self.assertEqual(len(storage), 1) 53 self.assertEqual(list(storage)[0].message, 'user message') 54 55 def test_get_legacy(self): 56 request = self.get_request() 57 storage = self.storage_class(request) 58 cookie_storage = self.get_cookie_storage(storage) 59 self.user.message_set.create(message='user message') 60 set_cookie_data(cookie_storage, ['cookie']) 61 62 # Test that the message actually contains what we expect. 63 self.assertEqual(len(storage), 2) 64 self.assertEqual(list(storage)[0].message, 'user message') 65 self.assertEqual(list(storage)[1], 'cookie') -
new file tests/regressiontests/message_tests/urls.py
diff -r 1fb6476dce9d tests/regressiontests/message_tests/urls.py
- + 1 from django.conf.urls.defaults import * 2 3 urlpatterns = patterns('regressiontests.message_tests.views', 4 ('^add/(debug|info|success|warning|error)/$', 'add'), 5 ('^show/$', 'show'), 6 ) -
new file tests/regressiontests/message_tests/views.py
diff -r 1fb6476dce9d tests/regressiontests/message_tests/views.py
- + 1 from django.contrib import messages 2 from django.core.urlresolvers import reverse 3 from django.http import HttpResponseRedirect 4 from django.shortcuts import render_to_response 5 from django.template import RequestContext 6 7 def add(request, message_type): 8 # don't default to False here, because we want to test that it defaults 9 # to False if unspecified 10 fail_silently = request.POST.get('fail_silently', None) 11 for msg in request.POST.getlist('messages'): 12 if fail_silently is not None: 13 getattr(messages, message_type)(request, msg, 14 fail_silently=fail_silently) 15 else: 16 getattr(messages, message_type)(request, msg) 17 show_url = reverse('regressiontests.message_tests.views.show') 18 return HttpResponseRedirect(show_url) 19 20 21 def show(request): 22 return render_to_response('message_tests/show.html', {}, 23 context_instance=RequestContext(request)) 24 No newline at end of file -
tests/runtests.py
diff -r 1fb6476dce9d tests/runtests.py
a b 28 28 'django.contrib.flatpages', 29 29 'django.contrib.redirects', 30 30 'django.contrib.sessions', 31 'django.contrib.messages', 31 32 'django.contrib.comments', 32 33 'django.contrib.admin', 33 34 ] … … 107 108 settings.MIDDLEWARE_CLASSES = ( 108 109 'django.contrib.sessions.middleware.SessionMiddleware', 109 110 'django.contrib.auth.middleware.AuthenticationMiddleware', 111 'django.contrib.messages.middleware.MessageMiddleware', 110 112 'django.middleware.common.CommonMiddleware', 111 113 ) 112 114 settings.SITE_ID = 1