mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	[soc2009/multidb] Merged up to trunk r11804.
git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/multidb@11805 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
		
							
								
								
									
										3
									
								
								AUTHORS
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								AUTHORS
									
									
									
									
									
								
							| @@ -60,6 +60,7 @@ answer newbie questions, and generally made Django that much better: | ||||
|     Ned Batchelder <http://www.nedbatchelder.com/> | ||||
|     batiste@dosimple.ch | ||||
|     Batman | ||||
|     Chris Beaven <http://smileychris.tactful.co.nz/> | ||||
|     Brian Beck <http://blog.brianbeck.com/> | ||||
|     Shannon -jj Behrens <http://jjinux.blogspot.com/> | ||||
|     Esdras Beleza <linux@esdrasbeleza.com> | ||||
| @@ -299,6 +300,7 @@ answer newbie questions, and generally made Django that much better: | ||||
|     Jason McBrayer <http://www.carcosa.net/jason/> | ||||
|     Kevin McConnell <kevin.mcconnell@gmail.com> | ||||
|     mccutchen@gmail.com | ||||
|     Tobias McNulty <http://www.caktusgroup.com/blog> | ||||
|     Christian Metts | ||||
|     michael.mcewan@gmail.com | ||||
|     michal@plovarna.cz | ||||
| @@ -391,7 +393,6 @@ answer newbie questions, and generally made Django that much better: | ||||
|     Jozko Skrablin <jozko.skrablin@gmail.com> | ||||
|     Ben Slavin <benjamin.slavin@gmail.com> | ||||
|     sloonz <simon.lipp@insa-lyon.fr> | ||||
|     SmileyChris <smileychris@gmail.com> | ||||
|     Warren Smith <warren@wandrsmith.net> | ||||
|     smurf@smurf.noris.de | ||||
|     Vsevolod Solovyov | ||||
|   | ||||
							
								
								
									
										1
									
								
								TODO
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								TODO
									
									
									
									
									
								
							| @@ -7,6 +7,7 @@ Required for v1.2 | ||||
