Ticket #4604: django-contrib-messages-9f54c0f8719c.diff
File django-contrib-messages-9f54c0f8719c.diff, 81.8 KB (added by , 15 years ago) |
---|
-
django/conf/global_settings.py
comparing with http://bitbucket.org/mirror/django-trunk/ searching for changes diff -r ed90e13d2065 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 ed90e13d2065 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 ed90e13d2065 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 10 from django.contrib.messages.compat import compat_add_message 9 11 from django.views.decorators.csrf import csrf_protect 10 12 from django.core.exceptions import PermissionDenied 11 13 from django.db import models, transaction … … 541 543 def message_user(self, request, message): 542 544 """ 543 545 Send a message to the user. The default implementation 544 posts a message using the auth Message object.546 posts a message using the django.contrib.messages backend. 545 547 """ 546 request.user.message_set.create(message=message)548 compat_add_message(request, messages.INFO, message) 547 549 548 550 def save_form(self, request, form, change): 549 551 """ -
django/contrib/admin/views/template.py
diff -r ed90e13d2065 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 10 from django.contrib.messages.compat import compat_add_message 9 11 10 12 11 13 def template_validator(request): … … 23 25 form = TemplateValidatorForm(settings_modules, site_list, 24 26 data=request.POST) 25 27 if form.is_valid(): 26 request.user.message_set.create(message='The template is valid.')28 compat_add_message(request, messages.INFO, 'The template is valid.') 27 29 else: 28 30 form = TemplateValidatorForm(settings_modules, site_list) 29 31 return render_to_response('admin/template_validator.html', { -
django/contrib/auth/admin.py
diff -r ed90e13d2065 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 7 from django.contrib.messages.compat import compat_add_message 6 8 from django.core.exceptions import PermissionDenied 7 9 from django.http import HttpResponseRedirect, Http404 8 10 from django.shortcuts import render_to_response, get_object_or_404 … … 67 69 msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': 'user', 'obj': new_user} 68 70 self.log_addition(request, new_user) 69 71 if "_addanother" in request.POST: 70 request.user.message_set.create(message=msg)72 compat_add_message(request, messages.SUCCESS, msg) 71 73 return HttpResponseRedirect(request.path) 72 74 elif '_popup' in request.REQUEST: 73 75 return self.response_add(request, new_user) 74 76 else: 75 request.user.message_set.create(message=msg + ' ' + ugettext("You may edit it again below."))77 compat_add_message(request, messages.SUCCESS, msg + ' ' + 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 compat_add_message(request, messages.SUCCESS, msg) 108 110 return HttpResponseRedirect('..') 109 111 else: 110 112 form = self.change_password_form(user) -
django/contrib/auth/models.py
diff -r ed90e13d2065 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 will be deprecated in Django 1.2.' 294 ' Please update your code to use the new messaging API.', 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 ed90e13d2065 django/contrib/messages/__init__.py
- + 1 from constants import * -
new file django/contrib/messages/compat.py
diff -r ed90e13d2065 django/contrib/messages/compat.py
- + 1 """ 2 Compatibility methods to assist in the transition from user.message_set to the 3 messages contrib app. 4 5 These methods are for use by Django internally and it is not recommended 6 that they called directly from a Django project. They will be removed in a 7 future version of Django and are not subject to the normal deprecation policy. 8 """ 9 10 from django.utils.functional import lazy, memoize 11 12 __all__ = ( 13 'compat_add_message', 14 'compat_get_messages', 15 ) 16 17 18 def compat_add_message(request, level, message): 19 """ 20 Attempts to add a message to the request using the 'messages' app, falling 21 back to the user's message_set if MessageMiddleware hasn't been enabled. 22 """ 23 if hasattr(request, 'messages'): 24 request.messages.add(level, message) 25 elif hasattr(request, 'user') and request.user.is_authenticated(): 26 request.user.message_set.create(message=message) 27 28 29 def compat_get_messages(request, get_user=None): 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 35 def get_user(): 36 if hasattr(request, 'user'): 37 return request.user 38 else: 39 from django.contrib.auth.models import AnonymousUser 40 return AnonymousUser() 41 42 if hasattr(request, 'messages'): 43 return request.messages 44 else: 45 return lazy(memoize(get_user().get_and_delete_messages, {}, 0), list)() -
new file django/contrib/messages/constants.py
diff -r ed90e13d2065 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 ed90e13d2065 django/contrib/messages/context_processors.py
- + 1 from django.contrib.messages.compat import compat_get_messages 2 3 4 def messages(request): 5 """ 6 Returns a lazy 'messages' context variable. 7 """ 8 return {'messages': compat_get_messages(request)} -
new file django/contrib/messages/middleware.py
diff -r ed90e13d2065 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 ed90e13d2065 django/contrib/messages/models.py
- + 1 # Models module required so tests are discovered. -
new file django/contrib/messages/storage/__init__.py
diff -r ed90e13d2065 django/contrib/messages/storage/__init__.py
- + 1 from django.conf import settings 2 from django.contrib.messages.storage.base import BaseStorage 3 from django.utils.importcls import get_class 4 5 Storage = get_class(getattr(settings, 'MESSAGE_STORAGE', 6 'django.contrib.messages.storage.user_messages.LegacyFallbackStorage')) -
new file django/contrib/messages/storage/base.py
diff -r ed90e13d2065 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 __unicode__(self): 33 return force_unicode(self.message) 34 35 def _get_tags(self): 36 label_tag = force_unicode(LEVEL_TAGS.get(self.level, ''), 37 strings_only=True) 38 extra_tags = force_unicode(self.extra_tags, strings_only=True) 39 if extra_tags: 40 if label_tag: 41 return u'%s %s' % (extra_tags, label_tag) 42 return extra_tags 43 return label_tag or '' 44 45 tags = property(_get_tags) 46 47 48 class BaseStorage(object): 49 """ 50 This is the base backend for temporary message storage. 51 52 This is not a complete class; to be a usable storage backend, it must be 53 subclassed and the two methods ``_get`` and ``_store`` overridden. 54 """ 55 store_serialized = True 56 57 def __init__(self, request, *args, **kwargs): 58 self.request = request 59 self._queued_messages = [] 60 self.used = False 61 self.added_new = False 62 super(BaseStorage, self).__init__(*args, **kwargs) 63 64 def __len__(self): 65 return len(self._loaded_messages) + len(self._queued_messages) 66 67 def __iter__(self): 68 self.used = True 69 if self._queued_messages: 70 self._loaded_messages.extend(self._queued_messages) 71 self._queued_messages = [] 72 return iter(self._loaded_messages) 73 74 def __contains__(self, item): 75 return item in self._loaded_messages or item in self._queued_messages 76 77 @property 78 def _loaded_messages(self): 79 """ 80 Returns a list of loaded messages, retrieving them first if they have 81 not been loaded yet. 82 """ 83 if not hasattr(self, '_loaded_data'): 84 messages, all_retrieved = self._get() 85 self._loaded_data = messages or [] 86 return self._loaded_data 87 88 def _get(self, *args, **kwargs): 89 """ 90 Retrieves a list of stored messages. Returns a tuple of the messages and 91 a flag indicating whether or not all the messages originally intended 92 to be stored in this storage were, in fact, stored and now retrieved. 93 94 **This method must be implemented by a subclass.** 95 96 If it is possible to tell if the backend was not used (as opposed to 97 just containing no messages) then ``None`` should be returned. 98 """ 99 raise NotImplementedError() 100 101 def _store(self, messages, response, *args, **kwargs): 102 """ 103 Stores a list of messages, returning a list of any messages which could 104 not be stored. 105 106 One type of object must be able to be stored, ``Message``. 107 108 **This method must be implemented by a subclass.** 109 """ 110 raise NotImplementedError() 111 112 def _prepare_messages(self, messages): 113 """ 114 Prepares a list of messages for storage. 115 """ 116 if self.store_serialized: 117 for message in messages: 118 message._prepare() 119 120 def update(self, response, fail_silently=True): 121 """ 122 Stores all unread messages. 123 124 If the backend has yet to be iterated, previously stored messages will 125 be stored again. Otherwise, only messages added after the last 126 iteration will be stored. 127 """ 128 if self.used: 129 self._prepare_messages(self._queued_messages) 130 return self._store(self._queued_messages, response) 131 elif self.added_new: 132 messages = self._loaded_messages + self._queued_messages 133 self._prepare_messages(messages) 134 return self._store(messages, response) 135 136 def add(self, level, message, extra_tags=''): 137 """ 138 Queues a message to be stored. 139 140 The message is only queued if it contained something and its level is 141 not less than the recording level (``self.level``). 142 """ 143 if not message: 144 return 145 # Check that the message level is not less than the recording level. 146 level = int(level) 147 if level < self.level: 148 return 149 # Add the message. 150 self.added_new = True 151 message = Message(level, message, extra_tags=extra_tags) 152 self._queued_messages.append(message) 153 154 def debug(self, message, extra_tags=''): 155 """ 156 Adds a message with the ``DEBUG`` level. 157 """ 158 self.add(constants.DEBUG, message, extra_tags=extra_tags) 159 160 def info(self, message, extra_tags=''): 161 """ 162 Adds a message with the ``INFO`` level. 163 """ 164 self.add(constants.INFO, message, extra_tags=extra_tags) 165 166 def success(self, message, extra_tags=''): 167 """ 168 Adds a message with the ``SUCCESS`` level. 169 """ 170 self.add(constants.SUCCESS, message, extra_tags=extra_tags) 171 172 def warning(self, message, extra_tags=''): 173 """ 174 Adds a message with the ``WARNING`` level. 175 """ 176 self.add(constants.WARNING, message, extra_tags=extra_tags) 177 178 def error(self, message, extra_tags=''): 179 """ 180 Adds a message with the ``ERROR`` level. 181 """ 182 self.add(constants.ERROR, message, extra_tags=extra_tags) 183 184 def _get_level(self): 185 """ 186 Returns the minimum recorded level. 187 188 The default level is the ``MESSAGE_LEVEL`` setting. If this is 189 not found, the ``INFO`` level is used. 190 """ 191 if not hasattr(self, '_level'): 192 self._level = getattr(settings, 'MESSAGE_LEVEL', constants.INFO) 193 return self._level 194 195 def _set_level(self, value=None): 196 """ 197 Sets a custom minimum recorded level. 198 199 If set to ``None``, the default level will be used (see the 200 ``_get_level`` method). 201 """ 202 if value is None and hasattr(self, '_level'): 203 del self._level 204 else: 205 self._level = int(value) 206 207 level = property(_get_level, _set_level, _set_level) -
new file django/contrib/messages/storage/cookie.py
diff -r ed90e13d2065 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 ed90e13d2065 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 ed90e13d2065 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 super(SessionStorage, self).__init__(request, *args, **kwargs) 14 15 def _get(self, *args, **kwargs): 16 """ 17 Retrieves a list of messages from the request's session. This storage 18 always stores everything it is given, so return True for the 19 all_retrieved flag. 20 """ 21 return self.request.session.get(self.session_key), True 22 23 def _store(self, messages, response, *args, **kwargs): 24 """ 25 Stores a list of messages to the request's session. 26 """ 27 if messages: 28 self.request.session[self.session_key] = messages 29 else: 30 self.request.session.pop(self.session_key, None) -
new file django/contrib/messages/storage/user_messages.py
diff -r ed90e13d2065 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 ed90e13d2065 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 UserMessagesTest, \ 6 LegacyFallbackTest -
new file django/contrib/messages/tests/base.py
diff -r ed90e13d2065 django/contrib/messages/tests/base.py
- + 1 from django.contrib.messages import constants 2 import unittest 3 from django import http 4 from django.conf import settings 5 from django.utils.translation import ugettext_lazy 6 from django.contrib.messages import utils 7 from django.contrib.messages.storage import Storage, base 8 from django.contrib.messages.storage.base import Message 9 10 11 def add_level_messages(storage): 12 """ 13 Adds 6 messages from different levels (including a custom one) to a storage 14 instance. 15 """ 16 storage.add(constants.INFO, 'A generic info message') 17 storage.add(29, 'Some custom level') 18 storage.debug('A debugging message', extra_tags='extra-tag') 19 storage.warning('A warning') 20 storage.error('An error') 21 storage.success('This was a triumph.') 22 23 24 class BaseTest(unittest.TestCase): 25 storage_class = Storage 26 restore_settings = ['MESSAGE_LEVEL', 'MESSAGE_TAGS'] 27 28 def setUp(self): 29 self._remembered_settings = {} 30 for setting in self.restore_settings: 31 if hasattr(settings, setting): 32 self._remembered_settings[setting] = getattr(settings, setting) 33 delattr(settings._wrapped, setting) 34 35 def tearDown(self): 36 for setting in self.restore_settings: 37 self.restore_setting(setting) 38 39 def restore_setting(self, setting): 40 if setting in self._remembered_settings: 41 value = self._remembered_settings.pop(setting) 42 setattr(settings, setting, value) 43 elif hasattr(settings, setting): 44 delattr(settings._wrapped, setting) 45 46 def get_request(self): 47 return http.HttpRequest() 48 49 def get_response(self): 50 return http.HttpResponse() 51 52 def get_storage(self, data=None): 53 """ 54 Returns the storage backend, setting its loaded data to the ``data`` 55 argument. 56 57 This method avoids the storage ``_get`` method from getting called so 58 that other parts of the storage backend can be tested independent of 59 the message retrieval logic. 60 """ 61 storage = self.storage_class(self.get_request()) 62 storage._loaded_data = data or [] 63 return storage 64 65 def test_add(self): 66 storage = self.get_storage() 67 self.assertFalse(storage.added_new) 68 storage.add(constants.INFO, 'Test message 1') 69 self.assert_(storage.added_new) 70 storage.add(constants.INFO, 'Test message 2', extra_tags='tag') 71 self.assertEqual(len(storage), 2) 72 73 def test_add_lazy_translation(self): 74 storage = self.get_storage() 75 response = self.get_response() 76 77 storage.add(constants.INFO, ugettext_lazy('lazy message')) 78 storage.update(response) 79 80 storing = self.stored_messages_count(storage, response) 81 self.assertEqual(storing, 1) 82 83 def test_no_update(self): 84 storage = self.get_storage() 85 response = self.get_response() 86 storage.update(response) 87 storing = self.stored_messages_count(storage, response) 88 self.assertEqual(storing, 0) 89 90 def test_add_update(self): 91 storage = self.get_storage() 92 response = self.get_response() 93 94 storage.add(constants.INFO, 'Test message 1') 95 storage.add(constants.INFO, 'Test message 1', extra_tags='tag') 96 storage.update(response) 97 98 storing = self.stored_messages_count(storage, response) 99 self.assertEqual(storing, 2) 100 101 def test_existing_add_read_update(self): 102 storage = self.get_existing_storage() 103 response = self.get_response() 104 105 storage.add(constants.INFO, 'Test message 3') 106 list(storage) # Simulates a read 107 storage.update(response) 108 109 storing = self.stored_messages_count(storage, response) 110 self.assertEqual(storing, 0) 111 112 def test_existing_read_add_update(self): 113 storage = self.get_existing_storage() 114 response = self.get_response() 115 116 list(storage) # Simulates a read 117 storage.add(constants.INFO, 'Test message 3') 118 storage.update(response) 119 120 storing = self.stored_messages_count(storage, response) 121 self.assertEqual(storing, 1) 122 123 def stored_messages_count(self, storage, response): 124 """ 125 Returns the number of messages being stored after a 126 ``storage.update()`` call. 127 """ 128 raise NotImplementedError('This method must be set by a subclass.') 129 130 def test_get(self): 131 raise NotImplementedError('This method must be set by a subclass.') 132 133 def get_existing_storage(self): 134 return self.get_storage([Message(constants.INFO, 'Test message 1'), 135 Message(constants.INFO, 'Test message 2', 136 extra_tags='tag')]) 137 138 def test_existing_read(self): 139 """ 140 Tests that reading the existing storage doesn't cause the data to be 141 lost. 142 """ 143 storage = self.get_existing_storage() 144 self.assertFalse(storage.used) 145 # After iterating the storage engine directly, the used flag is set. 146 data = list(storage) 147 self.assert_(storage.used) 148 # The data does not disappear because it has been iterated. 149 self.assertEqual(data, list(storage)) 150 151 def test_existing_add(self): 152 storage = self.get_existing_storage() 153 self.assertFalse(storage.added_new) 154 storage.add(constants.INFO, 'Test message 3') 155 self.assert_(storage.added_new) 156 157 def test_default_level(self): 158 storage = self.get_storage() 159 add_level_messages(storage) 160 self.assertEqual(len(storage), 5) 161 162 def test_low_level(self): 163 storage = self.get_storage() 164 storage.level = 5 165 add_level_messages(storage) 166 self.assertEqual(len(storage), 6) 167 168 def test_high_level(self): 169 storage = self.get_storage() 170 storage.level = 30 171 add_level_messages(storage) 172 self.assertEqual(len(storage), 2) 173 174 def test_settings_level(self): 175 settings.MESSAGE_LEVEL = 29 176 storage = self.get_storage() 177 add_level_messages(storage) 178 self.assertEqual(len(storage), 3) 179 180 def test_tags(self): 181 storage = self.get_storage() 182 storage.level = 0 183 add_level_messages(storage) 184 tags = [msg.tags for msg in storage] 185 self.assertEqual(tags, 186 ['info', '', 'extra-tag debug', 'warning', 'error', 187 'success']) 188 189 def test_custom_tags(self): 190 settings.MESSAGE_TAGS = { 191 constants.INFO: 'info', 192 constants.DEBUG: '', 193 constants.WARNING: '', 194 constants.ERROR: 'bad', 195 29: 'custom', 196 } 197 # LEVEL_TAGS is a constant defined in the 198 # django.contrib.messages.storage.base module, so after changing 199 # settings.MESSAGE_TAGS, we need to update that constant too. 200 base.LEVEL_TAGS = utils.get_level_tags() 201 try: 202 storage = self.get_storage() 203 storage.level = 0 204 add_level_messages(storage) 205 tags = [msg.tags for msg in storage] 206 self.assertEqual(tags, 207 ['info', 'custom', 'extra-tag', '', 'bad', 'success']) 208 finally: 209 # Ensure the level tags constant is put back like we found it. 210 self.restore_setting('MESSAGE_TAGS') 211 base.LEVEL_TAGS = utils.get_level_tags() -
new file django/contrib/messages/tests/cookie.py
diff -r ed90e13d2065 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 5 6 def set_cookie_data(storage, messages, invalid=False, encode_empty=False): 7 """ 8 Sets ``request.COOKIES`` with the encoded data and removes the storage 9 backend's loaded data cache. 10 """ 11 encoded_data = storage._encode(messages, encode_empty=encode_empty) 12 if invalid: 13 # Truncate the first character so that the hash is invalid. 14 encoded_data = encoded_data[1:] 15 storage.request.COOKIES = {CookieStorage.cookie_name: encoded_data} 16 if hasattr(storage, '_loaded_data'): 17 del storage._loaded_data 18 19 20 def stored_cookie_messages_count(storage, response): 21 """ 22 Returns an integer containing the number of messages stored. 23 """ 24 # Get a list of cookies, excluding ones with a max-age of 0 (because 25 # they have been marked for deletion). 26 cookie = response.cookies.get(storage.cookie_name) 27 if not cookie or cookie['max-age'] == 0: 28 return 0 29 data = storage._decode(cookie.value) 30 if not data: 31 return 0 32 if data[-1] == CookieStorage.not_finished: 33 data.pop() 34 return len(data) 35 36 37 class CookieTest(BaseTest): 38 storage_class = CookieStorage 39 40 def stored_messages_count(self, storage, response): 41 return stored_cookie_messages_count(storage, response) 42 43 def test_get(self): 44 storage = self.storage_class(self.get_request()) 45 # Set initial data. 46 example_messages = ['test', 'me'] 47 set_cookie_data(storage, example_messages) 48 # Test that the message actually contains what we expect. 49 self.assertEqual(list(storage), example_messages) 50 51 def test_get_bad_cookie(self): 52 request = self.get_request() 53 storage = self.storage_class(request) 54 # Set initial (invalid) data. 55 example_messages = ['test', 'me'] 56 set_cookie_data(storage, example_messages, invalid=True) 57 # Test that the message actually contains what we expect. 58 self.assertEqual(list(storage), []) 59 60 def test_max_cookie_length(self): 61 """ 62 Tests that, if the data exceeds what is allowed in a cookie, older 63 messages are removed before saving (and returned by the ``update`` 64 method). 65 """ 66 storage = self.get_storage() 67 response = self.get_response() 68 69 for i in range(5): 70 storage.add(constants.INFO, str(i) * 900) 71 unstored_messages = storage.update(response) 72 73 cookie_storing = self.stored_messages_count(storage, response) 74 self.assertEqual(cookie_storing, 4) 75 76 self.assertEqual(len(unstored_messages), 1) 77 self.assert_(unstored_messages[0].message == '0' * 900) -
new file django/contrib/messages/tests/fallback.py
diff -r ed90e13d2065 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 ed90e13d2065 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 ed90e13d2065 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/user_messages.py
diff -r ed90e13d2065 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 ed90e13d2065 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 ed90e13d2065 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.messages.compat import compat_get_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 'messages': compat_get_messages(request), 41 42 'perms': lazy(lambda: PermWrapper(get_user()), PermWrapper)(), 42 43 } 43 44 -
django/core/files/storage.py
diff -r ed90e13d2065 django/core/files/storage.py
a b 8 8 from django.core.files.move import file_move_safe 9 9 from django.utils.encoding import force_unicode, smart_str 10 10 from django.utils.functional import LazyObject 11 from django.utils.import lib import import_module11 from django.utils.importcls import get_class 12 12 from django.utils.text import get_valid_filename 13 13 from django.utils._os import safe_join 14 14 … … 221 221 def get_storage_class(import_path=None): 222 222 if import_path is None: 223 223 import_path = settings.DEFAULT_FILE_STORAGE 224 try: 225 dot = import_path.rindex('.') 226 except ValueError: 227 raise ImproperlyConfigured("%s isn't a storage module." % import_path) 228 module, classname = import_path[:dot], import_path[dot+1:] 229 try: 230 mod = import_module(module) 231 except ImportError, e: 232 raise ImproperlyConfigured('Error importing storage module %s: "%s"' % (module, e)) 233 try: 234 return getattr(mod, classname) 235 except AttributeError: 236 raise ImproperlyConfigured('Storage module "%s" does not define a "%s" class.' % (module, classname)) 224 return get_class(import_path) 237 225 238 226 class DefaultStorage(LazyObject): 239 227 def _setup(self): -
new file django/utils/importcls.py
diff -r ed90e13d2065 django/utils/importcls.py
- + 1 from django.utils.importlib import import_module 2 3 4 def get_class(import_path): 5 """ 6 Imports the class described by import_path, where import_path is the full 7 Python path to the class. 8 """ 9 try: 10 dot = import_path.rindex('.') 11 except ValueError: 12 raise ImproperlyConfigured("%s isn't a Python path." % import_path) 13 module, classname = import_path[:dot], import_path[dot+1:] 14 try: 15 mod = import_module(module) 16 except ImportError, e: 17 raise ImproperlyConfigured('Error importing module %s: "%s"' % (module, e)) 18 try: 19 return getattr(mod, classname) 20 except AttributeError: 21 raise ImproperlyConfigured('Module "%s" does not define a "%s" class.' % (module, classname)) -
django/views/generic/create_update.py
diff -r ed90e13d2065 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 10 from django.contrib.messages.compat import compat_add_message 9 11 10 12 11 13 def apply_extra_context(extra_context, context): … … 110 112 form = form_class(request.POST, request.FILES) 111 113 if form.is_valid(): 112 114 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})115 116 compat_add_message(request, messages.SUCCESS, ugettext("The %(verbose_name)s was created successfully.") % {"verbose_name": model._meta.verbose_name}) 115 117 return redirect(post_save_redirect, new_object) 116 118 else: 117 119 form = form_class() … … 152 154 form = form_class(request.POST, request.FILES, instance=obj) 153 155 if form.is_valid(): 154 156 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}) 157 compat_add_message(request, messages.SUCCESS, ugettext("The %(verbose_name)s was updated successfully.") % {"verbose_name": model._meta.verbose_name}) 157 158 return redirect(post_save_redirect, obj) 158 159 else: 159 160 form = form_class(instance=obj) … … 194 195 195 196 if request.method == 'POST': 196 197 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}) 198 compat_add_message(request, messages.SUCCESS, ugettext("The %(verbose_name)s was deleted.") % {"verbose_name": model._meta.verbose_name}) 199 199 return HttpResponseRedirect(post_delete_redirect) 200 200 else: 201 201 if not template_name: -
docs/index.txt
diff -r ed90e13d2065 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 ed90e13d2065 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``) and the 32 associated methods (such as ``user.message_set.create``), which have 33 been deprecated since the 1.2 release, will be removed. The 34 :ref:`messages framework <ref-contrib-messages>` should be used 35 instead. 36 31 37 * 2.0 32 38 * ``django.views.defaults.shortcut()``. This function has been moved 33 39 to ``django.contrib.contenttypes.views.shortcut()`` as part of the -
docs/ref/contrib/index.txt
diff -r ed90e13d2065 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 ed90e13d2065 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 message 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 :setting:`MIDDLEWARE_CLASSES` contains ``'django.contrib.messages.middleware.MessageMiddleware'``. 30 31 * Edit the :setting:`TEMPLATE_CONTEXT_PROCESSORS` setting and make sure 32 :setting:`TEMPLATE_CONTEXT_PROCESSORS` contains ``'django.contrib.messages.context_processors.messages'``. 33 34 * Add ``'django.contrib.messages'`` to your :setting:`INSTALLED_APPS` setting 35 36 The default ``settings.py`` created by ``django-admin.py startproject`` has 37 ``MessageMiddleware`` activated and the ``django.contrib.messages`` app 38 installed. Also, the default value for :setting:`TEMPLATE_CONTEXT_PROCESSORS` 39 contains ``'django.contrib.messages.context_processors.messages'``. 40 41 If you don't want to use messages, you can remove the 42 ``MessageMiddleware`` line from :setting:`MIDDLEWARE_CLASSES`, the ``messages`` 43 context processor from :setting:`TEMPLATE_CONTEXT_PROCESSORS` and 44 ``'django.contrib.messages'`` from your :setting:`INSTALLED_APPS`. 45 46 Configuring the message engine 47 ============================== 48 49 By default, Django first tries to store messages in a lightweight cookie, 50 and falls back to storing any messages that don't fit in a session variable. 51 52 Storage backends 53 ---------------- 54 55 The messages framework can use different backends to store temporary messages. 56 To change which backend is being used, add a `MESSAGE_STORAGE`_ to your 57 settings, referencing the module and class of the storage class. For 58 example:: 59 60 MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage' 61 62 The value should be the full path of the desired storage class. 63 64 Four storage classes are included: 65 66 ``'django.contrib.messages.storage.session.SessionStorage'`` 67 This class stores all messages inside of the request's session. It 68 requires Django's ``contrib.session`` application. 69 70 ``'django.contrib.messages.storage.cookie.CookieStorage'`` 71 This class stores the message data in a cookie (signed with a secret hash to 72 prevent manipulation) to persist notifications across requests. Old messages 73 are dropped if the cookie data size would exceed 4096 bytes. 74 75 ``'django.contrib.messages.storage.fallback.FallbackStorage'`` 76 This class first uses CookieStorage for all messages, falling back to using 77 SessionStorage for the messages that could not fit in a single cookie. 78 79 Since it is uses SessionStorage, it also requires Django's 80 ``contrib.session`` application. 81 82 ``'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'`` 83 This is the default temporary storage class. 84 85 This class extends FallbackStorage and adds compatibility methods to 86 to retrieve any messages stored in the user Message model by code that 87 has not yet been updated to use the new API. This storage is temporary 88 (because it makes use of code that is pending deprecation) and will be 89 removed in Django 1.4. At that time, the default storage will become 90 ``django.contrib.messages.storage.fallback.FallbackStorage``. For more 91 information, see `LegacyFallbackStorage`_ below. 92 93 To write your own storage class, subclass the ``BaseStorage`` class in 94 ``django.contrib.messages.storage.base`` and implement the ``_get`` and 95 ``_store`` methods. 96 97 LegacyFallbackStorage 98 ^^^^^^^^^^^^^^^^^^^^^ 99 100 The ``LegacyFallbackStorage`` is a temporary tool to facilitate the transition 101 from the deprecated ``user.message_set`` API and will be removed in Django 1.4. 102 103 In addition to the functionality in the ``FallbackStorage``, it adds a custom, 104 read-only storage class that retrieves messages from the user ``Message`` 105 model. Any messages that were stored in the ``Message`` model (e.g., by code 106 that has not yet been updated to use the messages framework) will be 107 retrieved first, followed those stored in a cookie and session, if any. 108 Since messages stored in the ``Message`` model do not have a concept of levels, 109 they will be assigned the ``INFO`` level by default. 110 111 Existing coding that calls ``user.message_set.create`` will continue to work 112 in Django 1.2 and Django 1.3. In Django 1.2, the method raises a 113 ``PendingDeprecationWarning`` (which is not shown by default). In Django 1.3, 114 the method will raise a louder ``DeprecationWarning`` stating that the code at 115 fault needs to be updated. Any code that uses ``user.message_set`` must be 116 updated to use the messages framework before Django 1.4, when the old API will 117 be removed. 118 119 120 Message levels 121 -------------- 122 123 The messages framework is based on a configurable level architecture similar 124 to that of the Python logging module. Message levels allow you to group 125 messages by type so they can be filtered or displayed differently in views and 126 templates. 127 128 The built-in levels (which can be imported from ``django.contrib.messages`` 129 directly) are: 130 131 =========== ======== 132 Constant Purpose 133 =========== ======== 134 ``DEBUG`` Development-related messages that will be ignored (or removed) in a production deployment 135 ``INFO`` Informational messages for the user 136 ``SUCCESS`` An action was successful, e.g. "Your profile was updated successfully" 137 ``WARNING`` A failure did not occur but may be imminent 138 ``ERROR`` An action was **not** successful or some other failure occurred 139 =========== ======== 140 141 The `MESSAGE_LEVEL`_ setting can be used to change the minimum recorded 142 level. Attempts to add messages of a level less than this will be ignored. 143 144 Message tags 145 ------------ 146 147 Message tags are a string representation of the message level plus any 148 extra tags that were added directly in the view (see 149 `Adding extra message tags`_ below for more details). Tags are stored in a 150 string and are separated by spaces. Typically, message tags 151 are used as CSS classes to customize message style based on message type. By 152 default, each level has a single tag that's a lowercase version of its own 153 constant: 154 155 ============== =========== 156 Level Constant Tag 157 ============== =========== 158 ``DEBUG`` ``debug`` 159 ``INFO`` ``info`` 160 ``SUCCESS`` ``success`` 161 ``WARNING`` ``warning`` 162 ``ERROR`` ``error`` 163 ============== =========== 164 165 To change the default tags for a message level (either built-in or custom), 166 set the `MESSAGE_TAGS`_ setting to a dictionary containing the levels 167 you wish to change. As this extends the default tags, you only need to provide 168 tags for the levels you wish to override:: 169 170 from django.contrib.messages import constants as messages 171 MESSAGE_TAGS = { 172 messages.INFO: '', 173 50: 'critical', 174 } 175 176 Using messages in views and templates 177 ===================================== 178 179 Adding a message 180 ---------------- 181 182 The middleware attaches an instance of a temporary storage class called 183 ``messages`` to your ``request``. To add a message, call:: 184 185 request.messages.add(messages.INFO, 'Hello world.') 186 187 Some other methods provide a standard way to add messages with commonly 188 used tags (which are usually represented as HTML classes for the message):: 189 190 request.messages.debug('%s SQL statements were executed.' % count) 191 request.messages.info('Three credits remain in your account.') 192 request.messages.success('Profile details updated.') 193 request.messages.warning('Your account expires in three days.') 194 request.messages.error('Document deleted.') 195 196 Displaying messages 197 ------------------- 198 199 In your template, use something like:: 200 201 {% if messages %} 202 <ul class="messages"> 203 {% for message in messages %} 204 <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li> 205 {% endfor %} 206 </ul> 207 {% endif %} 208 209 If you're using the context processor, your template should be rendered with a 210 ``RequestContext``. Otherwise, ensure ``request.messages`` is available to 211 the template context. 212 213 Creating custom message levels 214 ------------------------------ 215 216 Messages levels are nothing more than integers, so you can define your own 217 level constants and use them to create more customized user feedback, e.g.:: 218 219 CRITICAL = 50 220 221 def my_view(request): 222 request.messages.add(CRITICAL, 'A serious error occured.') 223 224 When creating custom message levels you should be careful to avoid overloading 225 existing levels. The values for the built-in levels are: 226 227 ============== ===== 228 Level Constant Value 229 ============== ===== 230 ``DEBUG`` 10 231 ``INFO`` 20 232 ``SUCCESS`` 25 233 ``WARNING`` 30 234 ``ERROR`` 40 235 ============== ===== 236 237 If you need to identify the custom levels in your HTML or CSS, you need to 238 provide a mapping via the `MESSAGE_TAGS`_ setting. 239 240 **Note:** If you are creating a reusable application, it is recommended to use 241 only the built-in `message levels`_ and not rely on any custom levels. 242 243 Changing the minimum recorded level per-request 244 ----------------------------------------------- 245 246 The minimum recorded level can be set per request by changing the ``level`` 247 attribute of the messages storage instance:: 248 249 from django.contrib import messages 250 251 # Change the messages level to ensure the debug message is added. 252 request.messages.level = messages.DEBUG 253 request.messages.debug('Test message...') 254 255 # In another request, record only messages with a level of WARNING and higher 256 request.messages.level = messages.WARNING 257 request.messages.success('Your profile was updated.') # ignored 258 request.messages.warning('Your account is about to expire.') # recorded 259 260 # Set the messages level back to default. 261 request.messages.level = None 262 263 For more information, on how the minimum recorded level functions, see 264 `Message levels`_ above. 265 266 Adding extra message tags 267 ------------------------- 268 269 For more direct control over message tags, you can optionally provide a string 270 containing extra tags to any of the add methods:: 271 272 request.messages.add(messages.INFO, 'Over 9000!', extra_tags='dragonball') 273 request.messages.error('Email box full', extra_tags='email') 274 275 Extra tags are added before the default tag for that level and are space 276 separated. 277 278 Expiration of messages 279 ====================== 280 281 The messages are marked to be cleared when the storage instance is iterated 282 (and cleared when the response is processed). 283 284 To avoid the messages being cleared, you can set 285 ``request.messages.used = False`` after iterating. 286 287 Settings 288 ======== 289 290 A few :ref:`Django settings <ref-settings>` give you control over message behavior: 291 292 MESSAGE_LEVEL 293 ------------- 294 295 Default: ``messages.INFO`` 296 297 Important: If you override this in your settings file and rely on any of the built-in 298 constants, you must import the constants module directly to avoid the potential 299 for circular imports. 300 301 This sets the minimum message that will be saved in the message storage. See 302 `Message levels`_ above for more details. 303 304 MESSAGE_STORAGE 305 --------------- 306 307 Default: ``'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'`` 308 309 Controls where Django stores message data. Valid values are: 310 311 * ``'django.contrib.messages.storage.fallback.FallbackStorage'`` 312 * ``'django.contrib.messages.storage.session.SessionStorage'`` 313 * ``'django.contrib.messages.storage.cookie.CookieStorage'`` 314 * ``'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'`` 315 316 See `Storage backends`_ for more details. 317 318 MESSAGE_TAGS 319 ------------ 320 321 Default:: 322 323 {messages.DEBUG: 'debug', 324 messages.INFO: 'info', 325 messages.SUCCESS: 'success', 326 messages.WARNING: 'warning', 327 messages.ERROR: 'error',} 328 329 Important: If you override this in your settings file and rely on any of the built-in 330 constants, you must import the constants module directly to avoid the potential 331 for circular imports. 332 333 This sets the mapping of message level to message tag, which is typically 334 rendered as a CSS class in HTML. If you specify a value, it will extend 335 the default. This means you only have to specify those values which you need 336 to override. See `Displaying messages`_ above for more details. 337 338 .. _Django settings: ../settings/ 339 -
docs/ref/settings.txt
diff -r ed90e13d2065 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 ed90e13d2065 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 user model (using ``user.message_set.create``) or through 375 the :ref:`messages framework <ref-contrib-messages>`. 374 376 375 377 * ``perms`` -- An instance of 376 378 ``django.core.context_processors.PermWrapper``, representing the 377 379 permissions that the currently logged-in user has. 378 380 381 .. versionchanged:: 1.2 382 The ``messages`` variable was changed to include any messages added 383 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/topics/auth.txt
diff -r ed90e13d2065 docs/topics/auth.txt
a b 1289 1289 Messages 1290 1290 ======== 1291 1291 1292 **NOTE:** This functionality is pending deprecation and will be removed in 1293 Django 1.4. It is recommended to use the 1294 :ref:`messages framework <ref-contrib-messages>` for new projects whenever 1295 possible. 1296 1292 1297 The message system is a lightweight way to queue messages for given users. 1293 1298 1294 1299 A message is associated with a :class:`~django.contrib.auth.models.User`. … … 1334 1339 </ul> 1335 1340 {% endif %} 1336 1341 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. 1342 .. versionchanged:: 1.2 1343 The ``messages`` template variable uses a backwards compatible method in the 1344 :ref:`messages framework <ref-contrib-messages>` to retrieve messages from 1345 both the user ``Message`` model and from the new framework. Unlike in 1346 previous revisions, the messages will not be erased unless they are actually 1347 displayed. 1340 1348 1341 1349 Finally, note that this messages framework only works with users in the user 1342 1350 database. To send messages to anonymous users, use the 1343 :ref:` session framework <topics-http-sessions>`.1351 :ref:`messages framework <ref-contrib-messages>`. 1344 1352 1345 1353 .. _authentication-backends: 1346 1354 -
tests/runtests.py
diff -r ed90e13d2065 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