Ticket #4604: django-contrib-messages.2.diff
File django-contrib-messages.2.diff, 98.5 KB (added by , 15 years ago) |
---|
-
AUTHORS
diff -r 70e75e8cd224 AUTHORS
a b 60 60 Ned Batchelder <http://www.nedbatchelder.com/> 61 61 batiste@dosimple.ch 62 62 Batman 63 Chris Beaven <http://smileychris.tactful.co.nz/> 63 64 Brian Beck <http://blog.brianbeck.com/> 64 65 Shannon -jj Behrens <http://jjinux.blogspot.com/> 65 66 Esdras Beleza <linux@esdrasbeleza.com> … … 299 300 Jason McBrayer <http://www.carcosa.net/jason/> 300 301 Kevin McConnell <kevin.mcconnell@gmail.com> 301 302 mccutchen@gmail.com 303 Tobias McNulty <http://www.caktusgroup.com/blog> 302 304 Christian Metts 303 305 michael.mcewan@gmail.com 304 306 michal@plovarna.cz … … 391 393 Jozko Skrablin <jozko.skrablin@gmail.com> 392 394 Ben Slavin <benjamin.slavin@gmail.com> 393 395 sloonz <simon.lipp@insa-lyon.fr> 394 SmileyChris <smileychris@gmail.com>395 396 Warren Smith <warren@wandrsmith.net> 396 397 smurf@smurf.noris.de 397 398 Vsevolod Solovyov -
django/conf/global_settings.py
diff -r 70e75e8cd224 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 ) … … 393 395 CSRF_COOKIE_NAME = 'csrftoken' 394 396 CSRF_COOKIE_DOMAIN = None 395 397 398 ############ 399 # MESSAGES # 400 ############ 401 402 # Class to use as messges backend 403 MESSAGE_STORAGE = 'django.contrib.messages.storage.user_messages.LegacyFallbackStorage' 404 405 # Default values of MESSAGE_LEVEL and MESSAGE_TAGS are defined within 406 # django.contrib.messages to avoid imports in this settings file. 407 396 408 ########### 397 409 # TESTING # 398 410 ########### -
django/conf/project_template/settings.py
diff -r 70e75e8cd224 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 70e75e8cd224 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 70e75e8cd224 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 70e75e8cd224 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 70e75e8cd224 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 70e75e8cd224 django/contrib/messages/__init__.py
- + 1 from api import * 2 from constants import * -
new file django/contrib/messages/api.py
diff -r 70e75e8cd224 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 MessageFailure(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 MessageFailure('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 70e75e8cd224 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 70e75e8cd224 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 70e75e8cd224 django/contrib/messages/middleware.py
- + 1 from django.conf import settings 2 from django.contrib.messages.storage import default_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 = default_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 70e75e8cd224 django/contrib/messages/models.py
- + 1 # Models module required so tests are discovered. -
new file django/contrib/messages/storage/__init__.py
diff -r 70e75e8cd224 django/contrib/messages/storage/__init__.py
- + 1 from django.conf import settings 2 from django.core.exceptions import ImproperlyConfigured 3 from django.utils.importlib import import_module 4 5 6 def get_storage(import_path): 7 """ 8 Imports the message storage class described by import_path, where 9 import_path is the full Python path to the class. 10 """ 11 try: 12 dot = import_path.rindex('.') 13 except ValueError: 14 raise ImproperlyConfigured("%s isn't a Python path." % import_path) 15 module, classname = import_path[:dot], import_path[dot + 1:] 16 try: 17 mod = import_module(module) 18 except ImportError, e: 19 raise ImproperlyConfigured('Error importing module %s: "%s"' % 20 (module, e)) 21 try: 22 return getattr(mod, classname) 23 except AttributeError: 24 raise ImproperlyConfigured('Module "%s" does not define a "%s" ' 25 'class.' % (module, classname)) 26 27 28 # Callable with the same interface as the storage classes i.e. accepts a 29 # 'request' object. It is wrapped in a lambda to stop 'settings' being used at 30 # the module level 31 default_storage = lambda request: get_storage(settings.MESSAGE_STORAGE)(request) -
new file django/contrib/messages/storage/base.py
diff -r 70e75e8cd224 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 95 and a flag indicating whether or not all the messages originally 96 intended to be stored in this storage were, in fact, stored and 97 retrieved; e.g., ``(messages, all_retrieved)``. 98 99 **This method must be implemented by a subclass.** 100 101 If it is possible to tell if the backend was not used (as opposed to 102 just containing no messages) then ``None`` should be returned in 103 place of ``messages``. 104 """ 105 raise NotImplementedError() 106 107 def _store(self, messages, response, *args, **kwargs): 108 """ 109 Stores a list of messages, returning a list of any messages which could 110 not be stored. 111 112 One type of object must be able to be stored, ``Message``. 113 114 **This method must be implemented by a subclass.** 115 """ 116 raise NotImplementedError() 117 118 def _prepare_messages(self, messages): 119 """ 120 Prepares a list of messages for storage. 121 """ 122 for message in messages: 123 message._prepare() 124 125 def update(self, response): 126 """ 127 Stores all unread messages. 128 129 If the backend has yet to be iterated, previously stored messages will 130 be stored again. Otherwise, only messages added after the last 131 iteration will be stored. 132 """ 133 self._prepare_messages(self._queued_messages) 134 if self.used: 135 return self._store(self._queued_messages, response) 136 elif self.added_new: 137 messages = self._loaded_messages + self._queued_messages 138 return self._store(messages, response) 139 140 def add(self, level, message, extra_tags=''): 141 """ 142 Queues a message to be stored. 143 144 The message is only queued if it contained something and its level is 145 not less than the recording level (``self.level``). 146 """ 147 if not message: 148 return 149 # Check that the message level is not less than the recording level. 150 level = int(level) 151 if level < self.level: 152 return 153 # Add the message. 154 self.added_new = True 155 message = Message(level, message, extra_tags=extra_tags) 156 self._queued_messages.append(message) 157 158 def _get_level(self): 159 """ 160 Returns the minimum recorded level. 161 162 The default level is the ``MESSAGE_LEVEL`` setting. If this is 163 not found, the ``INFO`` level is used. 164 """ 165 if not hasattr(self, '_level'): 166 self._level = getattr(settings, 'MESSAGE_LEVEL', constants.INFO) 167 return self._level 168 169 def _set_level(self, value=None): 170 """ 171 Sets a custom minimum recorded level. 172 173 If set to ``None``, the default level will be used (see the 174 ``_get_level`` method). 175 """ 176 if value is None and hasattr(self, '_level'): 177 del self._level 178 else: 179 self._level = int(value) 180 181 level = property(_get_level, _set_level, _set_level) -
new file django/contrib/messages/storage/cookie.py
diff -r 70e75e8cd224 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 from django.utils import simplejson as json 8 9 10 class MessageEncoder(json.JSONEncoder): 11 """ 12 Compactly serializes instances of the ``Message`` class as JSON. 13 """ 14 message_key = '__json_message' 15 16 def default(self, obj): 17 if isinstance(obj, Message): 18 message = [self.message_key, obj.level, obj.message] 19 if obj.extra_tags: 20 message.append(obj.extra_tags) 21 return message 22 return super(MessageEncoder, self).default(obj) 23 24 25 class MessageDecoder(json.JSONDecoder): 26 """ 27 Decodes JSON that includes serialized ``Message`` instances. 28 """ 29 30 def process_messages(self, obj): 31 if isinstance(obj, list) and obj: 32 if obj[0] == MessageEncoder.message_key: 33 return Message(*obj[1:]) 34 return [self.process_messages(item) for item in obj] 35 if isinstance(obj, dict): 36 return dict([(key, self.process_messages(value)) 37 for key, value in obj.iteritems()]) 38 return obj 39 40 def decode(self, s, **kwargs): 41 decoded = super(MessageDecoder, self).decode(s, **kwargs) 42 return self.process_messages(decoded) 43 44 45 class CookieStorage(BaseStorage): 46 """ 47 Stores messages in a cookie. 48 """ 49 cookie_name = 'messages' 50 max_cookie_size = 4096 51 not_finished = '__messagesnotfinished__' 52 53 def _get(self, *args, **kwargs): 54 """ 55 Retrieves a list of messages from the messages cookie. If the 56 not_finished sentinel value is found at the end of the message list, 57 remove it and return a result indicating that not all messages were 58 retrieved by this storage. 59 """ 60 data = self.request.COOKIES.get(self.cookie_name) 61 messages = self._decode(data) 62 all_retrieved = not (messages and messages[-1] == self.not_finished) 63 if messages and not all_retrieved: 64 # remove the sentinel value 65 messages.pop() 66 return messages, all_retrieved 67 68 def _update_cookie(self, encoded_data, response): 69 """ 70 Either sets the cookie with the encoded data if there is any data to 71 store, or deletes the cookie. 72 """ 73 if encoded_data: 74 response.set_cookie(self.cookie_name, encoded_data) 75 else: 76 response.delete_cookie(self.cookie_name) 77 78 def _store(self, messages, response, remove_oldest=True, *args, **kwargs): 79 """ 80 Stores the messages to a cookie, returning a list of any messages which 81 could not be stored. 82 83 If the encoded data is larger than ``max_cookie_size``, removes 84 messages until the data fits (these are the messages which are 85 returned), and add the not_finished sentinel value to indicate as much. 86 """ 87 unstored_messages = [] 88 encoded_data = self._encode(messages) 89 if self.max_cookie_size: 90 while encoded_data and len(encoded_data) > self.max_cookie_size: 91 if remove_oldest: 92 unstored_messages.append(messages.pop(0)) 93 else: 94 unstored_messages.insert(0, messages.pop()) 95 encoded_data = self._encode(messages + [self.not_finished], 96 encode_empty=unstored_messages) 97 self._update_cookie(encoded_data, response) 98 return unstored_messages 99 100 def _hash(self, value): 101 """ 102 Creates an HMAC/SHA1 hash based on the value and the project setting's 103 SECRET_KEY, modified to make it unique for the present purpose. 104 """ 105 key = 'django.contrib.messages' + settings.SECRET_KEY 106 return hmac.new(key, value, sha_constructor).hexdigest() 107 108 def _encode(self, messages, encode_empty=False): 109 """ 110 Returns an encoded version of the messages list which can be stored as 111 plain text. 112 113 Since the data will be retrieved from the client-side, the encoded data 114 also contains a hash to ensure that the data was not tampered with. 115 """ 116 if messages or encode_empty: 117 encoder = MessageEncoder(separators=(',', ':')) 118 value = encoder.encode(messages) 119 return '%s$%s' % (self._hash(value), value) 120 121 def _decode(self, data): 122 """ 123 Safely decodes a encoded text stream back into a list of messages. 124 125 If the encoded text stream contained an invalid hash or was in an 126 invalid format, ``None`` is returned. 127 """ 128 if not data: 129 return None 130 bits = data.split('$', 1) 131 if len(bits) == 2: 132 hash, value = bits 133 if hash == self._hash(value): 134 try: 135 # If we get here (and the JSON decode works), everything is 136 # good. In any other case, drop back and return None. 137 return json.loads(value, cls=MessageDecoder) 138 except ValueError: 139 pass 140 # Mark the data as used (so it gets removed) since something was wrong 141 # with the data. 142 self.used = True 143 return None -
new file django/contrib/messages/storage/fallback.py
diff -r 70e75e8cd224 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(messages, response, 53 remove_oldest=False) 54 # Even if there are no more messages, continue iterating to ensure 55 # storages which contained messages are flushed. 56 elif storage in self._used_storages: 57 storage._store([], response) 58 self._used_storages.remove(storage) 59 return messages -
new file django/contrib/messages/storage/session.py
diff -r 70e75e8cd224 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 "\ 14 "MIDDLEWARE_CLASSES 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 70e75e8cd224 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/tests/__init__.py
diff -r 70e75e8cd224 django/contrib/messages/tests/__init__.py
- + 1 from django.contrib.messages.tests.cookie import CookieTest 2 from django.contrib.messages.tests.fallback import FallbackTest 3 from django.contrib.messages.tests.middleware import MiddlewareTest 4 from django.contrib.messages.tests.session import SessionTest 5 from django.contrib.messages.tests.user_messages import \ 6 UserMessagesTest, LegacyFallbackTest -
new file django/contrib/messages/tests/base.py
diff -r 70e75e8cd224 django/contrib/messages/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 default_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 MessageFailure 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 = default_storage 28 restore_settings = ['MESSAGE_LEVEL', 'MESSAGE_TAGS'] 29 urls = 'django.contrib.messages.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 38 def setUp(self): 39 self._remembered_settings = {} 40 for setting in self.restore_settings: 41 if hasattr(settings, setting): 42 self._remembered_settings[setting] = getattr(settings, setting) 43 delattr(settings._wrapped, setting) 44 # backup these manually because we do not want them deleted 45 self._middleware_classes = settings.MIDDLEWARE_CLASSES 46 self._template_context_processors = \ 47 settings.TEMPLATE_CONTEXT_PROCESSORS 48 self._installed_apps = settings.INSTALLED_APPS 49 50 def tearDown(self): 51 for setting in self.restore_settings: 52 self.restore_setting(setting) 53 # restore these manually (see above) 54 settings.MIDDLEWARE_CLASSES = self._middleware_classes 55 settings.TEMPLATE_CONTEXT_PROCESSORS = \ 56 self._template_context_processors 57 settings.INSTALLED_APPS = self._installed_apps 58 59 def restore_setting(self, setting): 60 if setting in self._remembered_settings: 61 value = self._remembered_settings.pop(setting) 62 setattr(settings, setting, value) 63 elif hasattr(settings, setting): 64 delattr(settings._wrapped, setting) 65 66 def get_request(self): 67 return http.HttpRequest() 68 69 def get_response(self): 70 return http.HttpResponse() 71 72 def get_storage(self, data=None): 73 """ 74 Returns the storage backend, setting its loaded data to the ``data`` 75 argument. 76 77 This method avoids the storage ``_get`` method from getting called so 78 that other parts of the storage backend can be tested independent of 79 the message retrieval logic. 80 """ 81 storage = self.storage_class(self.get_request()) 82 storage._loaded_data = data or [] 83 return storage 84 85 def test_add(self): 86 storage = self.get_storage() 87 self.assertFalse(storage.added_new) 88 storage.add(constants.INFO, 'Test message 1') 89 self.assert_(storage.added_new) 90 storage.add(constants.INFO, 'Test message 2', extra_tags='tag') 91 self.assertEqual(len(storage), 2) 92 93 def test_add_lazy_translation(self): 94 storage = self.get_storage() 95 response = self.get_response() 96 97 storage.add(constants.INFO, ugettext_lazy('lazy message')) 98 storage.update(response) 99 100 storing = self.stored_messages_count(storage, response) 101 self.assertEqual(storing, 1) 102 103 def test_no_update(self): 104 storage = self.get_storage() 105 response = self.get_response() 106 storage.update(response) 107 storing = self.stored_messages_count(storage, response) 108 self.assertEqual(storing, 0) 109 110 def test_add_update(self): 111 storage = self.get_storage() 112 response = self.get_response() 113 114 storage.add(constants.INFO, 'Test message 1') 115 storage.add(constants.INFO, 'Test message 1', extra_tags='tag') 116 storage.update(response) 117 118 storing = self.stored_messages_count(storage, response) 119 self.assertEqual(storing, 2) 120 121 def test_existing_add_read_update(self): 122 storage = self.get_existing_storage() 123 response = self.get_response() 124 125 storage.add(constants.INFO, 'Test message 3') 126 list(storage) # Simulates a read 127 storage.update(response) 128 129 storing = self.stored_messages_count(storage, response) 130 self.assertEqual(storing, 0) 131 132 def test_existing_read_add_update(self): 133 storage = self.get_existing_storage() 134 response = self.get_response() 135 136 list(storage) # Simulates a read 137 storage.add(constants.INFO, 'Test message 3') 138 storage.update(response) 139 140 storing = self.stored_messages_count(storage, response) 141 self.assertEqual(storing, 1) 142 143 def test_full_request_response_cycle(self): 144 """ 145 With the message middleware enabled, tests that messages are properly 146 stored and then retrieved across the full request/redirect/response 147 cycle. 148 """ 149 settings.MESSAGE_LEVEL = constants.DEBUG 150 data = { 151 'messages': ['Test message %d' % x for x in xrange(10)], 152 } 153 show_url = reverse('django.contrib.messages.tests.urls.show') 154 for level in ('debug', 'info', 'success', 'warning', 'error'): 155 add_url = reverse('django.contrib.messages.tests.urls.add', 156 args=(level,)) 157 response = self.client.post(add_url, data, follow=True) 158 self.assertRedirects(response, show_url) 159 self.assertTrue('messages' in response.context) 160 messages = [Message(self.levels[level], msg) for msg in 161 data['messages']] 162 self.assertEqual(list(response.context['messages']), messages) 163 for msg in data['messages']: 164 self.assertContains(response, msg) 165 166 def test_multiple_posts(self): 167 """ 168 Tests that messages persist properly when multiple POSTs are made 169 before a GET. 170 """ 171 settings.MESSAGE_LEVEL = constants.DEBUG 172 data = { 173 'messages': ['Test message %d' % x for x in xrange(10)], 174 } 175 show_url = reverse('django.contrib.messages.tests.urls.show') 176 messages = [] 177 for level in ('debug', 'info', 'success', 'warning', 'error'): 178 messages.extend([Message(self.levels[level], msg) for msg in 179 data['messages']]) 180 add_url = reverse('django.contrib.messages.tests.urls.add', 181 args=(level,)) 182 self.client.post(add_url, data) 183 response = self.client.get(show_url) 184 self.assertTrue('messages' in response.context) 185 self.assertEqual(list(response.context['messages']), messages) 186 for msg in data['messages']: 187 self.assertContains(response, msg) 188 189 def test_middleware_disabled_auth_user(self): 190 """ 191 Tests that the messages API successfully falls back to using 192 user.message_set to store messages directly when the middleware is 193 disabled. 194 """ 195 settings.MESSAGE_LEVEL = constants.DEBUG 196 user = User.objects.create_user('test', 'test@example.com', 'test') 197 self.client.login(username='test', password='test') 198 settings.INSTALLED_APPS = list(settings.INSTALLED_APPS) 199 settings.INSTALLED_APPS.remove( 200 'django.contrib.messages', 201 ) 202 settings.MIDDLEWARE_CLASSES = list(settings.MIDDLEWARE_CLASSES) 203 settings.MIDDLEWARE_CLASSES.remove( 204 'django.contrib.messages.middleware.MessageMiddleware', 205 ) 206 settings.TEMPLATE_CONTEXT_PROCESSORS = \ 207 list(settings.TEMPLATE_CONTEXT_PROCESSORS) 208 settings.TEMPLATE_CONTEXT_PROCESSORS.remove( 209 'django.contrib.messages.context_processors.messages', 210 ) 211 data = { 212 'messages': ['Test message %d' % x for x in xrange(10)], 213 } 214 show_url = reverse('django.contrib.messages.tests.urls.show') 215 for level in ('debug', 'info', 'success', 'warning', 'error'): 216 add_url = reverse('django.contrib.messages.tests.urls.add', 217 args=(level,)) 218 response = self.client.post(add_url, data, follow=True) 219 self.assertRedirects(response, show_url) 220 self.assertTrue('messages' in response.context) 221 self.assertEqual(list(response.context['messages']), 222 data['messages']) 223 for msg in data['messages']: 224 self.assertContains(response, msg) 225 226 def test_middleware_disabled_anon_user(self): 227 """ 228 Tests that, when the middleware is disabled and a user is not logged 229 in, an exception is raised when one attempts to store a message. 230 """ 231 settings.MESSAGE_LEVEL = constants.DEBUG 232 settings.INSTALLED_APPS = list(settings.INSTALLED_APPS) 233 settings.INSTALLED_APPS.remove( 234 'django.contrib.messages', 235 ) 236 settings.MIDDLEWARE_CLASSES = list(settings.MIDDLEWARE_CLASSES) 237 settings.MIDDLEWARE_CLASSES.remove( 238 'django.contrib.messages.middleware.MessageMiddleware', 239 ) 240 settings.TEMPLATE_CONTEXT_PROCESSORS = \ 241 list(settings.TEMPLATE_CONTEXT_PROCESSORS) 242 settings.TEMPLATE_CONTEXT_PROCESSORS.remove( 243 'django.contrib.messages.context_processors.messages', 244 ) 245 data = { 246 'messages': ['Test message %d' % x for x in xrange(10)], 247 } 248 show_url = reverse('django.contrib.messages.tests.urls.show') 249 for level in ('debug', 'info', 'success', 'warning', 'error'): 250 add_url = reverse('django.contrib.messages.tests.urls.add', 251 args=(level,)) 252 self.assertRaises(MessageFailure, self.client.post, add_url, 253 data, follow=True) 254 255 def test_middleware_disabled_anon_user_fail_silently(self): 256 """ 257 Tests that, when the middleware is disabled and a user is not logged 258 in, an exception is raised when one attempts to store a message. 259 """ 260 settings.MESSAGE_LEVEL = constants.DEBUG 261 settings.INSTALLED_APPS = list(settings.INSTALLED_APPS) 262 settings.INSTALLED_APPS.remove( 263 'django.contrib.messages', 264 ) 265 settings.MIDDLEWARE_CLASSES = list(settings.MIDDLEWARE_CLASSES) 266 settings.MIDDLEWARE_CLASSES.remove( 267 'django.contrib.messages.middleware.MessageMiddleware', 268 ) 269 settings.TEMPLATE_CONTEXT_PROCESSORS = \ 270 list(settings.TEMPLATE_CONTEXT_PROCESSORS) 271 settings.TEMPLATE_CONTEXT_PROCESSORS.remove( 272 'django.contrib.messages.context_processors.messages', 273 ) 274 data = { 275 'messages': ['Test message %d' % x for x in xrange(10)], 276 'fail_silently': True, 277 } 278 show_url = reverse('django.contrib.messages.tests.urls.show') 279 for level in ('debug', 'info', 'success', 'warning', 'error'): 280 add_url = reverse('django.contrib.messages.tests.urls.add', 281 args=(level,)) 282 response = self.client.post(add_url, data, follow=True) 283 self.assertRedirects(response, show_url) 284 self.assertTrue('messages' in response.context) 285 self.assertEqual(list(response.context['messages']), []) 286 287 def stored_messages_count(self, storage, response): 288 """ 289 Returns the number of messages being stored after a 290 ``storage.update()`` call. 291 """ 292 raise NotImplementedError('This method must be set by a subclass.') 293 294 def test_get(self): 295 raise NotImplementedError('This method must be set by a subclass.') 296 297 def get_existing_storage(self): 298 return self.get_storage([Message(constants.INFO, 'Test message 1'), 299 Message(constants.INFO, 'Test message 2', 300 extra_tags='tag')]) 301 302 def test_existing_read(self): 303 """ 304 Tests that reading the existing storage doesn't cause the data to be 305 lost. 306 """ 307 storage = self.get_existing_storage() 308 self.assertFalse(storage.used) 309 # After iterating the storage engine directly, the used flag is set. 310 data = list(storage) 311 self.assert_(storage.used) 312 # The data does not disappear because it has been iterated. 313 self.assertEqual(data, list(storage)) 314 315 def test_existing_add(self): 316 storage = self.get_existing_storage() 317 self.assertFalse(storage.added_new) 318 storage.add(constants.INFO, 'Test message 3') 319 self.assert_(storage.added_new) 320 321 def test_default_level(self): 322 storage = self.get_storage() 323 add_level_messages(storage) 324 self.assertEqual(len(storage), 5) 325 326 def test_low_level(self): 327 storage = self.get_storage() 328 storage.level = 5 329 add_level_messages(storage) 330 self.assertEqual(len(storage), 6) 331 332 def test_high_level(self): 333 storage = self.get_storage() 334 storage.level = 30 335 add_level_messages(storage) 336 self.assertEqual(len(storage), 2) 337 338 def test_settings_level(self): 339 settings.MESSAGE_LEVEL = 29 340 storage = self.get_storage() 341 add_level_messages(storage) 342 self.assertEqual(len(storage), 3) 343 344 def test_tags(self): 345 storage = self.get_storage() 346 storage.level = 0 347 add_level_messages(storage) 348 tags = [msg.tags for msg in storage] 349 self.assertEqual(tags, 350 ['info', '', 'extra-tag debug', 'warning', 'error', 351 'success']) 352 353 def test_custom_tags(self): 354 settings.MESSAGE_TAGS = { 355 constants.INFO: 'info', 356 constants.DEBUG: '', 357 constants.WARNING: '', 358 constants.ERROR: 'bad', 359 29: 'custom', 360 } 361 # LEVEL_TAGS is a constant defined in the 362 # django.contrib.messages.storage.base module, so after changing 363 # settings.MESSAGE_TAGS, we need to update that constant too. 364 base.LEVEL_TAGS = utils.get_level_tags() 365 try: 366 storage = self.get_storage() 367 storage.level = 0 368 add_level_messages(storage) 369 tags = [msg.tags for msg in storage] 370 self.assertEqual(tags, 371 ['info', 'custom', 'extra-tag', '', 'bad', 'success']) 372 finally: 373 # Ensure the level tags constant is put back like we found it. 374 self.restore_setting('MESSAGE_TAGS') 375 base.LEVEL_TAGS = utils.get_level_tags() -
new file django/contrib/messages/tests/cookie.py
diff -r 70e75e8cd224 django/contrib/messages/tests/cookie.py
- + 1 from django.contrib.messages import constants 2 from django.contrib.messages.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 9 def set_cookie_data(storage, messages, invalid=False, encode_empty=False): 10 """ 11 Sets ``request.COOKIES`` with the encoded data and removes the storage 12 backend's loaded data cache. 13 """ 14 encoded_data = storage._encode(messages, encode_empty=encode_empty) 15 if invalid: 16 # Truncate the first character so that the hash is invalid. 17 encoded_data = encoded_data[1:] 18 storage.request.COOKIES = {CookieStorage.cookie_name: encoded_data} 19 if hasattr(storage, '_loaded_data'): 20 del storage._loaded_data 21 22 23 def stored_cookie_messages_count(storage, response): 24 """ 25 Returns an integer containing the number of messages stored. 26 """ 27 # Get a list of cookies, excluding ones with a max-age of 0 (because 28 # they have been marked for deletion). 29 cookie = response.cookies.get(storage.cookie_name) 30 if not cookie or cookie['max-age'] == 0: 31 return 0 32 data = storage._decode(cookie.value) 33 if not data: 34 return 0 35 if data[-1] == CookieStorage.not_finished: 36 data.pop() 37 return len(data) 38 39 40 class CookieTest(BaseTest): 41 storage_class = CookieStorage 42 43 def stored_messages_count(self, storage, response): 44 return stored_cookie_messages_count(storage, response) 45 46 def test_get(self): 47 storage = self.storage_class(self.get_request()) 48 # Set initial data. 49 example_messages = ['test', 'me'] 50 set_cookie_data(storage, example_messages) 51 # Test that the message actually contains what we expect. 52 self.assertEqual(list(storage), example_messages) 53 54 def test_get_bad_cookie(self): 55 request = self.get_request() 56 storage = self.storage_class(request) 57 # Set initial (invalid) data. 58 example_messages = ['test', 'me'] 59 set_cookie_data(storage, example_messages, invalid=True) 60 # Test that the message actually contains what we expect. 61 self.assertEqual(list(storage), []) 62 63 def test_max_cookie_length(self): 64 """ 65 Tests that, if the data exceeds what is allowed in a cookie, older 66 messages are removed before saving (and returned by the ``update`` 67 method). 68 """ 69 storage = self.get_storage() 70 response = self.get_response() 71 72 for i in range(5): 73 storage.add(constants.INFO, str(i) * 900) 74 unstored_messages = storage.update(response) 75 76 cookie_storing = self.stored_messages_count(storage, response) 77 self.assertEqual(cookie_storing, 4) 78 79 self.assertEqual(len(unstored_messages), 1) 80 self.assert_(unstored_messages[0].message == '0' * 900) 81 82 def test_json_encoder_decoder(self): 83 """ 84 Tests that an complex nested data structure containing Message 85 instances is properly encoded/decoded by the custom JSON 86 encoder/decoder classes. 87 """ 88 messages = [ 89 { 90 'message': Message(constants.INFO, 'Test message'), 91 'message_list': [Message(constants.INFO, 'message %s') \ 92 for x in xrange(5)] + [{'another-message': \ 93 Message(constants.ERROR, 'error')}], 94 }, 95 Message(constants.INFO, 'message %s'), 96 ] 97 encoder = MessageEncoder(separators=(',', ':')) 98 value = encoder.encode(messages) 99 decoded_messages = json.loads(value, cls=MessageDecoder) 100 self.assertEqual(messages, decoded_messages) -
new file django/contrib/messages/tests/fallback.py
diff -r 70e75e8cd224 django/contrib/messages/tests/fallback.py
- + 1 from django.contrib.messages import constants 2 from django.contrib.messages.storage.fallback import FallbackStorage, \ 3 CookieStorage 4 from django.contrib.messages.tests.base import BaseTest 5 from django.contrib.messages.tests.cookie import set_cookie_data, \ 6 stored_cookie_messages_count 7 from django.contrib.messages.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 django/contrib/messages/tests/middleware.py
diff -r 70e75e8cd224 django/contrib/messages/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 django/contrib/messages/tests/session.py
diff -r 70e75e8cd224 django/contrib/messages/tests/session.py
- + 1 from django.contrib.messages.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 django/contrib/messages/tests/urls.py
diff -r 70e75e8cd224 django/contrib/messages/tests/urls.py
- + 1 from django.conf.urls.defaults import * 2 from django.contrib import messages 3 from django.core.urlresolvers import reverse 4 from django.http import HttpResponseRedirect, HttpResponse 5 from django.shortcuts import render_to_response 6 from django.template import RequestContext, Template 7 8 9 def add(request, message_type): 10 # don't default to False here, because we want to test that it defaults 11 # to False if unspecified 12 fail_silently = request.POST.get('fail_silently', None) 13 for msg in request.POST.getlist('messages'): 14 if fail_silently is not None: 15 getattr(messages, message_type)(request, msg, 16 fail_silently=fail_silently) 17 else: 18 getattr(messages, message_type)(request, msg) 19 show_url = reverse('django.contrib.messages.tests.urls.show') 20 return HttpResponseRedirect(show_url) 21 22 23 def show(request): 24 t = Template("""{% if messages %} 25 <ul class="messages"> 26 {% for message in messages %} 27 <li{% if message.tags %} class="{{ message.tags }}"{% endif %}> 28 {{ message }} 29 </li> 30 {% endfor %} 31 </ul> 32 {% endif %}""") 33 return HttpResponse(t.render(RequestContext(request))) 34 35 36 urlpatterns = patterns('', 37 ('^add/(debug|info|success|warning|error)/$', add), 38 ('^show/$', show), 39 ) -
new file django/contrib/messages/tests/user_messages.py
diff -r 70e75e8cd224 django/contrib/messages/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 django.contrib.messages.tests.cookie import set_cookie_data 6 from django.contrib.messages.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 django/contrib/messages/utils.py
diff -r 70e75e8cd224 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 70e75e8cd224 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 70e75e8cd224 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 70e75e8cd224 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 70e75e8cd224 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 70e75e8cd224 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 70e75e8cd224 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 <message-storage-backends>` that 32 relies 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 .. _message-storage-backends: 57 58 Storage backends 59 ---------------- 60 61 The messages framework can use different backends to store temporary messages. 62 To change which backend is being used, add a `MESSAGE_STORAGE`_ to your 63 settings, referencing the module and class of the storage class. For 64 example:: 65 66 MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage' 67 68 The value should be the full path of the desired storage class. 69 70 Four storage classes are included: 71 72 ``'django.contrib.messages.storage.session.SessionStorage'`` 73 This class stores all messages inside of the request's session. It 74 requires Django's ``contrib.session`` application. 75 76 ``'django.contrib.messages.storage.cookie.CookieStorage'`` 77 This class stores the message data in a cookie (signed with a secret hash 78 to prevent manipulation) to persist notifications across requests. Old 79 messages are dropped if the cookie data size would exceed 4096 bytes. 80 81 ``'django.contrib.messages.storage.fallback.FallbackStorage'`` 82 This class first uses CookieStorage for all messages, falling back to using 83 SessionStorage for the messages that could not fit in a single cookie. 84 85 Since it is uses SessionStorage, it also requires Django's 86 ``contrib.session`` application. 87 88 ``'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'`` 89 This is the default temporary storage class. 90 91 This class extends FallbackStorage and adds compatibility methods to 92 to retrieve any messages stored in the user Message model by code that 93 has not yet been updated to use the new API. This storage is temporary 94 (because it makes use of code that is pending deprecation) and will be 95 removed in Django 1.4. At that time, the default storage will become 96 ``django.contrib.messages.storage.fallback.FallbackStorage``. For more 97 information, see `LegacyFallbackStorage`_ below. 98 99 To write your own storage class, subclass the ``BaseStorage`` class in 100 ``django.contrib.messages.storage.base`` and implement the ``_get`` and 101 ``_store`` methods. 102 103 LegacyFallbackStorage 104 ^^^^^^^^^^^^^^^^^^^^^ 105 106 The ``LegacyFallbackStorage`` is a temporary tool to facilitate the transition 107 from the deprecated ``user.message_set`` API and will be removed in Django 1.4 108 according to Django's standard deprecation policy. For more information, see 109 the full :ref:`release process documentation <internals-release-process>`. 110 111 In addition to the functionality in the ``FallbackStorage``, it adds a custom, 112 read-only storage class that retrieves messages from the user ``Message`` 113 model. Any messages that were stored in the ``Message`` model (e.g., by code 114 that has not yet been updated to use the messages framework) will be retrieved 115 first, followed by those stored in a cookie and in the session, if any. Since 116 messages stored in the ``Message`` model do not have a concept of levels, they 117 will be assigned the ``INFO`` level by default. 118 119 Message levels 120 -------------- 121 122 The messages framework is based on a configurable level architecture similar 123 to that of the Python logging module. Message levels allow you to group 124 messages by type so they can be filtered or displayed differently in views and 125 templates. 126 127 The built-in levels (which can be imported from ``django.contrib.messages`` 128 directly) are: 129 130 =========== ======== 131 Constant Purpose 132 =========== ======== 133 ``DEBUG`` Development-related messages that will be ignored (or removed) in a production deployment 134 ``INFO`` Informational messages for the user 135 ``SUCCESS`` An action was successful, e.g. "Your profile was updated successfully" 136 ``WARNING`` A failure did not occur but may be imminent 137 ``ERROR`` An action was **not** successful or some other failure occurred 138 =========== ======== 139 140 The `MESSAGE_LEVEL`_ setting can be used to change the minimum recorded 141 level. Attempts to add messages of a level less than this will be ignored. 142 143 Message tags 144 ------------ 145 146 Message tags are a string representation of the message level plus any 147 extra tags that were added directly in the view (see 148 `Adding extra message tags`_ below for more details). Tags are stored in a 149 string and are separated by spaces. Typically, message tags 150 are used as CSS classes to customize message style based on message type. By 151 default, each level has a single tag that's a lowercase version of its own 152 constant: 153 154 ============== =========== 155 Level Constant Tag 156 ============== =========== 157 ``DEBUG`` ``debug`` 158 ``INFO`` ``info`` 159 ``SUCCESS`` ``success`` 160 ``WARNING`` ``warning`` 161 ``ERROR`` ``error`` 162 ============== =========== 163 164 To change the default tags for a message level (either built-in or custom), 165 set the `MESSAGE_TAGS`_ setting to a dictionary containing the levels 166 you wish to change. As this extends the default tags, you only need to provide 167 tags for the levels you wish to override:: 168 169 from django.contrib.messages import constants as messages 170 MESSAGE_TAGS = { 171 messages.INFO: '', 172 50: 'critical', 173 } 174 175 Using messages in views and templates 176 ===================================== 177 178 Adding a message 179 ---------------- 180 181 To add a message, call:: 182 183 from django.contrib import messages 184 messages.add_message(request, messages.INFO, 'Hello world.') 185 186 Some shortcut methods provide a standard way to add messages with commonly 187 used tags (which are usually represented as HTML classes for the message):: 188 189 messages.debug(request, '%s SQL statements were executed.' % count) 190 messages.info(request, 'Three credits remain in your account.') 191 messages.success(request, 'Profile details updated.') 192 messages.warning(request, 'Your account expires in three days.') 193 messages.error(request, 'Document deleted.') 194 195 Displaying messages 196 ------------------- 197 198 In your template, use something like:: 199 200 {% if messages %} 201 <ul class="messages"> 202 {% for message in messages %} 203 <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li> 204 {% endfor %} 205 </ul> 206 {% endif %} 207 208 If you're using the context processor, your template should be rendered with a 209 ``RequestContext``. Otherwise, ensure ``messages`` is available to 210 the template context. 211 212 Creating custom message levels 213 ------------------------------ 214 215 Messages levels are nothing more than integers, so you can define your own 216 level constants and use them to create more customized user feedback, e.g.:: 217 218 CRITICAL = 50 219 220 def my_view(request): 221 messages.add_message(request, CRITICAL, 'A serious error occurred.') 222 223 When creating custom message levels you should be careful to avoid overloading 224 existing levels. The values for the built-in levels are: 225 226 .. _message-level-constants: 227 228 ============== ===== 229 Level Constant Value 230 ============== ===== 231 ``DEBUG`` 10 232 ``INFO`` 20 233 ``SUCCESS`` 25 234 ``WARNING`` 30 235 ``ERROR`` 40 236 ============== ===== 237 238 If you need to identify the custom levels in your HTML or CSS, you need to 239 provide a mapping via the `MESSAGE_TAGS`_ setting. 240 241 .. note:: 242 If you are creating a reusable application, it is recommended to use 243 only the built-in `message levels`_ and not rely on any custom levels. 244 245 Changing the minimum recorded level per-request 246 ----------------------------------------------- 247 248 The minimum recorded level can be set per request by changing the ``level`` 249 attribute of the messages storage instance:: 250 251 from django.contrib import messages 252 253 # Change the messages level to ensure the debug message is added. 254 messages.get_messages(request).level = messages.DEBUG 255 messages.debug(request, 'Test message...') 256 257 # In another request, record only messages with a level of WARNING and higher 258 messages.get_messages(request).level = messages.WARNING 259 messages.success(request, 'Your profile was updated.') # ignored 260 messages.warning(request, 'Your account is about to expire.') # recorded 261 262 # Set the messages level back to default. 263 messages.get_messages(request).level = None 264 265 For more information on how the minimum recorded level functions, see 266 `Message levels`_ above. 267 268 Adding extra message tags 269 ------------------------- 270 271 For more direct control over message tags, you can optionally provide a string 272 containing extra tags to any of the add methods:: 273 274 messages.add_message(request, messages.INFO, 'Over 9000!', 275 extra_tags='dragonball') 276 messages.error(request, 'Email box full', extra_tags='email') 277 278 Extra tags are added before the default tag for that level and are space 279 separated. 280 281 Failing silently when the message framework is disabled 282 ------------------------------------------------------- 283 284 If you're writing a reusable app (or other piece of code) and want to include 285 messaging functionality, but don't want to require your users to enable it 286 if they don't want to, you may pass an additional keyword argument 287 ``fail_silently=True`` to any of the ``add_message`` family of methods. For 288 example:: 289 290 messages.add_message(request, messages.SUCCESS, 'Profile details updated.', 291 fail_silently=True) 292 messages.info(request, 'Hello world.', fail_silently=True) 293 294 Internally, Django uses this functionality in the create, update, and delete 295 :ref:`generic views <topics-generic-views>` so that they work even if the 296 message framework is disabled. 297 298 .. note:: 299 Setting ``fail_silently=True`` only hides the ``MessageFailure`` that would 300 otherwise occur when the messages framework disabled and one attempts to 301 use one of the ``add_message`` family of methods. It does not hide failures 302 that may occur for other reasons. 303 304 Expiration of messages 305 ====================== 306 307 The messages are marked to be cleared when the storage instance is iterated 308 (and cleared when the response is processed). 309 310 To avoid the messages being cleared, you can set the messages storage to 311 ``False`` after iterating:: 312 313 storage = messages.get_messages(request) 314 for message in storage: 315 do_something_with(message) 316 storage.used = False 317 318 Behavior of parallel requests 319 ============================= 320 321 Due to the way cookies (and hence sessions) work, **the behavior of any 322 backends that make use of cookies or sessions is undefined when the same 323 client makes multiple requests that set or get messages in parallel**. For 324 example, if a client initiates a request that creates a message in one window 325 (or tab) and then another that fetches any uniterated messages in another 326 window, before the first window redirects, the message may appear in the 327 second window instead of the first window where it may be expected. 328 329 In short, when multiple simultaneous requests from the same client are 330 involved, messages are not guaranteed to be delivered to the same window that 331 created them nor, in some cases, at all. Note that this is typically not a 332 problem in most applications and will become a non-issue in HTML5, where each 333 window/tab will have its own browsing context. 334 335 Settings 336 ======== 337 338 A few :ref:`Django settings <ref-settings>` give you control over message 339 behavior: 340 341 MESSAGE_LEVEL 342 ------------- 343 344 Default: ``messages.INFO`` 345 346 This sets the minimum message that will be saved in the message storage. See 347 `Message levels`_ above for more details. 348 349 .. admonition:: Important 350 351 If you override ``MESSAGE_LEVEL`` in your settings file and rely on any of 352 the built-in constants, you must import the constants module directly to 353 avoid the potential for circular imports, e.g.:: 354 355 from django.contrib.messages import constants as message_constants 356 MESSAGE_LEVEL = message_constants.DEBUG 357 358 If desired, you may specify the numeric values for the constants directly 359 according to the values in the above :ref:`constants table 360 <message-level-constants>`. 361 362 MESSAGE_STORAGE 363 --------------- 364 365 Default: ``'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'`` 366 367 Controls where Django stores message data. Valid values are: 368 369 * ``'django.contrib.messages.storage.fallback.FallbackStorage'`` 370 * ``'django.contrib.messages.storage.session.SessionStorage'`` 371 * ``'django.contrib.messages.storage.cookie.CookieStorage'`` 372 * ``'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'`` 373 374 See `Storage backends`_ for more details. 375 376 MESSAGE_TAGS 377 ------------ 378 379 Default:: 380 381 {messages.DEBUG: 'debug', 382 messages.INFO: 'info', 383 messages.SUCCESS: 'success', 384 messages.WARNING: 'warning', 385 messages.ERROR: 'error',} 386 387 This sets the mapping of message level to message tag, which is typically 388 rendered as a CSS class in HTML. If you specify a value, it will extend 389 the default. This means you only have to specify those values which you need 390 to override. See `Displaying messages`_ above for more details. 391 392 .. admonition:: Important 393 394 If you override ``MESSAGE_TAGS`` in your settings file and rely on any of 395 the built-in constants, you must import the ``constants`` module directly to 396 avoid the potential for circular imports, e.g.:: 397 398 from django.contrib.messages import constants as message_constants 399 MESSAGE_TAGS = {message_constants.INFO: ''} 400 401 If desired, you may specify the numeric values for the constants directly 402 according to the values in the above :ref:`constants table 403 <message-level-constants>`. 404 405 .. _Django settings: ../settings/ -
docs/ref/middleware.txt
diff -r 70e75e8cd224 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 .. versionadded:: 1.2 151 ``MessageMiddleware`` was added. 152 153 Enables cookie- and session-based message support. See the 154 :ref:`messages documentation <ref-contrib-messages>`. 155 142 156 Session middleware 143 157 ------------------ 144 158 -
docs/ref/settings.txt
diff -r 70e75e8cd224 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 865 .. versionchanged:: 1.2 866 ``'django.contrib.messages.middleware.MessageMiddleware'`` was added to the 867 default. For more information, see the :ref:`messages documentation 868 <ref-contrib-messages>`. 869 827 870 .. setting:: MONTH_DAY_FORMAT 828 871 829 872 MONTH_DAY_FORMAT … … 1059 1102 ("django.core.context_processors.auth", 1060 1103 "django.core.context_processors.debug", 1061 1104 "django.core.context_processors.i18n", 1062 "django.core.context_processors.media") 1105 "django.core.context_processors.media", 1106 "django.contrib.messages.context_processors.messages") 1063 1107 1064 1108 A tuple of callables that are used to populate the context in ``RequestContext``. 1065 1109 These callables take a request object as their argument and return a dictionary 1066 1110 of items to be merged into the context. 1067 1111 1112 .. versionchanged:: 1.2 1113 ``"django.contrib.messages.context_processors.messages"`` was added to the 1114 default. For more information, see the :ref:`messages documentation 1115 <ref-contrib-messages>`. 1116 1068 1117 .. setting:: TEMPLATE_DEBUG 1069 1118 1070 1119 TEMPLATE_DEBUG -
docs/ref/templates/api.txt
diff -r 70e75e8cd224 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 70e75e8cd224 docs/releases/1.2.txt
a b 121 121 :meth:`~django.core.mail.get_connection()` call:: 122 122 123 123 connection = get_connection('django.core.mail.backends.smtp', hostname='localhost', port=1234) 124 125 User Messages API 126 ----------------- 127 128 The API for storing messages in the user ``Message`` model (via 129 ``user.message_set.create``) is now deprecated and will be removed in Django 130 1.4 according to the standard :ref:`release process <internals-release-process>`. 131 132 To upgrade your code, you need to replace any instances of:: 133 134 user.message_set.create('a message') 135 136 with the following:: 137 138 from django.contrib import messages 139 messages.add_message(request, messages.INFO, 'a message') 140 141 Additionally, if you make use of the method, you need to replace the 142 following:: 143 144 for message in user.get_and_delete_messages(): 145 ... 146 147 with:: 148 149 from django.contrib import messages 150 for message in messages.get_messages(request): 151 ... 152 153 For more information, see the full 154 :ref:`messages documentation <ref-contrib-messages>`. You should begin to 155 update your code to use the new API immediately. 124 156 125 157 What's new in Django 1.2 126 158 ======================== … … 155 187 :ref:`memory<topic-email-memory-backend>` - you can even configure all 156 188 e-mail to be :ref:`thrown away<topic-email-dummy-backend>`. 157 189 190 Messages Framework 191 ------------------------- 192 193 Django now includes a robust and configurable 194 :ref:`messages framework <ref-contrib-messages>` with built-in support for 195 cookie- and session-based messaging, for both anonymous and authenticated 196 clients. The messages framework replaces the deprecated user message API and 197 allows you to temporarily store messages in one request and retrieve them for 198 display in a subsequent request (usually the next one). -
docs/topics/auth.txt
diff -r 70e75e8cd224 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. You should use the 1297 :ref:`messages framework <ref-contrib-messages>` for all new projects and 1298 begin to update your existing code immediately. 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 -
tests/runtests.py
diff -r 70e75e8cd224 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