|  * Finalize the sql.Query internals | ||||
|    * Clean up the use of db.backend.query_class() | ||||
|    * Verify it still works with GeoDjango | ||||
|  * Modify the admin interface to support multiple databases (doh). | ||||
|  | ||||
| Optional for v1.2 | ||||
| ~~~~~~~~~~~~~~~~~ | ||||
|   | ||||
| @@ -127,7 +127,7 @@ class UserSettingsHolder(object): | ||||
|         return getattr(self.default_settings, name) | ||||
|  | ||||
|     def __dir__(self): | ||||
|         return dir(self) + dir(self.default_settings) | ||||
|         return self.__dict__.keys() + dir(self.default_settings) | ||||
|  | ||||
|     # For Python < 2.6: | ||||
|     __members__ = property(lambda self: self.__dir__()) | ||||
|   | ||||
| @@ -175,6 +175,7 @@ TEMPLATE_CONTEXT_PROCESSORS = ( | ||||
|     'django.core.context_processors.i18n', | ||||
|     'django.core.context_processors.media', | ||||
| #    'django.core.context_processors.request', | ||||
|     'django.contrib.messages.context_processors.messages', | ||||
| ) | ||||
|  | ||||
| # Output to use in template system for invalid (e.g. misspelled) variables. | ||||
| @@ -311,6 +312,7 @@ MIDDLEWARE_CLASSES = ( | ||||
|     'django.contrib.sessions.middleware.SessionMiddleware', | ||||
|     'django.middleware.csrf.CsrfViewMiddleware', | ||||
|     'django.contrib.auth.middleware.AuthenticationMiddleware', | ||||
|     'django.contrib.messages.middleware.MessageMiddleware', | ||||
| #     'django.middleware.http.ConditionalGetMiddleware', | ||||
| #     'django.middleware.gzip.GZipMiddleware', | ||||
| ) | ||||
| @@ -396,6 +398,16 @@ CSRF_FAILURE_VIEW = 'django.views.csrf.csrf_failure' | ||||
| CSRF_COOKIE_NAME = 'csrftoken' | ||||
| CSRF_COOKIE_DOMAIN = None | ||||
|  | ||||
| ############ | ||||
| # MESSAGES # | ||||
| ############ | ||||
|  | ||||
| # Class to use as messges backend | ||||
| MESSAGE_STORAGE = 'django.contrib.messages.storage.user_messages.LegacyFallbackStorage' | ||||
|  | ||||
| # Default values of MESSAGE_LEVEL and MESSAGE_TAGS are defined within | ||||
| # django.contrib.messages to avoid imports in this settings file. | ||||
|  | ||||
| ########### | ||||
| # TESTING # | ||||
| ########### | ||||
|   | ||||
| @@ -66,6 +66,7 @@ MIDDLEWARE_CLASSES = ( | ||||
|     'django.contrib.sessions.middleware.SessionMiddleware', | ||||
|     'django.middleware.csrf.CsrfViewMiddleware', | ||||
|     'django.contrib.auth.middleware.AuthenticationMiddleware', | ||||
|     'django.contrib.messages.middleware.MessageMiddleware', | ||||
| ) | ||||
|  | ||||
| ROOT_URLCONF = '{{ project_name }}.urls' | ||||
| @@ -81,4 +82,5 @@ INSTALLED_APPS = ( | ||||
|     'django.contrib.contenttypes', | ||||
|     'django.contrib.sessions', | ||||
|     'django.contrib.sites', | ||||
|     'django.contrib.messages', | ||||
| ) | ||||
|   | ||||
| @@ -6,6 +6,7 @@ from django.contrib.contenttypes.models import ContentType | ||||
| from django.contrib.admin import widgets | ||||
| from django.contrib.admin import helpers | ||||
| from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects, model_ngettext, model_format_dict | ||||
| from django.contrib import messages | ||||
| from django.views.decorators.csrf import csrf_protect | ||||
| from django.core.exceptions import PermissionDenied | ||||
| from django.db import models, transaction | ||||
| @@ -541,9 +542,9 @@ class ModelAdmin(BaseModelAdmin): | ||||
|     def message_user(self, request, message): | ||||
|         """ | ||||
|         Send a message to the user. The default implementation | ||||
|         posts a message using the auth Message object. | ||||
|         posts a message using the django.contrib.messages backend. | ||||
|         """ | ||||
|         request.user.message_set.create(message=message) | ||||
|         messages.info(request, message) | ||||
|  | ||||
|     def save_form(self, request, form, change): | ||||
|         """ | ||||
|   | ||||
| @@ -452,7 +452,7 @@ class AdminSite(object): | ||||
|         import warnings | ||||
|         warnings.warn( | ||||
|             "AdminSite.root() is deprecated; use include(admin.site.urls) instead.", | ||||
|             PendingDeprecationWarning | ||||
|             DeprecationWarning | ||||
|         ) | ||||
|  | ||||
|         # | ||||
|   | ||||
| @@ -6,6 +6,7 @@ from django.contrib.sites.models import Site | ||||
| from django.conf import settings | ||||
| from django.utils.importlib import import_module | ||||
| from django.utils.translation import ugettext_lazy as _ | ||||
| from django.contrib import messages | ||||
|  | ||||
|  | ||||
| def template_validator(request): | ||||
| @@ -23,7 +24,7 @@ def template_validator(request): | ||||
|         form = TemplateValidatorForm(settings_modules, site_list, | ||||
|                                      data=request.POST) | ||||
|         if form.is_valid(): | ||||
|             request.user.message_set.create(message='The template is valid.') | ||||
|             messages.info(request, 'The template is valid.') | ||||
|     else: | ||||
|         form = TemplateValidatorForm(settings_modules, site_list) | ||||
|     return render_to_response('admin/template_validator.html', { | ||||
|   | ||||
| @@ -3,6 +3,7 @@ from django.conf import settings | ||||
| from django.contrib import admin | ||||
| from django.contrib.auth.forms import UserCreationForm, UserChangeForm, AdminPasswordChangeForm | ||||
| from django.contrib.auth.models import User, Group | ||||
| from django.contrib import messages | ||||
| from django.core.exceptions import PermissionDenied | ||||
| from django.http import HttpResponseRedirect, Http404 | ||||
| from django.shortcuts import render_to_response, get_object_or_404 | ||||
| @@ -67,12 +68,13 @@ class UserAdmin(admin.ModelAdmin): | ||||
|                 msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': 'user', 'obj': new_user} | ||||
|                 self.log_addition(request, new_user) | ||||
|                 if "_addanother" in request.POST: | ||||
|                     request.user.message_set.create(message=msg) | ||||
|                     messages.success(request, msg) | ||||
|                     return HttpResponseRedirect(request.path) | ||||
|                 elif '_popup' in request.REQUEST: | ||||
|                     return self.response_add(request, new_user) | ||||
|                 else: | ||||
|                     request.user.message_set.create(message=msg + ' ' + ugettext("You may edit it again below.")) | ||||
|                     messages.success(request, msg + ' ' + | ||||
|                                      ugettext("You may edit it again below.")) | ||||
|                     return HttpResponseRedirect('../%s/' % new_user.id) | ||||
|         else: | ||||
|             form = self.add_form() | ||||
| @@ -104,7 +106,7 @@ class UserAdmin(admin.ModelAdmin): | ||||
|             if form.is_valid(): | ||||
|                 new_user = form.save() | ||||
|                 msg = ugettext('Password changed successfully.') | ||||
|                 request.user.message_set.create(message=msg) | ||||
|                 messages.success(request, msg) | ||||
|                 return HttpResponseRedirect('..') | ||||
|         else: | ||||
|             form = self.change_password_form(user) | ||||
|   | ||||
| @@ -288,6 +288,14 @@ class User(models.Model): | ||||
|                 raise SiteProfileNotAvailable | ||||
|         return self._profile_cache | ||||
|  | ||||
|     def _get_message_set(self): | ||||
|         import warnings | ||||
|         warnings.warn('The user messaging API is deprecated. Please update' | ||||
|                       ' your code to use the new messages framework.', | ||||
|                       category=PendingDeprecationWarning) | ||||
|         return self._message_set | ||||
|     message_set = property(_get_message_set) | ||||
|  | ||||
| class Message(models.Model): | ||||
|     """ | ||||
|     The message system is a lightweight way to queue messages for given | ||||
| @@ -297,7 +305,7 @@ class Message(models.Model): | ||||
|     actions. For example, "The poll Foo was created successfully." is a | ||||
|     message. | ||||
|     """ | ||||
|     user = models.ForeignKey(User) | ||||
|     user = models.ForeignKey(User, related_name='_message_set') | ||||
|     message = models.TextField(_('message')) | ||||
|  | ||||
|     def __unicode__(self): | ||||
|   | ||||
| @@ -317,10 +317,13 @@ class BaseGenericInlineFormSet(BaseModelFormSet): | ||||
|         from django.contrib.contenttypes.models import ContentType | ||||
|         if self.instance is None or self.instance.pk is None: | ||||
|             return self.model._default_manager.none() | ||||
|         return self.model._default_manager.filter(**{ | ||||
|         qs = self.model._default_manager.filter(**{ | ||||
|             self.ct_field.name: ContentType.objects.get_for_model(self.instance), | ||||
|             self.ct_fk_field.name: self.instance.pk, | ||||
|         }) | ||||
|         if not qs.ordered: | ||||
|             qs = qs.order_by(self.model._meta.pk.name) | ||||
|         return qs | ||||
|  | ||||
|     def save_new(self, form, commit=True): | ||||
|         # Avoid a circular import. | ||||
|   | ||||
							
								
								
									
										2
									
								
								django/contrib/messages/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								django/contrib/messages/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| from api import * | ||||
| from constants import * | ||||
							
								
								
									
										84
									
								
								django/contrib/messages/api.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								django/contrib/messages/api.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | ||||
| from django.contrib.messages import constants | ||||
| from django.utils.functional import lazy, memoize | ||||
|  | ||||
| __all__ = ( | ||||
|     'add_message', 'get_messages', | ||||
|     'debug', 'info', 'success', 'warning', 'error', | ||||
| ) | ||||
|  | ||||
|  | ||||
| class MessageFailure(Exception): | ||||
|     pass | ||||
|  | ||||
|  | ||||
| def add_message(request, level, message, extra_tags='', fail_silently=False): | ||||
|     """ | ||||
|     Attempts to add a message to the request using the 'messages' app, falling | ||||
|     back to the user's message_set if MessageMiddleware hasn't been enabled. | ||||
|     """ | ||||
|     if hasattr(request, '_messages'): | ||||
|         return request._messages.add(level, message, extra_tags) | ||||
|     if hasattr(request, 'user') and request.user.is_authenticated(): | ||||
|         return request.user.message_set.create(message=message) | ||||
|     if not fail_silently: | ||||
|         raise MessageFailure('Without the django.contrib.messages ' | ||||
|                                 'middleware, messages can only be added to ' | ||||
|                                 'authenticated users.') | ||||
|  | ||||
|  | ||||
| def get_messages(request): | ||||
|     """ | ||||
|     Returns the message storage on the request if it exists, otherwise returns | ||||
|     user.message_set.all() as the old auth context processor did. | ||||
|     """ | ||||
|     if hasattr(request, '_messages'): | ||||
|         return request._messages | ||||
|  | ||||
|     def get_user(): | ||||
|         if hasattr(request, 'user'): | ||||
|             return request.user | ||||
|         else: | ||||
|             from django.contrib.auth.models import AnonymousUser | ||||
|             return AnonymousUser() | ||||
|  | ||||
|     return lazy(memoize(get_user().get_and_delete_messages, {}, 0), list)() | ||||
|  | ||||
|  | ||||
| def debug(request, message, extra_tags='', fail_silently=False): | ||||
|     """ | ||||
|     Adds a message with the ``DEBUG`` level. | ||||
|     """ | ||||
|     add_message(request, constants.DEBUG, message, extra_tags=extra_tags, | ||||
|                 fail_silently=fail_silently) | ||||
|  | ||||
|  | ||||
| def info(request, message, extra_tags='', fail_silently=False): | ||||
|     """ | ||||
|     Adds a message with the ``INFO`` level. | ||||
|     """ | ||||
|     add_message(request, constants.INFO, message, extra_tags=extra_tags, | ||||
|                 fail_silently=fail_silently) | ||||
|  | ||||
|  | ||||
| def success(request, message, extra_tags='', fail_silently=False): | ||||
|     """ | ||||
|     Adds a message with the ``SUCCESS`` level. | ||||
|     """ | ||||
|     add_message(request, constants.SUCCESS, message, extra_tags=extra_tags, | ||||
|                 fail_silently=fail_silently) | ||||
|  | ||||
|  | ||||
| def warning(request, message, extra_tags='', fail_silently=False): | ||||
|     """ | ||||
|     Adds a message with the ``WARNING`` level. | ||||
|     """ | ||||
|     add_message(request, constants.WARNING, message, extra_tags=extra_tags, | ||||
|                 fail_silently=fail_silently) | ||||
|  | ||||
|  | ||||
| def error(request, message, extra_tags='', fail_silently=False): | ||||
|     """ | ||||
|     Adds a message with the ``ERROR`` level. | ||||
|     """ | ||||
|     add_message(request, constants.ERROR, message, extra_tags=extra_tags, | ||||
|                 fail_silently=fail_silently) | ||||
							
								
								
									
										13
									
								
								django/contrib/messages/constants.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								django/contrib/messages/constants.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| DEBUG = 10 | ||||
| INFO = 20 | ||||
| SUCCESS = 25 | ||||
| WARNING = 30 | ||||
| ERROR = 40 | ||||
|  | ||||
| DEFAULT_TAGS = { | ||||
|     DEBUG: 'debug', | ||||
|     INFO: 'info', | ||||
|     SUCCESS: 'success', | ||||
|     WARNING: 'warning', | ||||
|     ERROR: 'error', | ||||
| } | ||||
							
								
								
									
										8
									
								
								django/contrib/messages/context_processors.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								django/contrib/messages/context_processors.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| from django.contrib.messages.api import get_messages | ||||
|  | ||||
|  | ||||
| def messages(request): | ||||
|     """ | ||||
|     Returns a lazy 'messages' context variable. | ||||
|     """ | ||||
|     return {'messages': get_messages(request)} | ||||
							
								
								
									
										26
									
								
								django/contrib/messages/middleware.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								django/contrib/messages/middleware.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| from django.conf import settings | ||||
| from django.contrib.messages.storage import default_storage | ||||
|  | ||||
|  | ||||
| class MessageMiddleware(object): | ||||
|     """ | ||||
|     Middleware that handles temporary messages. | ||||
|     """ | ||||
|  | ||||
|     def process_request(self, request): | ||||
|         request._messages = default_storage(request) | ||||
|  | ||||
|     def process_response(self, request, response): | ||||
|         """ | ||||
|         Updates the storage backend (i.e., saves the messages). | ||||
|  | ||||
|         If not all messages could not be stored and ``DEBUG`` is ``True``, a | ||||
|         ``ValueError`` is raised. | ||||
|         """ | ||||
|         # A higher middleware layer may return a request which does not contain | ||||
|         # messages storage, so make no assumption that it will be there. | ||||
|         if hasattr(request, '_messages'): | ||||
|             unstored_messages = request._messages.update(response) | ||||
|             if unstored_messages and settings.DEBUG: | ||||
|                 raise ValueError('Not all temporary messages could be stored.') | ||||
|         return response | ||||
							
								
								
									
										1
									
								
								django/contrib/messages/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								django/contrib/messages/models.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| # Models module required so tests are discovered. | ||||
							
								
								
									
										31
									
								
								django/contrib/messages/storage/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								django/contrib/messages/storage/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| from django.conf import settings | ||||
| from django.core.exceptions import ImproperlyConfigured | ||||
| from django.utils.importlib import import_module | ||||
|  | ||||
|  | ||||
| def get_storage(import_path): | ||||
|     """ | ||||
|     Imports the message storage class described by import_path, where | ||||
|     import_path is the full Python path to the class. | ||||
|     """ | ||||
|     try: | ||||
|         dot = import_path.rindex('.') | ||||
|     except ValueError: | ||||
|         raise ImproperlyConfigured("%s isn't a Python path." % import_path) | ||||
|     module, classname = import_path[:dot], import_path[dot + 1:] | ||||
|     try: | ||||
|         mod = import_module(module) | ||||
|     except ImportError, e: | ||||
|         raise ImproperlyConfigured('Error importing module %s: "%s"' % | ||||
|                                    (module, e)) | ||||
|     try: | ||||
|         return getattr(mod, classname) | ||||
|     except AttributeError: | ||||
|         raise ImproperlyConfigured('Module "%s" does not define a "%s" ' | ||||
|                                    'class.' % (module, classname)) | ||||
|  | ||||
|  | ||||
| # Callable with the same interface as the storage classes i.e.  accepts a | ||||
| # 'request' object.  It is wrapped in a lambda to stop 'settings' being used at | ||||
| # the module level | ||||
| default_storage = lambda request: get_storage(settings.MESSAGE_STORAGE)(request) | ||||
							
								
								
									
										181
									
								
								django/contrib/messages/storage/base.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								django/contrib/messages/storage/base.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,181 @@ | ||||
| from django.conf import settings | ||||
| from django.utils.encoding import force_unicode, StrAndUnicode | ||||
| from django.contrib.messages import constants, utils | ||||
|  | ||||
|  | ||||
| LEVEL_TAGS = utils.get_level_tags() | ||||
|  | ||||
|  | ||||
| class Message(StrAndUnicode): | ||||
|     """ | ||||
|     Represents an actual message that can be stored in any of the supported | ||||
|     storage classes (typically session- or cookie-based) and rendered in a view | ||||
|     or template. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, level, message, extra_tags=None): | ||||
|         self.level = int(level) | ||||
|         self.message = message | ||||
|         self.extra_tags = extra_tags | ||||
|  | ||||
|     def _prepare(self): | ||||
|         """ | ||||
|         Prepares the message for serialization by forcing the ``message`` | ||||
|         and ``extra_tags`` to unicode in case they are lazy translations. | ||||
|  | ||||
|         Known "safe" types (None, int, etc.) are not converted (see Django's | ||||
|         ``force_unicode`` implementation for details). | ||||
|         """ | ||||
|         self.message = force_unicode(self.message, strings_only=True) | ||||
|         self.extra_tags = force_unicode(self.extra_tags, strings_only=True) | ||||
|  | ||||
|     def __eq__(self, other): | ||||
|         return isinstance(other, Message) and self.level == other.level and \ | ||||
|                                               self.message == other.message | ||||
|  | ||||
|     def __unicode__(self): | ||||
|         return force_unicode(self.message) | ||||
|  | ||||
|     def _get_tags(self): | ||||
|         label_tag = force_unicode(LEVEL_TAGS.get(self.level, ''), | ||||
|                                   strings_only=True) | ||||
|         extra_tags = force_unicode(self.extra_tags, strings_only=True) | ||||
|         if extra_tags and label_tag: | ||||
|             return u' '.join([extra_tags, label_tag]) | ||||
|         elif extra_tags: | ||||
|             return extra_tags | ||||
|         elif label_tag: | ||||
|             return label_tag | ||||
|         return '' | ||||
|     tags = property(_get_tags) | ||||
|  | ||||
|  | ||||
| class BaseStorage(object): | ||||
|     """ | ||||
|     This is the base backend for temporary message storage. | ||||
|  | ||||
|     This is not a complete class; to be a usable storage backend, it must be | ||||
|     subclassed and the two methods ``_get`` and ``_store`` overridden. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, request, *args, **kwargs): | ||||
|         self.request = request | ||||
|         self._queued_messages = [] | ||||
|         self.used = False | ||||
|         self.added_new = False | ||||
|         super(BaseStorage, self).__init__(*args, **kwargs) | ||||
|  | ||||
|     def __len__(self): | ||||
|         return len(self._loaded_messages) + len(self._queued_messages) | ||||
|  | ||||
|     def __iter__(self): | ||||
|         self.used = True | ||||
|         if self._queued_messages: | ||||
|             self._loaded_messages.extend(self._queued_messages) | ||||
|             self._queued_messages = [] | ||||
|         return iter(self._loaded_messages) | ||||
|  | ||||
|     def __contains__(self, item): | ||||
|         return item in self._loaded_messages or item in self._queued_messages | ||||
|  | ||||
|     @property | ||||
|     def _loaded_messages(self): | ||||
|         """ | ||||
|         Returns a list of loaded messages, retrieving them first if they have | ||||
|         not been loaded yet. | ||||
|         """ | ||||
|         if not hasattr(self, '_loaded_data'): | ||||
|             messages, all_retrieved = self._get() | ||||
|             self._loaded_data = messages or [] | ||||
|         return self._loaded_data | ||||
|  | ||||
|     def _get(self, *args, **kwargs): | ||||
|         """ | ||||
|         Retrieves a list of stored messages. Returns a tuple of the messages | ||||
|         and a flag indicating whether or not all the messages originally | ||||
|         intended to be stored in this storage were, in fact, stored and | ||||
|         retrieved; e.g., ``(messages, all_retrieved)``. | ||||
|  | ||||
|         **This method must be implemented by a subclass.** | ||||
|  | ||||
|         If it is possible to tell if the backend was not used (as opposed to | ||||
|         just containing no messages) then ``None`` should be returned in | ||||
|         place of ``messages``. | ||||
|         """ | ||||
|         raise NotImplementedError() | ||||
|  | ||||
|     def _store(self, messages, response, *args, **kwargs): | ||||
|         """ | ||||
|         Stores a list of messages, returning a list of any messages which could | ||||
|         not be stored. | ||||
|  | ||||
|         One type of object must be able to be stored, ``Message``. | ||||
|  | ||||
|         **This method must be implemented by a subclass.** | ||||
|         """ | ||||
|         raise NotImplementedError() | ||||
|  | ||||
|     def _prepare_messages(self, messages): | ||||
|         """ | ||||
|         Prepares a list of messages for storage. | ||||
|         """ | ||||
|         for message in messages: | ||||
|             message._prepare() | ||||
|  | ||||
|     def update(self, response): | ||||
|         """ | ||||
|         Stores all unread messages. | ||||
|  | ||||
|         If the backend has yet to be iterated, previously stored messages will | ||||
|         be stored again. Otherwise, only messages added after the last | ||||
|         iteration will be stored. | ||||
|         """ | ||||
|         self._prepare_messages(self._queued_messages) | ||||
|         if self.used: | ||||
|             return self._store(self._queued_messages, response) | ||||
|         elif self.added_new: | ||||
|             messages = self._loaded_messages + self._queued_messages | ||||
|             return self._store(messages, response) | ||||
|  | ||||
|     def add(self, level, message, extra_tags=''): | ||||
|         """ | ||||
|         Queues a message to be stored. | ||||
|  | ||||
|         The message is only queued if it contained something and its level is | ||||
|         not less than the recording level (``self.level``). | ||||
|         """ | ||||
|         if not message: | ||||
|             return | ||||
|         # Check that the message level is not less than the recording level. | ||||
|         level = int(level) | ||||
|         if level < self.level: | ||||
|             return | ||||
|         # Add the message. | ||||
|         self.added_new = True | ||||
|         message = Message(level, message, extra_tags=extra_tags) | ||||
|         self._queued_messages.append(message) | ||||
|  | ||||
|     def _get_level(self): | ||||
|         """ | ||||
|         Returns the minimum recorded level. | ||||
|  | ||||
|         The default level is the ``MESSAGE_LEVEL`` setting. If this is | ||||
|         not found, the ``INFO`` level is used. | ||||
|         """ | ||||
|         if not hasattr(self, '_level'): | ||||
|             self._level = getattr(settings, 'MESSAGE_LEVEL', constants.INFO) | ||||
|         return self._level | ||||
|  | ||||
|     def _set_level(self, value=None): | ||||
|         """ | ||||
|         Sets a custom minimum recorded level. | ||||
|  | ||||
|         If set to ``None``, the default level will be used (see the | ||||
|         ``_get_level`` method). | ||||
|         """ | ||||
|         if value is None and hasattr(self, '_level'): | ||||
|             del self._level | ||||
|         else: | ||||
|             self._level = int(value) | ||||
|  | ||||
|     level = property(_get_level, _set_level, _set_level) | ||||
							
								
								
									
										143
									
								
								django/contrib/messages/storage/cookie.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								django/contrib/messages/storage/cookie.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,143 @@ | ||||
| import hmac | ||||
|  | ||||
| from django.conf import settings | ||||
| from django.utils.hashcompat import sha_constructor | ||||
| from django.contrib.messages import constants | ||||
| from django.contrib.messages.storage.base import BaseStorage, Message | ||||
| from django.utils import simplejson as json | ||||
|  | ||||
|  | ||||
| class MessageEncoder(json.JSONEncoder): | ||||
|     """ | ||||
|     Compactly serializes instances of the ``Message`` class as JSON. | ||||
|     """ | ||||
|     message_key = '__json_message' | ||||
|  | ||||
|     def default(self, obj): | ||||
|         if isinstance(obj, Message): | ||||
|             message = [self.message_key, obj.level, obj.message] | ||||
|             if obj.extra_tags: | ||||
|                 message.append(obj.extra_tags) | ||||
|             return message | ||||
|         return super(MessageEncoder, self).default(obj) | ||||
|  | ||||
|  | ||||
| class MessageDecoder(json.JSONDecoder): | ||||
|     """ | ||||
|     Decodes JSON that includes serialized ``Message`` instances. | ||||
|     """ | ||||
|  | ||||
|     def process_messages(self, obj): | ||||
|         if isinstance(obj, list) and obj: | ||||
|             if obj[0] == MessageEncoder.message_key: | ||||
|                 return Message(*obj[1:]) | ||||
|             return [self.process_messages(item) for item in obj] | ||||
|         if isinstance(obj, dict): | ||||
|             return dict([(key, self.process_messages(value)) | ||||
|                          for key, value in obj.iteritems()]) | ||||
|         return obj | ||||
|  | ||||
|     def decode(self, s, **kwargs): | ||||
|         decoded = super(MessageDecoder, self).decode(s, **kwargs) | ||||
|         return self.process_messages(decoded) | ||||
|  | ||||
|  | ||||
| class CookieStorage(BaseStorage): | ||||
|     """ | ||||
|     Stores messages in a cookie. | ||||
|     """ | ||||
|     cookie_name = 'messages' | ||||
|     max_cookie_size = 4096 | ||||
|     not_finished = '__messagesnotfinished__' | ||||
|  | ||||
|     def _get(self, *args, **kwargs): | ||||
|         """ | ||||
|         Retrieves a list of messages from the messages cookie.  If the | ||||
|         not_finished sentinel value is found at the end of the message list, | ||||
|         remove it and return a result indicating that not all messages were | ||||
|         retrieved by this storage. | ||||
|         """ | ||||
|         data = self.request.COOKIES.get(self.cookie_name) | ||||
|         messages = self._decode(data) | ||||
|         all_retrieved = not (messages and messages[-1] == self.not_finished) | ||||
|         if messages and not all_retrieved: | ||||
|             # remove the sentinel value | ||||
|             messages.pop() | ||||
|         return messages, all_retrieved | ||||
|  | ||||
|     def _update_cookie(self, encoded_data, response): | ||||
|         """ | ||||
|         Either sets the cookie with the encoded data if there is any data to | ||||
|         store, or deletes the cookie. | ||||
|         """ | ||||
|         if encoded_data: | ||||
|             response.set_cookie(self.cookie_name, encoded_data) | ||||
|         else: | ||||
|             response.delete_cookie(self.cookie_name) | ||||
|  | ||||
|     def _store(self, messages, response, remove_oldest=True, *args, **kwargs): | ||||
|         """ | ||||
|         Stores the messages to a cookie, returning a list of any messages which | ||||
|         could not be stored. | ||||
|  | ||||
|         If the encoded data is larger than ``max_cookie_size``, removes | ||||
|         messages until the data fits (these are the messages which are | ||||
|         returned), and add the not_finished sentinel value to indicate as much. | ||||
|         """ | ||||
|         unstored_messages = [] | ||||
|         encoded_data = self._encode(messages) | ||||
|         if self.max_cookie_size: | ||||
|             while encoded_data and len(encoded_data) > self.max_cookie_size: | ||||
|                 if remove_oldest: | ||||
|                     unstored_messages.append(messages.pop(0)) | ||||
|                 else: | ||||
|                     unstored_messages.insert(0, messages.pop()) | ||||
|                 encoded_data = self._encode(messages + [self.not_finished], | ||||
|                                             encode_empty=unstored_messages) | ||||
|         self._update_cookie(encoded_data, response) | ||||
|         return unstored_messages | ||||
|  | ||||
|     def _hash(self, value): | ||||
|         """ | ||||
|         Creates an HMAC/SHA1 hash based on the value and the project setting's | ||||
|         SECRET_KEY, modified to make it unique for the present purpose. | ||||
|         """ | ||||
|         key = 'django.contrib.messages' + settings.SECRET_KEY | ||||
|         return hmac.new(key, value, sha_constructor).hexdigest() | ||||
|  | ||||
|     def _encode(self, messages, encode_empty=False): | ||||
|         """ | ||||
|         Returns an encoded version of the messages list which can be stored as | ||||
|         plain text. | ||||
|  | ||||
|         Since the data will be retrieved from the client-side, the encoded data | ||||
|         also contains a hash to ensure that the data was not tampered with. | ||||
|         """ | ||||
|         if messages or encode_empty: | ||||
|             encoder = MessageEncoder(separators=(',', ':')) | ||||
|             value = encoder.encode(messages) | ||||
|             return '%s$%s' % (self._hash(value), value) | ||||
|  | ||||
|     def _decode(self, data): | ||||
|         """ | ||||
|         Safely decodes a encoded text stream back into a list of messages. | ||||
|  | ||||
|         If the encoded text stream contained an invalid hash or was in an | ||||
|         invalid format, ``None`` is returned. | ||||
|         """ | ||||
|         if not data: | ||||
|             return None | ||||
|         bits = data.split('$', 1) | ||||
|         if len(bits) == 2: | ||||
|             hash, value = bits | ||||
|             if hash == self._hash(value): | ||||
|                 try: | ||||
|                     # If we get here (and the JSON decode works), everything is | ||||
|                     # good. In any other case, drop back and return None. | ||||
|                     return json.loads(value, cls=MessageDecoder) | ||||
|                 except ValueError: | ||||
|                     pass | ||||
|         # Mark the data as used (so it gets removed) since something was wrong | ||||
|         # with the data. | ||||
|         self.used = True | ||||
|         return None | ||||
							
								
								
									
										59
									
								
								django/contrib/messages/storage/fallback.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								django/contrib/messages/storage/fallback.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| from django.contrib.messages.storage.base import BaseStorage | ||||
| from django.contrib.messages.storage.cookie import CookieStorage | ||||
| from django.contrib.messages.storage.session import SessionStorage | ||||
| try: | ||||
|     set | ||||
| except NameError: | ||||
|     from sets import Set as set   # Python 2.3 | ||||
|  | ||||
|  | ||||
| class FallbackStorage(BaseStorage): | ||||
|     """ | ||||
|     Tries to store all messages in the first backend, storing any unstored | ||||
|     messages in each subsequent backend backend. | ||||
|     """ | ||||
|     storage_classes = (CookieStorage, SessionStorage) | ||||
|  | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         super(FallbackStorage, self).__init__(*args, **kwargs) | ||||
|         self.storages = [storage_class(*args, **kwargs) | ||||
|                          for storage_class in self.storage_classes] | ||||
|         self._used_storages = set() | ||||
|  | ||||
|     def _get(self, *args, **kwargs): | ||||
|         """ | ||||
|         Gets a single list of messages from all storage backends. | ||||
|         """ | ||||
|         all_messages = [] | ||||
|         for storage in self.storages: | ||||
|             messages, all_retrieved = storage._get() | ||||
|             # If the backend hasn't been used, no more retrieval is necessary. | ||||
|             if messages is None: | ||||
|                 break | ||||
|             if messages: | ||||
|                 self._used_storages.add(storage) | ||||
|             all_messages.extend(messages) | ||||
|             # If this storage class contained all the messages, no further | ||||
|             # retrieval is necessary | ||||
|             if all_retrieved: | ||||
|                 break | ||||
|         return all_messages, all_retrieved | ||||
|  | ||||
|     def _store(self, messages, response, *args, **kwargs): | ||||
|         """ | ||||
|         Stores the messages, returning any unstored messages after trying all | ||||
|         backends. | ||||
|  | ||||
|         For each storage backend, any messages not stored are passed on to the | ||||
|         next backend. | ||||
|         """ | ||||
|         for storage in self.storages: | ||||
|             if messages: | ||||
|                 messages = storage._store(messages, response, | ||||
|                                           remove_oldest=False) | ||||
|             # Even if there are no more messages, continue iterating to ensure | ||||
|             # storages which contained messages are flushed. | ||||
|             elif storage in self._used_storages: | ||||
|                 storage._store([], response) | ||||
|                 self._used_storages.remove(storage) | ||||
|         return messages | ||||
							
								
								
									
										33
									
								
								django/contrib/messages/storage/session.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								django/contrib/messages/storage/session.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| from django.contrib.messages.storage.base import BaseStorage | ||||
|  | ||||
|  | ||||
| class SessionStorage(BaseStorage): | ||||
|     """ | ||||
|     Stores messages in the session (that is, django.contrib.sessions). | ||||
|     """ | ||||
|     session_key = '_messages' | ||||
|  | ||||
|     def __init__(self, request, *args, **kwargs): | ||||
|         assert hasattr(request, 'session'), "The session-based temporary "\ | ||||
|             "message storage requires session middleware to be installed, "\ | ||||
|             "and come before the message middleware in the "\ | ||||
|             "MIDDLEWARE_CLASSES list." | ||||
|         super(SessionStorage, self).__init__(request, *args, **kwargs) | ||||
|  | ||||
|     def _get(self, *args, **kwargs): | ||||
|         """ | ||||
|         Retrieves a list of messages from the request's session.  This storage | ||||
|         always stores everything it is given, so return True for the | ||||
|         all_retrieved flag. | ||||
|         """ | ||||
|         return self.request.session.get(self.session_key), True | ||||
|  | ||||
|     def _store(self, messages, response, *args, **kwargs): | ||||
|         """ | ||||
|         Stores a list of messages to the request's session. | ||||
|         """ | ||||
|         if messages: | ||||
|             self.request.session[self.session_key] = messages | ||||
|         else: | ||||
|             self.request.session.pop(self.session_key, None) | ||||
|         return [] | ||||
							
								
								
									
										64
									
								
								django/contrib/messages/storage/user_messages.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								django/contrib/messages/storage/user_messages.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| """ | ||||
| Storages used to assist in the deprecation of contrib.auth User messages. | ||||
|  | ||||
| """ | ||||
| from django.contrib.messages import constants | ||||
| from django.contrib.messages.storage.base import BaseStorage, Message | ||||
| from django.contrib.auth.models import User | ||||
| from django.contrib.messages.storage.fallback import FallbackStorage | ||||
|  | ||||
|  | ||||
| class UserMessagesStorage(BaseStorage): | ||||
|     """ | ||||
|     Retrieves messages from the User, using the legacy user.message_set API. | ||||
|  | ||||
|     This storage is "read-only" insofar as it can only retrieve and delete | ||||
|     messages, not store them. | ||||
|     """ | ||||
|     session_key = '_messages' | ||||
|  | ||||
|     def _get_messages_queryset(self): | ||||
|         """ | ||||
|         Returns the QuerySet containing all user messages (or ``None`` if | ||||
|         request.user is not a contrib.auth User). | ||||
|         """ | ||||
|         user = getattr(self.request, 'user', None) | ||||
|         if isinstance(user, User): | ||||
|             return user._message_set.all() | ||||
|  | ||||
|     def add(self, *args, **kwargs): | ||||
|         raise NotImplementedError('This message storage is read-only.') | ||||
|  | ||||
|     def _get(self, *args, **kwargs): | ||||
|         """ | ||||
|         Retrieves a list of messages assigned to the User.  This backend never | ||||
|         stores anything, so all_retrieved is assumed to be False. | ||||
|         """ | ||||
|         queryset = self._get_messages_queryset() | ||||
|         if queryset is None: | ||||
|             # This is a read-only and optional storage, so to ensure other | ||||
|             # storages will also be read if used with FallbackStorage an empty | ||||
|             # list is returned rather than None. | ||||
|             return [], False | ||||
|         messages = [] | ||||
|         for user_message in queryset: | ||||
|             messages.append(Message(constants.INFO, user_message.message)) | ||||
|         return messages, False | ||||
|  | ||||
|     def _store(self, messages, *args, **kwargs): | ||||
|         """ | ||||
|         Removes any messages assigned to the User and returns the list of | ||||
|         messages (since no messages are stored in this read-only storage). | ||||
|         """ | ||||
|         queryset = self._get_messages_queryset() | ||||
|         if queryset is not None: | ||||
|             queryset.delete() | ||||
|         return messages | ||||
|  | ||||
|  | ||||
| class LegacyFallbackStorage(FallbackStorage): | ||||
|     """ | ||||
|     Works like ``FallbackStorage`` but also handles retrieving (and clearing) | ||||
|     contrib.auth User messages. | ||||
|     """ | ||||
|     storage_classes = (UserMessagesStorage,) + FallbackStorage.storage_classes | ||||
							
								
								
									
										6
									
								
								django/contrib/messages/tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								django/contrib/messages/tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| from django.contrib.messages.tests.cookie import CookieTest | ||||
| from django.contrib.messages.tests.fallback import FallbackTest | ||||
| from django.contrib.messages.tests.middleware import MiddlewareTest | ||||
| from django.contrib.messages.tests.session import SessionTest | ||||
| from django.contrib.messages.tests.user_messages import \ | ||||
|                                            UserMessagesTest, LegacyFallbackTest | ||||
							
								
								
									
										375
									
								
								django/contrib/messages/tests/base.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										375
									
								
								django/contrib/messages/tests/base.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,375 @@ | ||||
| from django import http | ||||
| from django.test import TestCase | ||||
| from django.conf import settings | ||||
| from django.utils.translation import ugettext_lazy | ||||
| from django.contrib.messages import constants, utils | ||||
| from django.contrib.messages.storage import default_storage, base | ||||
| from django.contrib.messages.storage.base import Message | ||||
| from django.core.urlresolvers import reverse | ||||
| from django.contrib.auth.models import User | ||||
| from django.contrib.messages.api import MessageFailure | ||||
|  | ||||
|  | ||||
| def add_level_messages(storage): | ||||
|     """ | ||||
|     Adds 6 messages from different levels (including a custom one) to a storage | ||||
|     instance. | ||||
|     """ | ||||
|     storage.add(constants.INFO, 'A generic info message') | ||||
|     storage.add(29, 'Some custom level') | ||||
|     storage.add(constants.DEBUG, 'A debugging message', extra_tags='extra-tag') | ||||
|     storage.add(constants.WARNING, 'A warning') | ||||
|     storage.add(constants.ERROR, 'An error') | ||||
|     storage.add(constants.SUCCESS, 'This was a triumph.') | ||||
|  | ||||
|  | ||||
| class BaseTest(TestCase): | ||||
|     storage_class = default_storage | ||||
|     restore_settings = ['MESSAGE_LEVEL', 'MESSAGE_TAGS'] | ||||
|     urls = 'django.contrib.messages.tests.urls' | ||||
|     levels = { | ||||
|         'debug': constants.DEBUG, | ||||
|         'info': constants.INFO, | ||||
|         'success': constants.SUCCESS, | ||||
|         'warning': constants.WARNING, | ||||
|         'error': constants.ERROR, | ||||
|     } | ||||
|  | ||||
|     def setUp(self): | ||||
|         self._remembered_settings = {} | ||||
|         for setting in self.restore_settings: | ||||
|             if hasattr(settings, setting): | ||||
|                 self._remembered_settings[setting] = getattr(settings, setting) | ||||
|                 delattr(settings._wrapped, setting) | ||||
|         # backup these manually because we do not want them deleted | ||||
|         self._middleware_classes = settings.MIDDLEWARE_CLASSES | ||||
|         self._template_context_processors = \ | ||||
|            settings.TEMPLATE_CONTEXT_PROCESSORS | ||||
|         self._installed_apps = settings.INSTALLED_APPS | ||||
|  | ||||
|     def tearDown(self): | ||||
|         for setting in self.restore_settings: | ||||
|             self.restore_setting(setting) | ||||
|         # restore these manually (see above) | ||||
|         settings.MIDDLEWARE_CLASSES = self._middleware_classes | ||||
|         settings.TEMPLATE_CONTEXT_PROCESSORS = \ | ||||
|            self._template_context_processors | ||||
|         settings.INSTALLED_APPS = self._installed_apps | ||||
|  | ||||
|     def restore_setting(self, setting): | ||||
|         if setting in self._remembered_settings: | ||||
|             value = self._remembered_settings.pop(setting) | ||||
|             setattr(settings, setting, value) | ||||
|         elif hasattr(settings, setting): | ||||
|             delattr(settings._wrapped, setting) | ||||
|  | ||||
|     def get_request(self): | ||||
|         return http.HttpRequest() | ||||
|  | ||||
|     def get_response(self): | ||||
|         return http.HttpResponse() | ||||
|  | ||||
|     def get_storage(self, data=None): | ||||
|         """ | ||||
|         Returns the storage backend, setting its loaded data to the ``data`` | ||||
|         argument. | ||||
|  | ||||
|         This method avoids the storage ``_get`` method from getting called so | ||||
|         that other parts of the storage backend can be tested independent of | ||||
|         the message retrieval logic. | ||||
|         """ | ||||
|         storage = self.storage_class(self.get_request()) | ||||
|         storage._loaded_data = data or [] | ||||
|         return storage | ||||
|  | ||||
|     def test_add(self): | ||||
|         storage = self.get_storage() | ||||
|         self.assertFalse(storage.added_new) | ||||
|         storage.add(constants.INFO, 'Test message 1') | ||||
|         self.assert_(storage.added_new) | ||||
|         storage.add(constants.INFO, 'Test message 2', extra_tags='tag') | ||||
|         self.assertEqual(len(storage), 2) | ||||
|  | ||||
|     def test_add_lazy_translation(self): | ||||
|         storage = self.get_storage() | ||||
|         response = self.get_response() | ||||
|  | ||||
|         storage.add(constants.INFO, ugettext_lazy('lazy message')) | ||||
|         storage.update(response) | ||||
|  | ||||
|         storing = self.stored_messages_count(storage, response) | ||||
|         self.assertEqual(storing, 1) | ||||
|  | ||||
|     def test_no_update(self): | ||||
|         storage = self.get_storage() | ||||
|         response = self.get_response() | ||||
|         storage.update(response) | ||||
|         storing = self.stored_messages_count(storage, response) | ||||
|         self.assertEqual(storing, 0) | ||||
|  | ||||
|     def test_add_update(self): | ||||
|         storage = self.get_storage() | ||||
|         response = self.get_response() | ||||
|  | ||||
|         storage.add(constants.INFO, 'Test message 1') | ||||
|         storage.add(constants.INFO, 'Test message 1', extra_tags='tag') | ||||
|         storage.update(response) | ||||
|  | ||||
|         storing = self.stored_messages_count(storage, response) | ||||
|         self.assertEqual(storing, 2) | ||||
|  | ||||
|     def test_existing_add_read_update(self): | ||||
|         storage = self.get_existing_storage() | ||||
|         response = self.get_response() | ||||
|  | ||||
|         storage.add(constants.INFO, 'Test message 3') | ||||
|         list(storage)   # Simulates a read | ||||
|         storage.update(response) | ||||
|  | ||||
|         storing = self.stored_messages_count(storage, response) | ||||
|         self.assertEqual(storing, 0) | ||||
|  | ||||
|     def test_existing_read_add_update(self): | ||||
|         storage = self.get_existing_storage() | ||||
|         response = self.get_response() | ||||
|  | ||||
|         list(storage)   # Simulates a read | ||||
|         storage.add(constants.INFO, 'Test message 3') | ||||
|         storage.update(response) | ||||
|  | ||||
|         storing = self.stored_messages_count(storage, response) | ||||
|         self.assertEqual(storing, 1) | ||||
|  | ||||
|     def test_full_request_response_cycle(self): | ||||
|         """ | ||||
|         With the message middleware enabled, tests that messages are properly | ||||
|         stored and then retrieved across the full request/redirect/response | ||||
|         cycle. | ||||
|         """ | ||||
|         settings.MESSAGE_LEVEL = constants.DEBUG | ||||
|         data = { | ||||
|             'messages': ['Test message %d' % x for x in xrange(10)], | ||||
|         } | ||||
|         show_url = reverse('django.contrib.messages.tests.urls.show') | ||||
|         for level in ('debug', 'info', 'success', 'warning', 'error'): | ||||
|             add_url = reverse('django.contrib.messages.tests.urls.add', | ||||
|                               args=(level,)) | ||||
|             response = self.client.post(add_url, data, follow=True) | ||||
|             self.assertRedirects(response, show_url) | ||||
|             self.assertTrue('messages' in response.context) | ||||
|             messages = [Message(self.levels[level], msg) for msg in | ||||
|                                                          data['messages']] | ||||
|             self.assertEqual(list(response.context['messages']), messages) | ||||
|             for msg in data['messages']: | ||||
|                 self.assertContains(response, msg) | ||||
|  | ||||
|     def test_multiple_posts(self): | ||||
|         """ | ||||
|         Tests that messages persist properly when multiple POSTs are made | ||||
|         before a GET. | ||||
|         """ | ||||
|         settings.MESSAGE_LEVEL = constants.DEBUG | ||||
|         data = { | ||||
|             'messages': ['Test message %d' % x for x in xrange(10)], | ||||
|         } | ||||
|         show_url = reverse('django.contrib.messages.tests.urls.show') | ||||
|         messages = [] | ||||
|         for level in ('debug', 'info', 'success', 'warning', 'error'): | ||||
|             messages.extend([Message(self.levels[level], msg) for msg in | ||||
|                                                              data['messages']]) | ||||
|             add_url = reverse('django.contrib.messages.tests.urls.add', | ||||
|                               args=(level,)) | ||||
|             self.client.post(add_url, data) | ||||
|         response = self.client.get(show_url) | ||||
|         self.assertTrue('messages' in response.context) | ||||
|         self.assertEqual(list(response.context['messages']), messages) | ||||
|         for msg in data['messages']: | ||||
|             self.assertContains(response, msg) | ||||
|  | ||||
|     def test_middleware_disabled_auth_user(self): | ||||
|         """ | ||||
|         Tests that the messages API successfully falls back to using | ||||
|         user.message_set to store messages directly when the middleware is | ||||
|         disabled. | ||||
|         """ | ||||
|         settings.MESSAGE_LEVEL = constants.DEBUG | ||||
|         user = User.objects.create_user('test', 'test@example.com', 'test') | ||||
|         self.client.login(username='test', password='test') | ||||
|         settings.INSTALLED_APPS = list(settings.INSTALLED_APPS) | ||||
|         settings.INSTALLED_APPS.remove( | ||||
|             'django.contrib.messages', | ||||
|         ) | ||||
|         settings.MIDDLEWARE_CLASSES = list(settings.MIDDLEWARE_CLASSES) | ||||
|         settings.MIDDLEWARE_CLASSES.remove( | ||||
|             'django.contrib.messages.middleware.MessageMiddleware', | ||||
|         ) | ||||
|         settings.TEMPLATE_CONTEXT_PROCESSORS = \ | ||||
|           list(settings.TEMPLATE_CONTEXT_PROCESSORS) | ||||
|         settings.TEMPLATE_CONTEXT_PROCESSORS.remove( | ||||
|             'django.contrib.messages.context_processors.messages', | ||||
|         ) | ||||
|         data = { | ||||
|             'messages': ['Test message %d' % x for x in xrange(10)], | ||||
|         } | ||||
|         show_url = reverse('django.contrib.messages.tests.urls.show') | ||||
|         for level in ('debug', 'info', 'success', 'warning', 'error'): | ||||
|             add_url = reverse('django.contrib.messages.tests.urls.add', | ||||
|                               args=(level,)) | ||||
|             response = self.client.post(add_url, data, follow=True) | ||||
|             self.assertRedirects(response, show_url) | ||||
|             self.assertTrue('messages' in response.context) | ||||
|             self.assertEqual(list(response.context['messages']), | ||||
|                              data['messages']) | ||||
|             for msg in data['messages']: | ||||
|                 self.assertContains(response, msg) | ||||
|  | ||||
|     def test_middleware_disabled_anon_user(self): | ||||
|         """ | ||||
|         Tests that, when the middleware is disabled and a user is not logged | ||||
|         in, an exception is raised when one attempts to store a message. | ||||
|         """ | ||||
|         settings.MESSAGE_LEVEL = constants.DEBUG | ||||
|         settings.INSTALLED_APPS = list(settings.INSTALLED_APPS) | ||||
|         settings.INSTALLED_APPS.remove( | ||||
|             'django.contrib.messages', | ||||
|         ) | ||||
|         settings.MIDDLEWARE_CLASSES = list(settings.MIDDLEWARE_CLASSES) | ||||
|         settings.MIDDLEWARE_CLASSES.remove( | ||||
|             'django.contrib.messages.middleware.MessageMiddleware', | ||||
|         ) | ||||
|         settings.TEMPLATE_CONTEXT_PROCESSORS = \ | ||||
|           list(settings.TEMPLATE_CONTEXT_PROCESSORS) | ||||
|         settings.TEMPLATE_CONTEXT_PROCESSORS.remove( | ||||
|             'django.contrib.messages.context_processors.messages', | ||||
|         ) | ||||
|         data = { | ||||
|             'messages': ['Test message %d' % x for x in xrange(10)], | ||||
|         } | ||||
|         show_url = reverse('django.contrib.messages.tests.urls.show') | ||||
|         for level in ('debug', 'info', 'success', 'warning', 'error'): | ||||
|             add_url = reverse('django.contrib.messages.tests.urls.add', | ||||
|                               args=(level,)) | ||||
|             self.assertRaises(MessageFailure, self.client.post, add_url, | ||||
|                               data, follow=True) | ||||
|  | ||||
|     def test_middleware_disabled_anon_user_fail_silently(self): | ||||
|         """ | ||||
|         Tests that, when the middleware is disabled and a user is not logged | ||||
|         in, an exception is not raised if 'fail_silently' = True | ||||
|         """ | ||||
|         settings.MESSAGE_LEVEL = constants.DEBUG | ||||
|         settings.INSTALLED_APPS = list(settings.INSTALLED_APPS) | ||||
|         settings.INSTALLED_APPS.remove( | ||||
|             'django.contrib.messages', | ||||
|         ) | ||||
|         settings.MIDDLEWARE_CLASSES = list(settings.MIDDLEWARE_CLASSES) | ||||
|         settings.MIDDLEWARE_CLASSES.remove( | ||||
|             'django.contrib.messages.middleware.MessageMiddleware', | ||||
|         ) | ||||
|         settings.TEMPLATE_CONTEXT_PROCESSORS = \ | ||||
|           list(settings.TEMPLATE_CONTEXT_PROCESSORS) | ||||
|         settings.TEMPLATE_CONTEXT_PROCESSORS.remove( | ||||
|             'django.contrib.messages.context_processors.messages', | ||||
|         ) | ||||
|         data = { | ||||
|             'messages': ['Test message %d' % x for x in xrange(10)], | ||||
|             'fail_silently': True, | ||||
|         } | ||||
|         show_url = reverse('django.contrib.messages.tests.urls.show') | ||||
|         for level in ('debug', 'info', 'success', 'warning', 'error'): | ||||
|             add_url = reverse('django.contrib.messages.tests.urls.add', | ||||
|                               args=(level,)) | ||||
|             response = self.client.post(add_url, data, follow=True) | ||||
|             self.assertRedirects(response, show_url) | ||||
|             self.assertTrue('messages' in response.context) | ||||
|             self.assertEqual(list(response.context['messages']), []) | ||||
|  | ||||
|     def stored_messages_count(self, storage, response): | ||||
|         """ | ||||
|         Returns the number of messages being stored after a | ||||
|         ``storage.update()`` call. | ||||
|         """ | ||||
|         raise NotImplementedError('This method must be set by a subclass.') | ||||
|  | ||||
|     def test_get(self): | ||||
|         raise NotImplementedError('This method must be set by a subclass.') | ||||
|  | ||||
|     def get_existing_storage(self): | ||||
|         return self.get_storage([Message(constants.INFO, 'Test message 1'), | ||||
|                                  Message(constants.INFO, 'Test message 2', | ||||
|                                               extra_tags='tag')]) | ||||
|  | ||||
|     def test_existing_read(self): | ||||
|         """ | ||||
|         Tests that reading the existing storage doesn't cause the data to be | ||||
|         lost. | ||||
|         """ | ||||
|         storage = self.get_existing_storage() | ||||
|         self.assertFalse(storage.used) | ||||
|         # After iterating the storage engine directly, the used flag is set. | ||||
|         data = list(storage) | ||||
|         self.assert_(storage.used) | ||||
|         # The data does not disappear because it has been iterated. | ||||
|         self.assertEqual(data, list(storage)) | ||||
|  | ||||
|     def test_existing_add(self): | ||||
|         storage = self.get_existing_storage() | ||||
|         self.assertFalse(storage.added_new) | ||||
|         storage.add(constants.INFO, 'Test message 3') | ||||
|         self.assert_(storage.added_new) | ||||
|  | ||||
|     def test_default_level(self): | ||||
|         storage = self.get_storage() | ||||
|         add_level_messages(storage) | ||||
|         self.assertEqual(len(storage), 5) | ||||
|  | ||||
|     def test_low_level(self): | ||||
|         storage = self.get_storage() | ||||
|         storage.level = 5 | ||||
|         add_level_messages(storage) | ||||
|         self.assertEqual(len(storage), 6) | ||||
|  | ||||
|     def test_high_level(self): | ||||
|         storage = self.get_storage() | ||||
|         storage.level = 30 | ||||
|         add_level_messages(storage) | ||||
|         self.assertEqual(len(storage), 2) | ||||
|  | ||||
|     def test_settings_level(self): | ||||
|         settings.MESSAGE_LEVEL = 29 | ||||
|         storage = self.get_storage() | ||||
|         add_level_messages(storage) | ||||
|         self.assertEqual(len(storage), 3) | ||||
|  | ||||
|     def test_tags(self): | ||||
|         storage = self.get_storage() | ||||
|         storage.level = 0 | ||||
|         add_level_messages(storage) | ||||
|         tags = [msg.tags for msg in storage] | ||||
|         self.assertEqual(tags, | ||||
|                          ['info', '', 'extra-tag debug', 'warning', 'error', | ||||
|                           'success']) | ||||
|  | ||||
|     def test_custom_tags(self): | ||||
|         settings.MESSAGE_TAGS = { | ||||
|             constants.INFO: 'info', | ||||
|             constants.DEBUG: '', | ||||
|             constants.WARNING: '', | ||||
|             constants.ERROR: 'bad', | ||||
|             29: 'custom', | ||||
|         } | ||||
|         # LEVEL_TAGS is a constant defined in the | ||||
|         # django.contrib.messages.storage.base module, so after changing | ||||
|         # settings.MESSAGE_TAGS, we need to update that constant too. | ||||
|         base.LEVEL_TAGS = utils.get_level_tags() | ||||
|         try: | ||||
|             storage = self.get_storage() | ||||
|             storage.level = 0 | ||||
|             add_level_messages(storage) | ||||
|             tags = [msg.tags for msg in storage] | ||||
|             self.assertEqual(tags, | ||||
|                          ['info', 'custom', 'extra-tag', '', 'bad', 'success']) | ||||
|         finally: | ||||
|             # Ensure the level tags constant is put back like we found it. | ||||
|             self.restore_setting('MESSAGE_TAGS') | ||||
|             base.LEVEL_TAGS = utils.get_level_tags() | ||||
							
								
								
									
										100
									
								
								django/contrib/messages/tests/cookie.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								django/contrib/messages/tests/cookie.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,100 @@ | ||||
| from django.contrib.messages import constants | ||||
| from django.contrib.messages.tests.base import BaseTest | ||||
| from django.contrib.messages.storage.cookie import CookieStorage, \ | ||||
|                                             MessageEncoder, MessageDecoder | ||||
| from django.contrib.messages.storage.base import Message | ||||
| from django.utils import simplejson as json | ||||
|  | ||||
|  | ||||
| def set_cookie_data(storage, messages, invalid=False, encode_empty=False): | ||||
|     """ | ||||
|     Sets ``request.COOKIES`` with the encoded data and removes the storage | ||||
|     backend's loaded data cache. | ||||
|     """ | ||||
|     encoded_data = storage._encode(messages, encode_empty=encode_empty) | ||||
|     if invalid: | ||||
|         # Truncate the first character so that the hash is invalid. | ||||
|         encoded_data = encoded_data[1:] | ||||
|     storage.request.COOKIES = {CookieStorage.cookie_name: encoded_data} | ||||
|     if hasattr(storage, '_loaded_data'): | ||||
|         del storage._loaded_data | ||||
|  | ||||
|  | ||||
| def stored_cookie_messages_count(storage, response): | ||||
|     """ | ||||
|     Returns an integer containing the number of messages stored. | ||||
|     """ | ||||
|     # Get a list of cookies, excluding ones with a max-age of 0 (because | ||||
|     # they have been marked for deletion). | ||||
|     cookie = response.cookies.get(storage.cookie_name) | ||||
|     if not cookie or cookie['max-age'] == 0: | ||||
|         return 0 | ||||
|     data = storage._decode(cookie.value) | ||||
|     if not data: | ||||
|         return 0 | ||||
|     if data[-1] == CookieStorage.not_finished: | ||||
|         data.pop() | ||||
|     return len(data) | ||||
|  | ||||
|  | ||||
| class CookieTest(BaseTest): | ||||
|     storage_class = CookieStorage | ||||
|  | ||||
|     def stored_messages_count(self, storage, response): | ||||
|         return stored_cookie_messages_count(storage, response) | ||||
|  | ||||
|     def test_get(self): | ||||
|         storage = self.storage_class(self.get_request()) | ||||
|         # Set initial data. | ||||
|         example_messages = ['test', 'me'] | ||||
|         set_cookie_data(storage, example_messages) | ||||
|         # Test that the message actually contains what we expect. | ||||
|         self.assertEqual(list(storage), example_messages) | ||||
|  | ||||
|     def test_get_bad_cookie(self): | ||||
|         request = self.get_request() | ||||
|         storage = self.storage_class(request) | ||||
|         # Set initial (invalid) data. | ||||
|         example_messages = ['test', 'me'] | ||||
|         set_cookie_data(storage, example_messages, invalid=True) | ||||
|         # Test that the message actually contains what we expect. | ||||
|         self.assertEqual(list(storage), []) | ||||
|  | ||||
|     def test_max_cookie_length(self): | ||||
|         """ | ||||
|         Tests that, if the data exceeds what is allowed in a cookie, older | ||||
|         messages are removed before saving (and returned by the ``update`` | ||||
|         method). | ||||
|         """ | ||||
|         storage = self.get_storage() | ||||
|         response = self.get_response() | ||||
|  | ||||
|         for i in range(5): | ||||
|             storage.add(constants.INFO, str(i) * 900) | ||||
|         unstored_messages = storage.update(response) | ||||
|  | ||||
|         cookie_storing = self.stored_messages_count(storage, response) | ||||
|         self.assertEqual(cookie_storing, 4) | ||||
|  | ||||
|         self.assertEqual(len(unstored_messages), 1) | ||||
|         self.assert_(unstored_messages[0].message == '0' * 900) | ||||
|  | ||||
|     def test_json_encoder_decoder(self): | ||||
|         """ | ||||
|         Tests that an complex nested data structure containing Message | ||||
|         instances is properly encoded/decoded by the custom JSON | ||||
|         encoder/decoder classes. | ||||
|         """ | ||||
|         messages = [ | ||||
|             { | ||||
|                 'message': Message(constants.INFO, 'Test message'), | ||||
|                 'message_list': [Message(constants.INFO, 'message %s') \ | ||||
|                                  for x in xrange(5)] + [{'another-message': \ | ||||
|                                  Message(constants.ERROR, 'error')}], | ||||
|             }, | ||||
|             Message(constants.INFO, 'message %s'), | ||||
|         ] | ||||
|         encoder = MessageEncoder(separators=(',', ':')) | ||||
|         value = encoder.encode(messages) | ||||
|         decoded_messages = json.loads(value, cls=MessageDecoder) | ||||
|         self.assertEqual(messages, decoded_messages) | ||||
							
								
								
									
										173
									
								
								django/contrib/messages/tests/fallback.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								django/contrib/messages/tests/fallback.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,173 @@ | ||||
| from django.contrib.messages import constants | ||||
| from django.contrib.messages.storage.fallback import FallbackStorage, \ | ||||
|     CookieStorage | ||||
| from django.contrib.messages.tests.base import BaseTest | ||||
| from django.contrib.messages.tests.cookie import set_cookie_data, \ | ||||
|     stored_cookie_messages_count | ||||
| from django.contrib.messages.tests.session import set_session_data, \ | ||||
|     stored_session_messages_count | ||||
|  | ||||
|  | ||||
| class FallbackTest(BaseTest): | ||||
|     storage_class = FallbackStorage | ||||
|  | ||||
|     def get_request(self): | ||||
|         self.session = {} | ||||
|         request = super(FallbackTest, self).get_request() | ||||
|         request.session = self.session | ||||
|         return request | ||||
|  | ||||
|     def get_cookie_storage(self, storage): | ||||
|         return storage.storages[-2] | ||||
|  | ||||
|     def get_session_storage(self, storage): | ||||
|         return storage.storages[-1] | ||||
|  | ||||
|     def stored_cookie_messages_count(self, storage, response): | ||||
|         return stored_cookie_messages_count(self.get_cookie_storage(storage), | ||||
|                                             response) | ||||
|  | ||||
|     def stored_session_messages_count(self, storage, response): | ||||
|         return stored_session_messages_count(self.get_session_storage(storage)) | ||||
|  | ||||
|     def stored_messages_count(self, storage, response): | ||||
|         """ | ||||
|         Return the storage totals from both cookie and session backends. | ||||
|         """ | ||||
|         total = (self.stored_cookie_messages_count(storage, response) + | ||||
|                  self.stored_session_messages_count(storage, response)) | ||||
|         return total | ||||
|  | ||||
|     def test_get(self): | ||||
|         request = self.get_request() | ||||
|         storage = self.storage_class(request) | ||||
|         cookie_storage = self.get_cookie_storage(storage) | ||||
|  | ||||
|         # Set initial cookie data. | ||||
|         example_messages = [str(i) for i in range(5)] | ||||
|         set_cookie_data(cookie_storage, example_messages) | ||||
|  | ||||
|         # Overwrite the _get method of the fallback storage to prove it is not | ||||
|         # used (it would cause a TypeError: 'NoneType' object is not callable). | ||||
|         self.get_session_storage(storage)._get = None | ||||
|  | ||||
|         # Test that the message actually contains what we expect. | ||||
|         self.assertEqual(list(storage), example_messages) | ||||
|  | ||||
|     def test_get_empty(self): | ||||
|         request = self.get_request() | ||||
|         storage = self.storage_class(request) | ||||
|  | ||||
|         # Overwrite the _get method of the fallback storage to prove it is not | ||||
|         # used (it would cause a TypeError: 'NoneType' object is not callable). | ||||
|         self.get_session_storage(storage)._get = None | ||||
|  | ||||
|         # Test that the message actually contains what we expect. | ||||
|         self.assertEqual(list(storage), []) | ||||
|  | ||||
|     def test_get_fallback(self): | ||||
|         request = self.get_request() | ||||
|         storage = self.storage_class(request) | ||||
|         cookie_storage = self.get_cookie_storage(storage) | ||||
|         session_storage = self.get_session_storage(storage) | ||||
|  | ||||
|         # Set initial cookie and session data. | ||||
|         example_messages = [str(i) for i in range(5)] | ||||
|         set_cookie_data(cookie_storage, example_messages[:4] + | ||||
|                         [CookieStorage.not_finished]) | ||||
|         set_session_data(session_storage, example_messages[4:]) | ||||
|  | ||||
|         # Test that the message actually contains what we expect. | ||||
|         self.assertEqual(list(storage), example_messages) | ||||
|  | ||||
|     def test_get_fallback_only(self): | ||||
|         request = self.get_request() | ||||
|         storage = self.storage_class(request) | ||||
|         cookie_storage = self.get_cookie_storage(storage) | ||||
|         session_storage = self.get_session_storage(storage) | ||||
|  | ||||
|         # Set initial cookie and session data. | ||||
|         example_messages = [str(i) for i in range(5)] | ||||
|         set_cookie_data(cookie_storage, [CookieStorage.not_finished], | ||||
|                         encode_empty=True) | ||||
|         set_session_data(session_storage, example_messages) | ||||
|  | ||||
|         # Test that the message actually contains what we expect. | ||||
|         self.assertEqual(list(storage), example_messages) | ||||
|  | ||||
|     def test_flush_used_backends(self): | ||||
|         request = self.get_request() | ||||
|         storage = self.storage_class(request) | ||||
|         cookie_storage = self.get_cookie_storage(storage) | ||||
|         session_storage = self.get_session_storage(storage) | ||||
|  | ||||
|         # Set initial cookie and session data. | ||||
|         set_cookie_data(cookie_storage, ['cookie', CookieStorage.not_finished]) | ||||
|         set_session_data(session_storage, ['session']) | ||||
|  | ||||
|         # When updating, previously used but no longer needed backends are | ||||
|         # flushed. | ||||
|         response = self.get_response() | ||||
|         list(storage) | ||||
|         storage.update(response) | ||||
|         session_storing = self.stored_session_messages_count(storage, response) | ||||
|         self.assertEqual(session_storing, 0) | ||||
|  | ||||
|     def test_no_fallback(self): | ||||
|         """ | ||||
|         Confirms that: | ||||
|  | ||||
|         (1) A short number of messages whose data size doesn't exceed what is | ||||
|         allowed in a cookie will all be stored in the CookieBackend. | ||||
|  | ||||
|         (2) If the CookieBackend can store all messages, the SessionBackend | ||||
|         won't be written to at all. | ||||
|         """ | ||||
|         storage = self.get_storage() | ||||
|         response = self.get_response() | ||||
|  | ||||
|         # Overwrite the _store method of the fallback storage to prove it isn't | ||||
|         # used (it would cause a TypeError: 'NoneType' object is not callable). | ||||
|         self.get_session_storage(storage)._store = None | ||||
|  | ||||
|         for i in range(5): | ||||
|             storage.add(constants.INFO, str(i) * 100) | ||||
|         storage.update(response) | ||||
|  | ||||
|         cookie_storing = self.stored_cookie_messages_count(storage, response) | ||||
|         self.assertEqual(cookie_storing, 5) | ||||
|         session_storing = self.stored_session_messages_count(storage, response) | ||||
|         self.assertEqual(session_storing, 0) | ||||
|  | ||||
|     def test_session_fallback(self): | ||||
|         """ | ||||
|         Confirms that, if the data exceeds what is allowed in a cookie, | ||||
|         messages which did not fit are stored in the SessionBackend. | ||||
|         """ | ||||
|         storage = self.get_storage() | ||||
|         response = self.get_response() | ||||
|  | ||||
|         for i in range(5): | ||||
|             storage.add(constants.INFO, str(i) * 900) | ||||
|         storage.update(response) | ||||
|  | ||||
|         cookie_storing = self.stored_cookie_messages_count(storage, response) | ||||
|         self.assertEqual(cookie_storing, 4) | ||||
|         session_storing = self.stored_session_messages_count(storage, response) | ||||
|         self.assertEqual(session_storing, 1) | ||||
|  | ||||
|     def test_session_fallback_only(self): | ||||
|         """ | ||||
|         Confirms that large messages, none of which fit in a cookie, are stored | ||||
|         in the SessionBackend (and nothing is stored in the CookieBackend). | ||||
|         """ | ||||
|         storage = self.get_storage() | ||||
|         response = self.get_response() | ||||
|  | ||||
|         storage.add(constants.INFO, 'x' * 5000) | ||||
|         storage.update(response) | ||||
|  | ||||
|         cookie_storing = self.stored_cookie_messages_count(storage, response) | ||||
|         self.assertEqual(cookie_storing, 0) | ||||
|         session_storing = self.stored_session_messages_count(storage, response) | ||||
|         self.assertEqual(session_storing, 1) | ||||
							
								
								
									
										18
									
								
								django/contrib/messages/tests/middleware.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								django/contrib/messages/tests/middleware.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| import unittest | ||||
| from django import http | ||||
| from django.contrib.messages.middleware import MessageMiddleware | ||||
|  | ||||
|  | ||||
| class MiddlewareTest(unittest.TestCase): | ||||
|  | ||||
|     def setUp(self): | ||||
|         self.middleware = MessageMiddleware() | ||||
|  | ||||
|     def test_response_without_messages(self): | ||||
|         """ | ||||
|         Makes sure that the response middleware is tolerant of messages not | ||||
|         existing on request. | ||||
|         """ | ||||
|         request = http.HttpRequest() | ||||
|         response = http.HttpResponse() | ||||
|         self.middleware.process_response(request, response) | ||||
							
								
								
									
										38
									
								
								django/contrib/messages/tests/session.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								django/contrib/messages/tests/session.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| from django.contrib.messages.tests.base import BaseTest | ||||
| from django.contrib.messages.storage.session import SessionStorage | ||||
|  | ||||
|  | ||||
| def set_session_data(storage, messages): | ||||
|     """ | ||||
|     Sets the messages into the backend request's session and remove the | ||||
|     backend's loaded data cache. | ||||
|     """ | ||||
|     storage.request.session[storage.session_key] = messages | ||||
|     if hasattr(storage, '_loaded_data'): | ||||
|         del storage._loaded_data | ||||
|  | ||||
|  | ||||
| def stored_session_messages_count(storage): | ||||
|     data = storage.request.session.get(storage.session_key, []) | ||||
|     return len(data) | ||||
|  | ||||
|  | ||||
| class SessionTest(BaseTest): | ||||
|     storage_class = SessionStorage | ||||
|  | ||||
|     def get_request(self): | ||||
|         self.session = {} | ||||
|         request = super(SessionTest, self).get_request() | ||||
|         request.session = self.session | ||||
|         return request | ||||
|  | ||||
|     def stored_messages_count(self, storage, response): | ||||
|         return stored_session_messages_count(storage) | ||||
|  | ||||
|     def test_get(self): | ||||
|         storage = self.storage_class(self.get_request()) | ||||
|         # Set initial data. | ||||
|         example_messages = ['test', 'me'] | ||||
|         set_session_data(storage, example_messages) | ||||
|         # Test that the message actually contains what we expect. | ||||
|         self.assertEqual(list(storage), example_messages) | ||||
							
								
								
									
										39
									
								
								django/contrib/messages/tests/urls.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								django/contrib/messages/tests/urls.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| from django.conf.urls.defaults import * | ||||
| from django.contrib import messages | ||||
| from django.core.urlresolvers import reverse | ||||
| from django.http import HttpResponseRedirect, HttpResponse | ||||
| from django.shortcuts import render_to_response | ||||
| from django.template import RequestContext, Template | ||||
|  | ||||
|  | ||||
| def add(request, message_type): | ||||
|     # don't default to False here, because we want to test that it defaults | ||||
|     # to False if unspecified | ||||
|     fail_silently = request.POST.get('fail_silently', None) | ||||
|     for msg in request.POST.getlist('messages'): | ||||
|         if fail_silently is not None: | ||||
|             getattr(messages, message_type)(request, msg, | ||||
|                                             fail_silently=fail_silently) | ||||
|         else: | ||||
|             getattr(messages, message_type)(request, msg) | ||||
|     show_url = reverse('django.contrib.messages.tests.urls.show') | ||||
|     return HttpResponseRedirect(show_url) | ||||
|  | ||||
|  | ||||
| def show(request): | ||||
|     t = Template("""{% if messages %} | ||||
| <ul class="messages"> | ||||
|     {% for message in messages %} | ||||
|     <li{% if message.tags %} class="{{ message.tags }}"{% endif %}> | ||||
|         {{ message }} | ||||
|     </li> | ||||
|     {% endfor %} | ||||
| </ul> | ||||
| {% endif %}""") | ||||
|     return HttpResponse(t.render(RequestContext(request))) | ||||
|  | ||||
|  | ||||
| urlpatterns = patterns('', | ||||
|     ('^add/(debug|info|success|warning|error)/$', add), | ||||
|     ('^show/$', show), | ||||
| ) | ||||
							
								
								
									
										65
									
								
								django/contrib/messages/tests/user_messages.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								django/contrib/messages/tests/user_messages.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | ||||
| from django import http | ||||
| from django.contrib.auth.models import User | ||||
| from django.contrib.messages.storage.user_messages import UserMessagesStorage,\ | ||||
|     LegacyFallbackStorage | ||||
| from django.contrib.messages.tests.cookie import set_cookie_data | ||||
| from django.contrib.messages.tests.fallback import FallbackTest | ||||
| from django.test import TestCase | ||||
|  | ||||
|  | ||||
| class UserMessagesTest(TestCase): | ||||
|  | ||||
|     def setUp(self): | ||||
|         self.user = User.objects.create(username='tester') | ||||
|  | ||||
|     def test_add(self): | ||||
|         storage = UserMessagesStorage(http.HttpRequest()) | ||||
|         self.assertRaises(NotImplementedError, storage.add, 'Test message 1') | ||||
|  | ||||
|     def test_get_anonymous(self): | ||||
|         # Ensure that the storage still works if no user is attached to the | ||||
|         # request. | ||||
|         storage = UserMessagesStorage(http.HttpRequest()) | ||||
|         self.assertEqual(len(storage), 0) | ||||
|  | ||||
|     def test_get(self): | ||||
|         storage = UserMessagesStorage(http.HttpRequest()) | ||||
|         storage.request.user = self.user | ||||
|         self.user.message_set.create(message='test message') | ||||
|  | ||||
|         self.assertEqual(len(storage), 1) | ||||
|         self.assertEqual(list(storage)[0].message, 'test message') | ||||
|  | ||||
|  | ||||
| class LegacyFallbackTest(FallbackTest, TestCase): | ||||
|     storage_class = LegacyFallbackStorage | ||||
|  | ||||
|     def setUp(self): | ||||
|         super(LegacyFallbackTest, self).setUp() | ||||
|         self.user = User.objects.create(username='tester') | ||||
|  | ||||
|     def get_request(self, *args, **kwargs): | ||||
|         request = super(LegacyFallbackTest, self).get_request(*args, **kwargs) | ||||
|         request.user = self.user | ||||
|         return request | ||||
|  | ||||
|     def test_get_legacy_only(self): | ||||
|         request = self.get_request() | ||||
|         storage = self.storage_class(request) | ||||
|         self.user.message_set.create(message='user message') | ||||
|  | ||||
|         # Test that the message actually contains what we expect. | ||||
|         self.assertEqual(len(storage), 1) | ||||
|         self.assertEqual(list(storage)[0].message, 'user message') | ||||
|  | ||||
|     def test_get_legacy(self): | ||||
|         request = self.get_request() | ||||
|         storage = self.storage_class(request) | ||||
|         cookie_storage = self.get_cookie_storage(storage) | ||||
|         self.user.message_set.create(message='user message') | ||||
|         set_cookie_data(cookie_storage, ['cookie']) | ||||
|  | ||||
|         # Test that the message actually contains what we expect. | ||||
|         self.assertEqual(len(storage), 2) | ||||
|         self.assertEqual(list(storage)[0].message, 'user message') | ||||
|         self.assertEqual(list(storage)[1], 'cookie') | ||||
							
								
								
									
										11
									
								
								django/contrib/messages/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								django/contrib/messages/utils.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| from django.conf import settings | ||||
| from django.contrib.messages import constants | ||||
|  | ||||
|  | ||||
| def get_level_tags(): | ||||
|     """ | ||||
|     Returns the message level tags. | ||||
|     """ | ||||
|     level_tags = constants.DEFAULT_TAGS.copy() | ||||
|     level_tags.update(getattr(settings, 'MESSAGE_TAGS', {})) | ||||
|     return level_tags | ||||
| @@ -10,6 +10,7 @@ RequestContext. | ||||
| from django.conf import settings | ||||
| from django.middleware.csrf import get_token | ||||
| from django.utils.functional import lazy, memoize, SimpleLazyObject | ||||
| from django.contrib import messages | ||||
|  | ||||
| def auth(request): | ||||
|     """ | ||||
| @@ -37,8 +38,8 @@ def auth(request): | ||||
|  | ||||
|     return { | ||||
|         'user': SimpleLazyObject(get_user), | ||||
|         'messages': lazy(memoize(lambda: get_user().get_and_delete_messages(), {}, 0), list)(), | ||||
|         'perms':  lazy(lambda: PermWrapper(get_user()), PermWrapper)(), | ||||
|         'messages': messages.get_messages(request), | ||||
|         'perms': lazy(lambda: PermWrapper(get_user()), PermWrapper)(), | ||||
|     } | ||||
|  | ||||
| def csrf(request): | ||||
|   | ||||
| @@ -105,6 +105,6 @@ class SMTPConnection(_SMTPConnection): | ||||
|         import warnings | ||||
|         warnings.warn( | ||||
|             'mail.SMTPConnection is deprecated; use mail.get_connection() instead.', | ||||
|             DeprecationWarning | ||||
|             PendingDeprecationWarning | ||||
|         ) | ||||
|         super(SMTPConnection, self).__init__(*args, **kwds) | ||||
|   | ||||
| @@ -110,6 +110,36 @@ class QuerySet(object): | ||||
|             return False | ||||
|         return True | ||||
|  | ||||
|     def __contains__(self, val): | ||||
|         # The 'in' operator works without this method, due to __iter__. This | ||||
|         # implementation exists only to shortcut the creation of Model | ||||
|         # instances, by bailing out early if we find a matching element. | ||||
|         pos = 0 | ||||
|         if self._result_cache is not None: | ||||
|             if val in self._result_cache: | ||||
|                 return True | ||||
|             elif self._iter is None: | ||||
|                 # iterator is exhausted, so we have our answer | ||||
|                 return False | ||||
|             # remember not to check these again: | ||||
|             pos = len(self._result_cache) | ||||
|         else: | ||||
|             # We need to start filling the result cache out. The following | ||||
|             # ensures that self._iter is not None and self._result_cache is not | ||||
|             # None | ||||
|             it = iter(self) | ||||
|  | ||||
|         # Carry on, one result at a time. | ||||
|         while True: | ||||
|             if len(self._result_cache) <= pos: | ||||
|                 self._fill_cache(num=1) | ||||
|             if self._iter is None: | ||||
|                 # we ran out of items | ||||
|                 return False | ||||
|             if self._result_cache[pos] == val: | ||||
|                 return True | ||||
|             pos += 1 | ||||
|  | ||||
|     def __getitem__(self, k): | ||||
|         """ | ||||
|         Retrieves an item or slice from the set of results. | ||||
|   | ||||
| @@ -22,6 +22,7 @@ from django.db.models.sql.expressions import SQLEvaluator | ||||
| from django.db.models.sql.where import WhereNode, Constraint, EverythingNode, AND, OR | ||||
| from django.core.exceptions import FieldError | ||||
|  | ||||
|  | ||||
| __all__ = ['Query'] | ||||
|  | ||||
| class Query(object): | ||||
|   | ||||
| @@ -6,6 +6,7 @@ from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured | ||||
| from django.utils.translation import ugettext | ||||
| from django.contrib.auth.views import redirect_to_login | ||||
| from django.views.generic import GenericViewError | ||||
| from django.contrib import messages | ||||
|  | ||||
|  | ||||
| def apply_extra_context(extra_context, context): | ||||
| @@ -110,8 +111,10 @@ def create_object(request, model=None, template_name=None, | ||||
|         form = form_class(request.POST, request.FILES) | ||||
|         if form.is_valid(): | ||||
|             new_object = form.save() | ||||
|             if request.user.is_authenticated(): | ||||
|                 request.user.message_set.create(message=ugettext("The %(verbose_name)s was created successfully.") % {"verbose_name": model._meta.verbose_name}) | ||||
|              | ||||
|             msg = ugettext("The %(verbose_name)s was created successfully.") %\ | ||||
|                                     {"verbose_name": model._meta.verbose_name} | ||||
|             messages.success(request, msg, fail_silently=True) | ||||
|             return redirect(post_save_redirect, new_object) | ||||
|     else: | ||||
|         form = form_class() | ||||
| @@ -152,8 +155,9 @@ def update_object(request, model=None, object_id=None, slug=None, | ||||
|         form = form_class(request.POST, request.FILES, instance=obj) | ||||
|         if form.is_valid(): | ||||
|             obj = form.save() | ||||
|             if request.user.is_authenticated(): | ||||
|                 request.user.message_set.create(message=ugettext("The %(verbose_name)s was updated successfully.") % {"verbose_name": model._meta.verbose_name}) | ||||
|             msg = ugettext("The %(verbose_name)s was updated successfully.") %\ | ||||
|                                     {"verbose_name": model._meta.verbose_name} | ||||
|             messages.success(request, msg, fail_silently=True) | ||||
|             return redirect(post_save_redirect, obj) | ||||
|     else: | ||||
|         form = form_class(instance=obj) | ||||
| @@ -194,8 +198,9 @@ def delete_object(request, model, post_delete_redirect, object_id=None, | ||||
|  | ||||
|     if request.method == 'POST': | ||||
|         obj.delete() | ||||
|         if request.user.is_authenticated(): | ||||
|             request.user.message_set.create(message=ugettext("The %(verbose_name)s was deleted.") % {"verbose_name": model._meta.verbose_name}) | ||||
|         msg = ugettext("The %(verbose_name)s was deleted.") %\ | ||||
|                                     {"verbose_name": model._meta.verbose_name} | ||||
|         messages.success(request, msg, fail_silently=True) | ||||
|         return HttpResponseRedirect(post_delete_redirect) | ||||
|     else: | ||||
|         if not template_name: | ||||
|   | ||||
| @@ -171,6 +171,7 @@ Other batteries included | ||||
|     * :ref:`Internationalization <topics-i18n>` | ||||
|     * :ref:`Jython support <howto-jython>` | ||||
|     * :ref:`"Local flavor" <ref-contrib-localflavor>` | ||||
|     * :ref:`Messages <ref-contrib-messages>` | ||||
|     * :ref:`Pagination <topics-pagination>` | ||||
|     * :ref:`Redirects <ref-contrib-redirects>` | ||||
|     * :ref:`Serialization <topics-serialization>` | ||||
|   | ||||
| @@ -426,6 +426,47 @@ translated, here's what to do: | ||||
|  | ||||
| .. _Django i18n mailing list: http://groups.google.com/group/django-i18n/ | ||||
|  | ||||
| Django conventions | ||||
| ================== | ||||
|  | ||||
| Various Django-specific code issues are detailed in this section. | ||||
|  | ||||
| Use of ``django.conf.settings`` | ||||
| ------------------------------- | ||||
|  | ||||
| Modules should not in general use settings stored in ``django.conf.settings`` at | ||||
| the top level (i.e. evaluated when the module is imported). The explanation for | ||||
| this is as follows: | ||||
|  | ||||
| Manual configuration of settings (i.e. not relying on the | ||||
| ``DJANGO_SETTINGS_MODULE`` environment variable) is allowed and possible as | ||||
| follows:: | ||||
|  | ||||
|     from django.conf import settings | ||||
|  | ||||
|     settings.configure({}, SOME_SETTING='foo') | ||||
|  | ||||
| However, if any setting is accessed before the ``settings.configure`` line, this | ||||
| will not work. (Internally, ``setttings`` is a ``LazyObject`` which configures | ||||
| itself automatically when the settings are accessed if it has not already been | ||||
| configured). | ||||
|  | ||||
| So, if there is a module containg some code as follows:: | ||||
|  | ||||
|     from django.conf import settings | ||||
|     from django.core.urlresolvers import get_callable | ||||
|  | ||||
|     default_foo_view = get_callable(settings.FOO_VIEW) | ||||
|  | ||||
| ...then importing this module will cause the settings object to be configured. | ||||
| That means that the ability for third parties to import the module at the top | ||||
| level is incompatible with the ability to configure the settings object | ||||
| manually, or makes it very difficult in some circumstances. | ||||
|  | ||||
| Instead of the above code, a level of laziness or indirection must be used, such | ||||
| as :class:`django.utils.functional.LazyObject`, :func:`django.utils.functional.lazy` or | ||||
| ``lambda``. | ||||
|  | ||||
| Coding style | ||||
| ============ | ||||
|  | ||||
|   | ||||
| @@ -40,6 +40,14 @@ their deprecation, as per the :ref:`Django deprecation policy | ||||
|           multiple databases. In 1.4, the support functions that allow methods | ||||
|           with the old prototype to continue working will be removed. | ||||
|  | ||||
|         * The ``Message`` model (in ``django.contrib.auth``), its related | ||||
|           manager in the ``User`` model (``user.message_set``), and the | ||||
|           associated methods (``user.message_set.create()`` and | ||||
|           ``user.get_and_delete_messages()``), which have | ||||
|           been deprecated since the 1.2 release, will be removed.  The  | ||||
|           :ref:`messages framework <ref-contrib-messages>` should be used  | ||||
|           instead. | ||||
|  | ||||
|     * 2.0 | ||||
|         * ``django.views.defaults.shortcut()``. This function has been moved | ||||
|           to ``django.contrib.contenttypes.views.shortcut()`` as part of the | ||||
|   | ||||
| @@ -153,6 +153,8 @@ launch a CSRF attack on your site against that user.  The | ||||
| ``@csrf_response_exempt`` decorator can be used to fix this, but only if the | ||||
| page doesn't also contain internal forms that require the token. | ||||
|  | ||||
| .. _ref-csrf-upgrading-notes: | ||||
|  | ||||
| Upgrading notes | ||||
| --------------- | ||||
|  | ||||
|   | ||||
| @@ -34,6 +34,7 @@ those packages have. | ||||
|    formtools/index | ||||
|    humanize | ||||
|    localflavor | ||||
|    messages | ||||
|    redirects | ||||
|    sitemaps | ||||
|    sites | ||||
| @@ -150,6 +151,17 @@ read the source code in django/contrib/markup/templatetags/markup.py. | ||||
| .. _Markdown: http://en.wikipedia.org/wiki/Markdown | ||||
| .. _ReST (ReStructured Text): http://en.wikipedia.org/wiki/ReStructuredText | ||||
|  | ||||
| messages | ||||
| ======== | ||||
|  | ||||
| .. versionchanged:: 1.2 | ||||
|     The messages framework was added. | ||||
|  | ||||
| A framework for storing and retrieving temporary cookie- or session-based | ||||
| messages | ||||
|  | ||||
| See the :ref:`messages documentation <ref-contrib-messages>`. | ||||
|  | ||||
| redirects | ||||
| ========= | ||||
|  | ||||
|   | ||||
							
								
								
									
										405
									
								
								docs/ref/contrib/messages.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										405
									
								
								docs/ref/contrib/messages.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,405 @@ | ||||
| .. _ref-contrib-messages: | ||||
|  | ||||
| ====================== | ||||
| The messages framework | ||||
| ====================== | ||||
|  | ||||
| .. module:: django.contrib.messages | ||||
|    :synopsis: Provides cookie- and session-based temporary message storage. | ||||
|  | ||||
| Django provides full support for cookie- and session-based messaging, for | ||||
| both anonymous and authenticated clients. The messages framework allows you | ||||
| to temporarily store messages in one request and retrieve them for display | ||||
| in a subsequent request (usually the next one). Every message is tagged | ||||
| with a specific ``level`` that determines its priority (e.g., ``info``, | ||||
| ``warning``, or ``error``). | ||||
|  | ||||
| .. versionadded:: 1.2 | ||||
|    The messages framework was added. | ||||
|  | ||||
| Enabling messages | ||||
| ================= | ||||
|  | ||||
| Messages are implemented through a :ref:`middleware <ref-middleware>` | ||||
| class and corresponding :ref:`context processor <ref-templates-api>`. | ||||
|  | ||||
| To enable message functionality, do the following: | ||||
|  | ||||
|     * Edit the :setting:`MIDDLEWARE_CLASSES` setting and make sure | ||||
|       it contains ``'django.contrib.messages.middleware.MessageMiddleware'``. | ||||
|  | ||||
|       If you are using a :ref:`storage backend <message-storage-backends>` that | ||||
|       relies on :ref:`sessions <topics-http-sessions>` (the default), | ||||
|       ``'django.contrib.sessions.middleware.SessionMiddleware'`` must be | ||||
|       enabled and appear before ``MessageMiddleware`` in your | ||||
|       :setting:`MIDDLEWARE_CLASSES`. | ||||
|  | ||||
|     * Edit the :setting:`TEMPLATE_CONTEXT_PROCESSORS` setting and make sure | ||||
|       it contains ``'django.contrib.messages.context_processors.messages'``. | ||||
|  | ||||
|     * Add ``'django.contrib.messages'`` to your :setting:`INSTALLED_APPS` | ||||
|       setting | ||||
|  | ||||
| The default ``settings.py`` created by ``django-admin.py startproject`` has | ||||
| ``MessageMiddleware`` activated and the ``django.contrib.messages`` app | ||||
| installed.  Also, the default value for :setting:`TEMPLATE_CONTEXT_PROCESSORS` | ||||
| contains ``'django.contrib.messages.context_processors.messages'``. | ||||
|  | ||||
| If you don't want to use messages, you can remove the | ||||
| ``MessageMiddleware`` line from :setting:`MIDDLEWARE_CLASSES`, the ``messages`` | ||||
| context processor from :setting:`TEMPLATE_CONTEXT_PROCESSORS` and | ||||
| ``'django.contrib.messages'`` from your :setting:`INSTALLED_APPS`. | ||||
|  | ||||
| Configuring the message engine | ||||
| ============================== | ||||
|  | ||||
| .. _message-storage-backends: | ||||
|  | ||||
| Storage backends | ||||
| ---------------- | ||||
|  | ||||
| The messages framework can use different backends to store temporary messages. | ||||
| To change which backend is being used, add a `MESSAGE_STORAGE`_ to your | ||||
| settings, referencing the module and class of the storage class. For | ||||
| example:: | ||||
|  | ||||
|     MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage' | ||||
|  | ||||
| The value should be the full path of the desired storage class. | ||||
|  | ||||
| Four storage classes are included: | ||||
|  | ||||
| ``'django.contrib.messages.storage.session.SessionStorage'`` | ||||
|     This class stores all messages inside of the request's session. It | ||||
|     requires Django's ``contrib.session`` application. | ||||
|  | ||||
| ``'django.contrib.messages.storage.cookie.CookieStorage'`` | ||||
|     This class stores the message data in a cookie (signed with a secret hash | ||||
|     to prevent manipulation) to persist notifications across requests. Old | ||||
|     messages are dropped if the cookie data size would exceed 4096 bytes. | ||||
|  | ||||
| ``'django.contrib.messages.storage.fallback.FallbackStorage'`` | ||||
|     This class first uses CookieStorage for all messages, falling back to using | ||||
|     SessionStorage for the messages that could not fit in a single cookie. | ||||
|  | ||||
|     Since it is uses SessionStorage, it also requires Django's | ||||
|     ``contrib.session`` application. | ||||
|  | ||||
| ``'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'`` | ||||
|     This is the default temporary storage class. | ||||
|  | ||||
|     This class extends FallbackStorage and adds compatibility methods to | ||||
|     to retrieve any messages stored in the user Message model by code that | ||||
|     has not yet been updated to use the new API. This storage is temporary | ||||
|     (because it makes use of code that is pending deprecation) and will be | ||||
|     removed in Django 1.4. At that time, the default storage will become | ||||
|     ``django.contrib.messages.storage.fallback.FallbackStorage``. For more | ||||
|     information, see `LegacyFallbackStorage`_ below. | ||||
|  | ||||
| To write your own storage class, subclass the ``BaseStorage`` class in | ||||
| ``django.contrib.messages.storage.base`` and implement the ``_get`` and | ||||
| ``_store`` methods. | ||||
|  | ||||
| LegacyFallbackStorage | ||||
| ^^^^^^^^^^^^^^^^^^^^^ | ||||
|  | ||||
| The ``LegacyFallbackStorage`` is a temporary tool to facilitate the transition | ||||
| from the deprecated ``user.message_set`` API and will be removed in Django 1.4 | ||||
| according to Django's standard deprecation policy.  For more information, see | ||||
| the full :ref:`release process documentation <internals-release-process>`. | ||||
|  | ||||
| In addition to the functionality in the ``FallbackStorage``, it adds a custom, | ||||
| read-only storage class that retrieves messages from the user ``Message`` | ||||
| model. Any messages that were stored in the ``Message`` model (e.g., by code | ||||
| that has not yet been updated to use the messages framework) will be retrieved | ||||
| first, followed by those stored in a cookie and in the session, if any.  Since | ||||
| messages stored in the ``Message`` model do not have a concept of levels, they | ||||
| will be assigned the ``INFO`` level by default. | ||||
|  | ||||
| Message levels | ||||
| -------------- | ||||
|  | ||||
| The messages framework is based on a configurable level architecture similar | ||||
| to that of the Python logging module.  Message levels allow you to group | ||||
| messages by type so they can be filtered or displayed differently in views and | ||||
| templates. | ||||
|  | ||||
| The built-in levels (which can be imported from ``django.contrib.messages`` | ||||
| directly) are: | ||||
|  | ||||
| =========== ======== | ||||
| Constant    Purpose | ||||
| =========== ======== | ||||
| ``DEBUG``   Development-related messages that will be ignored (or removed) in a production deployment | ||||
| ``INFO``    Informational messages for the user | ||||
| ``SUCCESS`` An action was successful, e.g. "Your profile was updated successfully" | ||||
| ``WARNING`` A failure did not occur but may be imminent | ||||
| ``ERROR``   An action was **not** successful or some other failure occurred | ||||
| =========== ======== | ||||
|  | ||||
| The `MESSAGE_LEVEL`_ setting can be used to change the minimum recorded | ||||
| level. Attempts to add messages of a level less than this will be ignored. | ||||
|  | ||||
| Message tags | ||||
| ------------ | ||||
|  | ||||
| Message tags are a string representation of the message level plus any | ||||
| extra tags that were added directly in the view (see | ||||
| `Adding extra message tags`_ below for more details).  Tags are stored in a | ||||
| string and are separated by spaces.  Typically, message tags | ||||
| are used as CSS classes to customize message style based on message type. By | ||||
| default, each level has a single tag that's a lowercase version of its own | ||||
| constant: | ||||
|  | ||||
| ==============  =========== | ||||
| Level Constant  Tag | ||||
| ==============  =========== | ||||
| ``DEBUG``       ``debug`` | ||||
| ``INFO``        ``info`` | ||||
| ``SUCCESS``     ``success`` | ||||
| ``WARNING``     ``warning`` | ||||
| ``ERROR``       ``error`` | ||||
| ==============  =========== | ||||
|  | ||||
| To change the default tags for a message level (either built-in or custom), | ||||
| set the `MESSAGE_TAGS`_ setting to a dictionary containing the levels | ||||
| you wish to change. As this extends the default tags, you only need to provide | ||||
| tags for the levels you wish to override:: | ||||
|  | ||||
|     from django.contrib.messages import constants as messages | ||||
|     MESSAGE_TAGS = { | ||||
|         messages.INFO: '', | ||||
|         50: 'critical', | ||||
|     } | ||||
|  | ||||
| Using messages in views and templates | ||||
| ===================================== | ||||
|  | ||||
| Adding a message | ||||
| ---------------- | ||||
|  | ||||
| To add a message, call:: | ||||
|  | ||||
|     from django.contrib import messages | ||||
|     messages.add_message(request, messages.INFO, 'Hello world.') | ||||
|  | ||||
| Some shortcut methods provide a standard way to add messages with commonly | ||||
| used tags (which are usually represented as HTML classes for the message):: | ||||
|  | ||||
|     messages.debug(request, '%s SQL statements were executed.' % count) | ||||
|     messages.info(request, 'Three credits remain in your account.') | ||||
|     messages.success(request, 'Profile details updated.') | ||||
|     messages.warning(request, 'Your account expires in three days.') | ||||
|     messages.error(request, 'Document deleted.') | ||||
|  | ||||
| Displaying messages | ||||
| ------------------- | ||||
|  | ||||
| In your template, use something like:: | ||||
|  | ||||
|     {% if messages %} | ||||
|     <ul class="messages"> | ||||
|         {% for message in messages %} | ||||
|         <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li> | ||||
|         {% endfor %} | ||||
|     </ul> | ||||
|     {% endif %} | ||||
|  | ||||
| If you're using the context processor, your template should be rendered with a | ||||
| ``RequestContext``. Otherwise, ensure ``messages`` is available to | ||||
| the template context. | ||||
|  | ||||
| Creating custom message levels | ||||
| ------------------------------ | ||||
|  | ||||
| Messages levels are nothing more than integers, so you can define your own | ||||
| level constants and use them to create more customized user feedback, e.g.:: | ||||
|  | ||||
|     CRITICAL = 50 | ||||
|  | ||||
|     def my_view(request): | ||||
|         messages.add_message(request, CRITICAL, 'A serious error occurred.') | ||||
|  | ||||
| When creating custom message levels you should be careful to avoid overloading | ||||
| existing levels.  The values for the built-in levels are: | ||||
|  | ||||
| .. _message-level-constants: | ||||
|  | ||||
| ==============  ===== | ||||
| Level Constant  Value | ||||
| ==============  ===== | ||||
| ``DEBUG``       10 | ||||
| ``INFO``        20 | ||||
| ``SUCCESS``     25 | ||||
| ``WARNING``     30 | ||||
| ``ERROR``       40 | ||||
| ==============  ===== | ||||
|  | ||||
| If you need to identify the custom levels in your HTML or CSS, you need to | ||||
| provide a mapping via the `MESSAGE_TAGS`_ setting. | ||||
|  | ||||
| .. note:: | ||||
|    If you are creating a reusable application, it is recommended to use | ||||
|    only the built-in `message levels`_ and not rely on any custom levels. | ||||
|  | ||||
| Changing the minimum recorded level per-request | ||||
| ----------------------------------------------- | ||||
|  | ||||
| The minimum recorded level can be set per request by changing the ``level`` | ||||
| attribute of the messages storage instance:: | ||||
|  | ||||
|     from django.contrib import messages | ||||
|  | ||||
|     # Change the messages level to ensure the debug message is added. | ||||
|     messages.get_messages(request).level = messages.DEBUG | ||||
|     messages.debug(request, 'Test message...') | ||||
|  | ||||
|     # In another request, record only messages with a level of WARNING and higher | ||||
|     messages.get_messages(request).level = messages.WARNING | ||||
|     messages.success(request, 'Your profile was updated.') # ignored | ||||
|     messages.warning(request, 'Your account is about to expire.') # recorded | ||||
|  | ||||
|     # Set the messages level back to default. | ||||
|     messages.get_messages(request).level = None | ||||
|  | ||||
| For more information on how the minimum recorded level functions, see | ||||
| `Message levels`_ above. | ||||
|  | ||||
| Adding extra message tags | ||||
| ------------------------- | ||||
|  | ||||
| For more direct control over message tags, you can optionally provide a string | ||||
| containing extra tags to any of the add methods:: | ||||
|  | ||||
|     messages.add_message(request, messages.INFO, 'Over 9000!', | ||||
|                          extra_tags='dragonball') | ||||
|     messages.error(request, 'Email box full', extra_tags='email') | ||||
|  | ||||
| Extra tags are added before the default tag for that level and are space | ||||
| separated. | ||||
|  | ||||
| Failing silently when the message framework is disabled | ||||
| ------------------------------------------------------- | ||||
|  | ||||
| If you're writing a reusable app (or other piece of code) and want to include | ||||
| messaging functionality, but don't want to require your users to enable it | ||||
| if they don't want to, you may pass an additional keyword argument | ||||
| ``fail_silently=True`` to any of the ``add_message`` family of methods. For | ||||
| example:: | ||||
|  | ||||
|     messages.add_message(request, messages.SUCCESS, 'Profile details updated.', | ||||
|                          fail_silently=True) | ||||
|     messages.info(request, 'Hello world.', fail_silently=True) | ||||
|  | ||||
| Internally, Django uses this functionality in the create, update, and delete | ||||
| :ref:`generic views <topics-generic-views>` so that they work even if the | ||||
| message framework is disabled. | ||||
|  | ||||
| .. note:: | ||||
|    Setting ``fail_silently=True`` only hides the ``MessageFailure`` that would | ||||
|    otherwise occur when the messages framework disabled and one attempts to | ||||
|    use one of the ``add_message`` family of methods.  It does not hide failures | ||||
|    that may occur for other reasons. | ||||
|  | ||||
| Expiration of messages | ||||
| ====================== | ||||
|  | ||||
| The messages are marked to be cleared when the storage instance is iterated | ||||
| (and cleared when the response is processed). | ||||
|  | ||||
| To avoid the messages being cleared, you can set the messages storage to | ||||
| ``False`` after iterating:: | ||||
|  | ||||
|     storage = messages.get_messages(request) | ||||
|     for message in storage: | ||||
|         do_something_with(message) | ||||
|     storage.used = False | ||||
|  | ||||
| Behavior of parallel requests | ||||
| ============================= | ||||
|  | ||||
| Due to the way cookies (and hence sessions) work, **the behavior of any | ||||
| backends that make use of cookies or sessions is undefined when the same | ||||
| client makes multiple requests that set or get messages in parallel**.  For | ||||
| example, if a client initiates a request that creates a message in one window | ||||
| (or tab) and then another that fetches any uniterated messages in another | ||||
| window, before the first window redirects, the message may appear in the | ||||
| second window instead of the first window where it may be expected. | ||||
|  | ||||
| In short, when multiple simultaneous requests from the same client are | ||||
| involved, messages are not guaranteed to be delivered to the same window that | ||||
| created them nor, in some cases, at all.  Note that this is typically not a | ||||
| problem in most applications and will become a non-issue in HTML5, where each | ||||
| window/tab will have its own browsing context. | ||||
|  | ||||
| Settings | ||||
| ======== | ||||
|  | ||||
| A few :ref:`Django settings <ref-settings>` give you control over message | ||||
| behavior: | ||||
|  | ||||
| MESSAGE_LEVEL | ||||
| ------------- | ||||
|  | ||||
| Default: ``messages.INFO`` | ||||
|  | ||||
| This sets the minimum message that will be saved in the message storage.  See | ||||
| `Message levels`_ above for more details. | ||||
|  | ||||
| .. admonition:: Important | ||||
|  | ||||
|    If you override ``MESSAGE_LEVEL`` in your settings file and rely on any of | ||||
|    the built-in constants, you must import the constants module directly to | ||||
|    avoid the potential for circular imports, e.g.:: | ||||
|  | ||||
|        from django.contrib.messages import constants as message_constants | ||||
|        MESSAGE_LEVEL = message_constants.DEBUG | ||||
|  | ||||
|    If desired, you may specify the numeric values for the constants directly | ||||
|    according to the values in the above :ref:`constants table | ||||
|    <message-level-constants>`. | ||||
|  | ||||
| MESSAGE_STORAGE | ||||
| --------------- | ||||
|  | ||||
| Default: ``'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'`` | ||||
|  | ||||
| Controls where Django stores message data. Valid values are: | ||||
|  | ||||
|     * ``'django.contrib.messages.storage.fallback.FallbackStorage'`` | ||||
|     * ``'django.contrib.messages.storage.session.SessionStorage'`` | ||||
|     * ``'django.contrib.messages.storage.cookie.CookieStorage'`` | ||||
|     * ``'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'`` | ||||
|  | ||||
| See `Storage backends`_ for more details. | ||||
|  | ||||
| MESSAGE_TAGS | ||||
| ------------ | ||||
|  | ||||
| Default:: | ||||
|  | ||||
|         {messages.DEBUG: 'debug', | ||||
|         messages.INFO: 'info', | ||||
|         messages.SUCCESS: 'success', | ||||
|         messages.WARNING: 'warning', | ||||
|         messages.ERROR: 'error',} | ||||
|  | ||||
| This sets the mapping of message level to message tag, which is typically | ||||
| rendered as a CSS class in HTML. If you specify a value, it will extend | ||||
| the default. This means you only have to specify those values which you need | ||||
| to override. See `Displaying messages`_ above for more details. | ||||
|  | ||||
| .. admonition:: Important | ||||
|  | ||||
|    If you override ``MESSAGE_TAGS`` in your settings file and rely on any of | ||||
|    the built-in constants, you must import the ``constants`` module directly to | ||||
|    avoid the potential for circular imports, e.g.:: | ||||
|  | ||||
|        from django.contrib.messages import constants as message_constants | ||||
|        MESSAGE_TAGS = {message_constants.INFO: ''} | ||||
|  | ||||
|    If desired, you may specify the numeric values for the constants directly | ||||
|    according to the values in the above :ref:`constants table | ||||
|    <message-level-constants>`. | ||||
|  | ||||
| .. _Django settings: ../settings/ | ||||
| @@ -139,6 +139,20 @@ Enables language selection based on data from the request. It customizes | ||||
| content for each user. See the :ref:`internationalization documentation | ||||
| <topics-i18n>`. | ||||
|  | ||||
| Message middleware | ||||
| ------------------ | ||||
|  | ||||
| .. module:: django.contrib.messages.middleware | ||||
|    :synopsis: Message middleware. | ||||
|  | ||||
| .. class:: django.contrib.messages.middleware.MessageMiddleware | ||||
|  | ||||
| .. versionadded:: 1.2 | ||||
|    ``MessageMiddleware`` was added. | ||||
|     | ||||
| Enables cookie- and session-based message support. See the  | ||||
| :ref:`messages documentation <ref-contrib-messages>`. | ||||
|  | ||||
| Session middleware | ||||
| ------------------ | ||||
|  | ||||
|   | ||||
| @@ -896,6 +896,43 @@ Bad: ``"http://www.example.com/static"`` | ||||
|  | ||||
| .. setting:: MIDDLEWARE_CLASSES | ||||
|  | ||||
| MESSAGE_LEVEL | ||||
| ------------- | ||||
|  | ||||
| .. versionadded:: 1.2 | ||||
|  | ||||
| Default: `messages.INFO` | ||||
|  | ||||
| Sets the minimum message level that will be recorded by the messages  | ||||
| framework. See the :ref:`messages documentation <ref-contrib-messages>` for | ||||
| more details. | ||||
|  | ||||
| MESSAGE_STORAGE | ||||
| --------------- | ||||
|  | ||||
| .. versionadded:: 1.2 | ||||
|  | ||||
| Default: ``'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'`` | ||||
|  | ||||
| Controls where Django stores message data.  See the | ||||
| :ref:`messages documentation <ref-contrib-messages>` for more details. | ||||
|  | ||||
| MESSAGE_TAGS | ||||
| ------------ | ||||
|  | ||||
| .. versionadded:: 1.2 | ||||
|  | ||||
| Default:: | ||||
|  | ||||
|         {messages.DEBUG: 'debug', | ||||
|         messages.INFO: 'info', | ||||
|         messages.SUCCESS: 'success', | ||||
|         messages.WARNING: 'warning', | ||||
|         messages.ERROR: 'error',} | ||||
|  | ||||
| Sets the mapping of message levels to message tags. See the | ||||
| :ref:`messages documentation <ref-contrib-messages>` for more details. | ||||
|  | ||||
| MIDDLEWARE_CLASSES | ||||
| ------------------ | ||||
|  | ||||
| @@ -904,10 +941,16 @@ Default:: | ||||
|     ('django.middleware.common.CommonMiddleware', | ||||
|      'django.contrib.sessions.middleware.SessionMiddleware', | ||||
|      'django.middleware.csrf.CsrfViewMiddleware', | ||||
|      'django.contrib.auth.middleware.AuthenticationMiddleware',) | ||||
|      'django.contrib.auth.middleware.AuthenticationMiddleware', | ||||
|      'django.contrib.messages.middleware.MessageMiddleware',) | ||||
|  | ||||
| A tuple of middleware classes to use. See :ref:`topics-http-middleware`. | ||||
|  | ||||
| .. versionchanged:: 1.2 | ||||
|    ``'django.contrib.messages.middleware.MessageMiddleware'`` was added to the | ||||
|    default.  For more information, see the :ref:`messages documentation | ||||
|    <ref-contrib-messages>`. | ||||
|  | ||||
| .. setting:: MONTH_DAY_FORMAT | ||||
|  | ||||
| MONTH_DAY_FORMAT | ||||
| @@ -1155,12 +1198,18 @@ Default:: | ||||
|     ("django.core.context_processors.auth", | ||||
|     "django.core.context_processors.debug", | ||||
|     "django.core.context_processors.i18n", | ||||
|     "django.core.context_processors.media") | ||||
|     "django.core.context_processors.media", | ||||
|     "django.contrib.messages.context_processors.messages") | ||||
|  | ||||
| A tuple of callables that are used to populate the context in ``RequestContext``. | ||||
| These callables take a request object as their argument and return a dictionary | ||||
| of items to be merged into the context. | ||||
|  | ||||
| .. versionchanged:: 1.2 | ||||
|    ``"django.contrib.messages.context_processors.messages"`` was added to the | ||||
|    default.  For more information, see the :ref:`messages documentation | ||||
|    <ref-contrib-messages>`. | ||||
|  | ||||
| .. setting:: TEMPLATE_DEBUG | ||||
|  | ||||
| TEMPLATE_DEBUG | ||||
|   | ||||
| @@ -311,7 +311,8 @@ and return a dictionary of items to be merged into the context. By default, | ||||
|     ("django.core.context_processors.auth", | ||||
|     "django.core.context_processors.debug", | ||||
|     "django.core.context_processors.i18n", | ||||
|     "django.core.context_processors.media") | ||||
|     "django.core.context_processors.media", | ||||
|     "django.contrib.messages.context_processors.messages") | ||||
|  | ||||
| .. versionadded:: 1.2 | ||||
|    In addition to these, ``RequestContext`` always uses | ||||
| @@ -320,6 +321,10 @@ and return a dictionary of items to be merged into the context. By default, | ||||
|    in case of accidental misconfiguration, it is deliberately hardcoded in and | ||||
|    cannot be turned off by the :setting:`TEMPLATE_CONTEXT_PROCESSORS` setting. | ||||
|  | ||||
| .. versionadded:: 1.2 | ||||
|    The ``'messages'`` context processor was added.  For more information, see  | ||||
|    the :ref:`messages documentation <ref-contrib-messages>`. | ||||
|  | ||||
| Each processor is applied in order. That means, if one processor adds a | ||||
| variable to the context and a second processor adds a variable with the same | ||||
| name, the second will override the first. The default processors are explained | ||||
| @@ -365,17 +370,18 @@ If :setting:`TEMPLATE_CONTEXT_PROCESSORS` contains this processor, every | ||||
|       logged-in user (or an ``AnonymousUser`` instance, if the client isn't | ||||
|       logged in). | ||||
|  | ||||
|     * ``messages`` -- A list of messages (as strings) for the currently | ||||
|       logged-in user. Behind the scenes, this calls | ||||
|       ``request.user.get_and_delete_messages()`` for every request. That method | ||||
|       collects the user's messages and deletes them from the database. | ||||
|  | ||||
|       Note that messages are set with ``user.message_set.create``. | ||||
|     * ``messages`` -- A list of messages (as strings) that have been set | ||||
|       via the :ref:`messages framework <ref-contrib-messages>`. | ||||
|  | ||||
|     * ``perms`` -- An instance of | ||||
|       ``django.core.context_processors.PermWrapper``, representing the | ||||
|       permissions that the currently logged-in user has. | ||||
|  | ||||
| .. versionchanged:: 1.2 | ||||
|    Prior to version 1.2, the ``messages`` variable was a lazy accessor for | ||||
|    ``user.get_and_delete_messages()``. It has been changed to include any  | ||||
|    messages added via the :ref:`messages framework <ref-contrib-messages`. | ||||
|  | ||||
| django.core.context_processors.debug | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
| @@ -427,6 +433,25 @@ If :setting:`TEMPLATE_CONTEXT_PROCESSORS` contains this processor, every | ||||
| :class:`~django.http.HttpRequest`. Note that this processor is not enabled by default; | ||||
| you'll have to activate it. | ||||
|  | ||||
| django.contrib.messages.context_processors.messages | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
| If :setting:`TEMPLATE_CONTEXT_PROCESSORS` contains this processor, every | ||||
| ``RequestContext`` will contain a single additional variable: | ||||
|  | ||||
|     * ``messages`` -- A list of messages (as strings) that have been set | ||||
|       via the user model (using ``user.message_set.create``) or through | ||||
|       the :ref:`messages framework <ref-contrib-messages>`. | ||||
|  | ||||
| .. versionadded:: 1.2 | ||||
|    This template context variable was previously supplied by the ``'auth'`` | ||||
|    context processor.  For backwards compatibility the ``'auth'`` context | ||||
|    processor will continue to supply the ``messages`` variable until Django | ||||
|    1.4.  If you use the ``messages`` variable, your project will work with | ||||
|    either (or both) context processors, but it is recommended to add  | ||||
|    ``django.contrib.messages.context_processors.messages`` so your project | ||||
|    will be prepared for the future upgrade. | ||||
|  | ||||
| Writing your own context processors | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
|   | ||||
| @@ -26,13 +26,13 @@ There have been large changes to the way that CSRF protection works, detailed in | ||||
| changes that developers must be aware of: | ||||
|  | ||||
|  * ``CsrfResponseMiddleware`` and ``CsrfMiddleware`` have been deprecated, and | ||||
|    will be removed completely in Django 1.4, in favour of a template tag that | ||||
|    will be removed completely in Django 1.4, in favor of a template tag that | ||||
|    should be inserted into forms. | ||||
|  | ||||
|  * All contrib apps use a ``csrf_protect`` decorator to protect the view.  This | ||||
|    requires the use of the csrf_token template tag in the template, so if you | ||||
|    have used custom templates for contrib views, you MUST READ THE UPGRADE | ||||
|    INSTRUCTIONS to fix those templates. | ||||
|    have used custom templates for contrib views, you MUST READ THE :ref:`UPGRADE | ||||
|    INSTRUCTIONS <ref-csrf-upgrading-notes>` to fix those templates. | ||||
|  | ||||
|  * ``CsrfViewMiddleware`` is included in :setting:`MIDDLEWARE_CLASSES` by | ||||
|    default. This turns on CSRF protection by default, so that views that accept | ||||
| @@ -42,8 +42,8 @@ changes that developers must be aware of: | ||||
|  * All of the CSRF has moved from contrib to core (with backwards compatible | ||||
|    imports in the old locations, which are deprecated). | ||||
|  | ||||
| LazyObject | ||||
| ---------- | ||||
| ``LazyObject`` | ||||
| -------------- | ||||
|  | ||||
| ``LazyObject`` is an undocumented utility class used for lazily wrapping other | ||||
| objects of unknown type.  In Django 1.1 and earlier, it handled introspection in | ||||
| @@ -214,7 +214,86 @@ argument to resolve database-specific values. | ||||
| Features deprecated in 1.2 | ||||
| ========================== | ||||
|  | ||||
| None. | ||||
| CSRF response rewriting middleware | ||||
| ---------------------------------- | ||||
|  | ||||
| ``CsrfResponseMiddleware``, the middleware that automatically inserted CSRF | ||||
| tokens into POST forms in outgoing pages, has been deprecated in favor of a | ||||
| template tag method (see above), and will be removed completely in Django | ||||
| 1.4. ``CsrfMiddleware``, which includes the functionality of | ||||
| ``CsrfResponseMiddleware`` and ``CsrfViewMiddleware`` has likewise been | ||||
| deprecated. | ||||
|  | ||||
| Also, the CSRF module has moved from contrib to core, and the old imports are | ||||
| deprecated, as described in the :ref:`upgrading notes <ref-csrf-upgrading-notes>`. | ||||
|  | ||||
| ``SMTPConnection`` | ||||
| ------------------ | ||||
|  | ||||
| The ``SMTPConnection`` class has been deprecated in favor of a generic | ||||
| E-mail backend API. Old code that explicitly instantiated an instance | ||||
| of an SMTPConnection:: | ||||
|  | ||||
|     from django.core.mail import SMTPConnection | ||||
|     connection = SMTPConnection() | ||||
|     messages = get_notification_email() | ||||
|     connection.send_messages(messages) | ||||
|  | ||||
| should now call :meth:`~django.core.mail.get_connection()` to | ||||
| instantiate a generic e-mail connection:: | ||||
|  | ||||
|     from django.core.mail import get_connection | ||||
|     connection = get_connection() | ||||
|     messages = get_notification_email() | ||||
|     connection.send_messages(messages) | ||||
|  | ||||
| Depending on the value of the :setting:`EMAIL_BACKEND` setting, this | ||||
| may not return an SMTP connection. If you explicitly require an SMTP | ||||
| connection with which to send e-mail, you can explicitly request an | ||||
| SMTP connection:: | ||||
|  | ||||
|     from django.core.mail import get_connection | ||||
|     connection = get_connection('django.core.mail.backends.smtp') | ||||
|     messages = get_notification_email() | ||||
|     connection.send_messages(messages) | ||||
|  | ||||
| If your call to construct an instance of ``SMTPConnection`` required | ||||
| additional arguments, those arguments can be passed to the | ||||
| :meth:`~django.core.mail.get_connection()` call:: | ||||
|  | ||||
|     connection = get_connection('django.core.mail.backends.smtp', hostname='localhost', port=1234) | ||||
|      | ||||
| User Messages API | ||||
| ----------------- | ||||
|  | ||||
| The API for storing messages in the user ``Message`` model (via  | ||||
| ``user.message_set.create``) is now deprecated and will be removed in Django | ||||
| 1.4 according to the standard :ref:`release process <internals-release-process>`. | ||||
|  | ||||
| To upgrade your code, you need to replace any instances of:: | ||||
|  | ||||
|     user.message_set.create('a message') | ||||
|  | ||||
| with the following:: | ||||
|  | ||||
|     from django.contrib import messages | ||||
|     messages.add_message(request, messages.INFO, 'a message') | ||||
|  | ||||
| Additionally, if you make use of the method, you need to replace the  | ||||
| following:: | ||||
|  | ||||
|     for message in user.get_and_delete_messages(): | ||||
|         ... | ||||
|      | ||||
| with:: | ||||
|  | ||||
|     from django.contrib import messages | ||||
|     for message in messages.get_messages(request): | ||||
|         ... | ||||
|      | ||||
| For more information, see the full  | ||||
| :ref:`messages documentation <ref-contrib-messages>`. You should begin to  | ||||
| update your code to use the new API immediately. | ||||
|  | ||||
| What's new in Django 1.2 | ||||
| ======================== | ||||
| @@ -231,23 +310,33 @@ malicious site in their browser. A related type of attack, 'login | ||||
| CSRF', where an attacking site tricks a user's browser into logging | ||||
| into a site with someone else's credentials, is also covered. | ||||
|  | ||||
| Email Backends | ||||
| -------------- | ||||
| E-mail Backends | ||||
| --------------- | ||||
|  | ||||
| You can now :ref:`configure the way that Django sends email | ||||
| <topic-email-backends>`. Instead of using SMTP to send all email, you | ||||
| can now choose a configurable email backend to send messages. If your | ||||
| You can now :ref:`configure the way that Django sends e-mail | ||||
| <topic-email-backends>`. Instead of using SMTP to send all e-mail, you | ||||
| can now choose a configurable e-mail backend to send messages. If your | ||||
| hosting provider uses a sandbox or some other non-SMTP technique for | ||||
| sending mail, you can now construct an email backend that will allow | ||||
| sending mail, you can now construct an e-mail backend that will allow | ||||
| Django's standard :ref:`mail sending methods<topics-email>` to use | ||||
| those facilities. | ||||
|  | ||||
| This also makes it easier to debug mail sending - Django ships with | ||||
| backend implementations that allow you to send email to a | ||||
| backend implementations that allow you to send e-mail to a | ||||
| :ref:`file<topic-email-file-backend>`, to the | ||||
| :ref:`console<topic-email-console-backend>`, or to | ||||
| :ref:`memory<topic-email-memory-backend>` - you can even configure all | ||||
| email to be :ref:`thrown away<topic-email-console-backend>`. | ||||
| e-mail to be :ref:`thrown away<topic-email-dummy-backend>`. | ||||
|  | ||||
| Messages Framework | ||||
| ------------------ | ||||
|  | ||||
| Django now includes a robust and configurable :ref:`messages framework | ||||
| <ref-contrib-messages>` with built-in support for cookie- and session-based | ||||
| messaging, for both anonymous and authenticated clients. The messages framework | ||||
| replaces the deprecated user message API and allows you to temporarily store | ||||
| messages in one request and retrieve them for display in a subsequent request | ||||
| (usually the next one). | ||||
|  | ||||
| Support for multiple databases | ||||
| ------------------------------ | ||||
|   | ||||
| @@ -23,6 +23,9 @@ The auth system consists of: | ||||
|       user. | ||||
|     * Messages: A simple way to queue messages for given users. | ||||
|  | ||||
| .. deprecated:: 1.2 | ||||
|    The Messages component of the auth system will be removed in Django 1.4. | ||||
|   | ||||
| Installation | ||||
| ============ | ||||
|  | ||||
| @@ -1289,6 +1292,11 @@ messages. | ||||
| Messages | ||||
| ======== | ||||
|  | ||||
| .. deprecated:: 1.2 | ||||
|    This functionality will be removed in Django 1.4.  You should use the | ||||
|    :ref:`messages framework <ref-contrib-messages>` for all new projects and | ||||
|    begin to update your existing code immediately. | ||||
|  | ||||
| The message system is a lightweight way to queue messages for given users. | ||||
|  | ||||
| A message is associated with a :class:`~django.contrib.auth.models.User`. | ||||
| @@ -1334,13 +1342,16 @@ logged-in user and his/her messages are made available in the | ||||
|     </ul> | ||||
|     {% endif %} | ||||
|  | ||||
| Note that :class:`~django.template.context.RequestContext` calls | ||||
| :meth:`~django.contrib.auth.models.User.get_and_delete_messages` behind the | ||||
| scenes, so any messages will be deleted even if you don't display them. | ||||
| .. versionchanged:: 1.2 | ||||
|    The ``messages`` template variable uses a backwards compatible method in the | ||||
|    :ref:`messages framework <ref-contrib-messages>` to retrieve messages from | ||||
|    both the user ``Message`` model and from the new framework.  Unlike in | ||||
|    previous revisions, the messages will not be erased unless they are actually | ||||
|    displayed. | ||||
|  | ||||
| Finally, note that this messages framework only works with users in the user | ||||
| database. To send messages to anonymous users, use the | ||||
| :ref:`session framework <topics-http-sessions>`. | ||||
| :ref:`messages framework <ref-contrib-messages>`. | ||||
|  | ||||
| .. _authentication-backends: | ||||
|  | ||||
|   | ||||
| @@ -10,7 +10,7 @@ Sending e-mail | ||||
| Although Python makes sending e-mail relatively easy via the `smtplib | ||||
| library`_, Django provides a couple of light wrappers over it. These wrappers | ||||
| are provided to make sending e-mail extra quick, to make it easy to test | ||||
| email sending during development, and to provide support for platforms that | ||||
| e-mail sending during development, and to provide support for platforms that | ||||
| can't use SMTP. | ||||
|  | ||||
| The code lives in the ``django.core.mail`` module. | ||||
| @@ -64,7 +64,7 @@ are required. | ||||
|     * ``auth_password``: The optional password to use to authenticate to the | ||||
|       SMTP server. If this isn't provided, Django will use the value of the | ||||
|       ``EMAIL_HOST_PASSWORD`` setting. | ||||
|     * ``connection``: The optional email backend to use to send the mail. | ||||
|     * ``connection``: The optional e-mail backend to use to send the mail. | ||||
|       If unspecified, an instance of the default backend will be used. | ||||
|       See the documentation on :ref:`E-mail backends <topic-email-backends>` | ||||
|       for more details. | ||||
| @@ -215,8 +215,8 @@ message itself. The :ref:`e-mail backend <topic-email-backends>` is then | ||||
| responsible for sending the e-mail. | ||||
|  | ||||
| For convenience, :class:`~django.core.mail.EmailMessage` provides a simple | ||||
| ``send()`` method for sending a single email. If you need to send multiple | ||||
| messages, the email backend API :ref:`provides an alternative | ||||
| ``send()`` method for sending a single e-mail. If you need to send multiple | ||||
| messages, the e-mail backend API :ref:`provides an alternative | ||||
| <topics-sending-multiple-emails>`. | ||||
|  | ||||
| EmailMessage Objects | ||||
| @@ -264,7 +264,7 @@ For example:: | ||||
| The class has the following methods: | ||||
|  | ||||
|     * ``send(fail_silently=False)`` sends the message. If a connection was | ||||
|       specified when the email was constructed, that connection will be used. | ||||
|       specified when the e-mail was constructed, that connection will be used. | ||||
|       Otherwise, an instance of the default backend will be instantiated and | ||||
|       used. If the keyword argument ``fail_silently`` is ``True``, exceptions | ||||
|       raised while sending the message will be quashed. | ||||
| @@ -358,9 +358,9 @@ The actual sending of an e-mail is handled by the e-mail backend. | ||||
|  | ||||
| The e-mail backend class has the following methods: | ||||
|  | ||||
|     * ``open()`` instantiates an long-lived email-sending connection. | ||||
|     * ``open()`` instantiates an long-lived e-mail-sending connection. | ||||
|  | ||||
|     * ``close()`` closes the current email-sending connection. | ||||
|     * ``close()`` closes the current e-mail-sending connection. | ||||
|  | ||||
|     * ``send_messages(email_messages)`` sends a list of | ||||
|       :class:`~django.core.mail.EmailMessage` objects. If the connection is | ||||
| @@ -379,11 +379,11 @@ instance of the e-mail backend that you can use. | ||||
| .. function:: get_connection(backend=None, fail_silently=False, *args, **kwargs) | ||||
|  | ||||
| By default, a call to ``get_connection()`` will return an instance of the | ||||
| email backend specified in :setting:`EMAIL_BACKEND`. If you specify the | ||||
| e-mail backend specified in :setting:`EMAIL_BACKEND`. If you specify the | ||||
| ``backend`` argument, an instance of that backend will be instantiated. | ||||
|  | ||||
| The ``fail_silently`` argument controls how the backend should handle errors. | ||||
| If ``fail_silently`` is True, exceptions during the email sending process | ||||
| If ``fail_silently`` is True, exceptions during the e-mail sending process | ||||
| will be silently ignored. | ||||
|  | ||||
| All other arguments are passed directly to the constructor of the | ||||
| @@ -391,8 +391,8 @@ e-mail backend. | ||||
|  | ||||
| Django ships with several e-mail sending backends. With the exception of the | ||||
| SMTP backend (which is the default), these backends are only useful during | ||||
| testing and development. If you have special email sending requirements, you | ||||
| can :ref:`write your own email backend <topic-custom-email-backend>`. | ||||
| testing and development. If you have special e-mail sending requirements, you | ||||
| can :ref:`write your own e-mail backend <topic-custom-email-backend>`. | ||||
|  | ||||
| .. _topic-email-smtp-backend: | ||||
|  | ||||
| @@ -414,8 +414,8 @@ want to specify it explicitly, put the following in your settings:: | ||||
|  | ||||
|     Prior to version 1.2, Django provided a | ||||
|     :class:`~django.core.mail.SMTPConnection` class. This class provided a way | ||||
|     to directly control the use of SMTP to send email. This class has been | ||||
|     deprecated in favor of the generic email backend API. | ||||
|     to directly control the use of SMTP to send e-mail. This class has been | ||||
|     deprecated in favor of the generic e-mail backend API. | ||||
|  | ||||
|     For backwards compatibility :class:`~django.core.mail.SMTPConnection` is | ||||
|     still available in ``django.core.mail`` as an alias for the SMTP backend. | ||||
| @@ -508,15 +508,15 @@ implementation. | ||||
|  | ||||
| .. _topics-sending-multiple-emails: | ||||
|  | ||||
| Sending multiple emails | ||||
| ----------------------- | ||||
| Sending multiple e-mails | ||||
| ------------------------ | ||||
|  | ||||
| Establishing and closing an SMTP connection (or any other network connection, | ||||
| for that matter) is an expensive process. If you have a lot of emails to send, | ||||
| for that matter) is an expensive process. If you have a lot of e-mails to send, | ||||
| it makes sense to reuse an SMTP connection, rather than creating and | ||||
| destroying a connection every time you want to send an email. | ||||
| destroying a connection every time you want to send an e-mail. | ||||
|  | ||||
| There are two ways you tell an email backend to reuse a connection. | ||||
| There are two ways you tell an e-mail backend to reuse a connection. | ||||
|  | ||||
| Firstly, you can use the ``send_messages()`` method. ``send_messages()`` takes | ||||
| a list of :class:`~django.core.mail.EmailMessage` instances (or subclasses), | ||||
| @@ -524,11 +524,11 @@ and sends them all using a single connection. | ||||
|  | ||||
| For example, if you have a function called ``get_notification_email()`` that | ||||
| returns a list of :class:`~django.core.mail.EmailMessage` objects representing | ||||
| some periodic e-mail you wish to send out, you could send these emails using | ||||
| some periodic e-mail you wish to send out, you could send these e-mails using | ||||
| a single call to send_messages:: | ||||
|  | ||||
|     from django.core import mail | ||||
|     connection = mail.get_connection()   # Use default email connection | ||||
|     connection = mail.get_connection()   # Use default e-mail connection | ||||
|     messages = get_notification_email() | ||||
|     connection.send_messages(messages) | ||||
|  | ||||
| @@ -536,7 +536,7 @@ In this example, the call to ``send_messages()`` opens a connection on the | ||||
| backend, sends the list of messages, and then closes the connection again. | ||||
|  | ||||
| The second approach is to use the ``open()`` and ``close()`` methods on the | ||||
| email backend to manually control the connection. ``send_messages()`` will not | ||||
| e-mail backend to manually control the connection. ``send_messages()`` will not | ||||
| manually open or close the connection if it is already open, so if you | ||||
| manually open the connection, you can control when it is closed. For example:: | ||||
|  | ||||
| @@ -546,10 +546,10 @@ manually open the connection, you can control when it is closed. For example:: | ||||
|     # Manually open the connection | ||||
|     connection.open() | ||||
|  | ||||
|     # Construct an email message that uses the connection | ||||
|     # Construct an e-mail message that uses the connection | ||||
|     email1 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com', | ||||
|                               ['to1@example.com'], connection=connection) | ||||
|     email1.send() # Send the email | ||||
|     email1.send() # Send the e-mail | ||||
|  | ||||
|     # Construct two more messages | ||||
|     email2 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com', | ||||
| @@ -557,7 +557,7 @@ manually open the connection, you can control when it is closed. For example:: | ||||
|     email3 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com', | ||||
|                               ['to3@example.com']) | ||||
|  | ||||
|     # Send the two emails in a single call - | ||||
|     # Send the two e-mails in a single call - | ||||
|     connection.send_messages([email2, email3]) | ||||
|     # The connection was already open so send_messages() doesn't close it. | ||||
|     # We need to manually close the connection. | ||||
| @@ -574,10 +574,10 @@ people under the right conditions, and that those e-mails will contain the | ||||
| correct content. | ||||
|  | ||||
| The easiest way to test your project's use of e-mail is to use the ``console`` | ||||
| email backend. This backend redirects all email to stdout, allowing you to | ||||
| e-mail backend. This backend redirects all e-mail to stdout, allowing you to | ||||
| inspect the content of mail. | ||||
|  | ||||
| The ``file`` email backend can also be useful during development -- this backend | ||||
| The ``file`` e-mail backend can also be useful during development -- this backend | ||||
| dumps the contents of every SMTP connection to a file that can be inspected | ||||
| at your leisure. | ||||
|  | ||||
| @@ -604,7 +604,7 @@ SMTPConnection | ||||
|  | ||||
| .. deprecated:: 1.2 | ||||
|  | ||||
| The ``SMTPConnection`` class has been deprecated in favor of the generic email | ||||
| The ``SMTPConnection`` class has been deprecated in favor of the generic e-mail | ||||
| backend API. | ||||
|  | ||||
| For backwards compatibility ``SMTPConnection`` is still available in | ||||
|   | ||||
| @@ -211,6 +211,14 @@ True | ||||
| >>> Article.objects.get(id__exact=8) == Article.objects.get(id__exact=7) | ||||
| False | ||||
|  | ||||
| # You can use 'in' to test for membership... | ||||
| >>> a8 in Article.objects.all() | ||||
| True | ||||
|  | ||||
| # ... but there will often be more efficient ways if that is all you need: | ||||
| >>> Article.objects.filter(id=a8.id).exists() | ||||
| True | ||||
|  | ||||
| # dates() returns a list of available dates of the given scope for the given field. | ||||
| >>> Article.objects.dates('pub_date', 'year') | ||||
| [datetime.datetime(2005, 1, 1, 0, 0)] | ||||
|   | ||||
| @@ -18,7 +18,7 @@ class Callproc(unittest.TestCase): | ||||
|             return True | ||||
|         else: | ||||
|             return True | ||||
|  | ||||
|              | ||||
| class LongString(unittest.TestCase): | ||||
|  | ||||
|     def test_long_string(self): | ||||
|   | ||||
| @@ -81,6 +81,11 @@ class GenericAdminViewTest(TestCase): | ||||
|         inline_formset = generic_inlineformset_factory(Media, | ||||
|             exclude=('url',)) | ||||
|  | ||||
|         # Regression test for #12340. | ||||
|         e = Episode.objects.get(name='This Week in Django') | ||||
|         formset = inline_formset(instance=e) | ||||
|         self.failUnless(formset.get_queryset().ordered) | ||||
|  | ||||
| class GenericInlineAdminParametersTest(TestCase): | ||||
|     fixtures = ['users.xml'] | ||||
|  | ||||
| @@ -139,4 +144,4 @@ class GenericInlineAdminParametersTest(TestCase): | ||||
|         e = self._create_object(EpisodeExclude) | ||||
|         response = self.client.get('/generic_inline_admin/admin/generic_inline_admin/episodeexclude/%s/' % e.pk) | ||||
|         formset = response.context['inline_admin_formsets'][0].formset | ||||
|         self.failIf('url' in formset.forms[0], 'The formset has excluded "url" field.') | ||||
|         self.failIf('url' in formset.forms[0], 'The formset has excluded "url" field.') | ||||
|   | ||||
| @@ -28,6 +28,7 @@ ALWAYS_INSTALLED_APPS = [ | ||||
|     'django.contrib.flatpages', | ||||
|     'django.contrib.redirects', | ||||
|     'django.contrib.sessions', | ||||
|     'django.contrib.messages', | ||||
|     'django.contrib.comments', | ||||
|     'django.contrib.admin', | ||||
| ] | ||||
| @@ -106,6 +107,7 @@ def django_tests(verbosity, interactive, test_labels): | ||||
|     settings.MIDDLEWARE_CLASSES = ( | ||||
|         'django.contrib.sessions.middleware.SessionMiddleware', | ||||
|         'django.contrib.auth.middleware.AuthenticationMiddleware', | ||||
|         'django.contrib.messages.middleware.MessageMiddleware', | ||||
|         'django.middleware.common.CommonMiddleware', | ||||
|     ) | ||||
|     settings.SITE_ID = 1 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user