mirror of
https://github.com/django/django.git
synced 2025-07-04 17:59:13 +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:
parent
2e900d47f2
commit
bee835fa44
3
AUTHORS
3
AUTHORS
@ -60,6 +60,7 @@ answer newbie questions, and generally made Django that much better:
|
|||||||
Ned Batchelder <http://www.nedbatchelder.com/>
|
Ned Batchelder <http://www.nedbatchelder.com/>
|
||||||
batiste@dosimple.ch
|
batiste@dosimple.ch
|
||||||
Batman
|
Batman
|
||||||
|
Chris Beaven <http://smileychris.tactful.co.nz/>
|
||||||
Brian Beck <http://blog.brianbeck.com/>
|
Brian Beck <http://blog.brianbeck.com/>
|
||||||
Shannon -jj Behrens <http://jjinux.blogspot.com/>
|
Shannon -jj Behrens <http://jjinux.blogspot.com/>
|
||||||
Esdras Beleza <linux@esdrasbeleza.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/>
|
Jason McBrayer <http://www.carcosa.net/jason/>
|
||||||
Kevin McConnell <kevin.mcconnell@gmail.com>
|
Kevin McConnell <kevin.mcconnell@gmail.com>
|
||||||
mccutchen@gmail.com
|
mccutchen@gmail.com
|
||||||
|
Tobias McNulty <http://www.caktusgroup.com/blog>
|
||||||
Christian Metts
|
Christian Metts
|
||||||
michael.mcewan@gmail.com
|
michael.mcewan@gmail.com
|
||||||
michal@plovarna.cz
|
michal@plovarna.cz
|
||||||
@ -391,7 +393,6 @@ answer newbie questions, and generally made Django that much better:
|
|||||||
Jozko Skrablin <jozko.skrablin@gmail.com>
|
Jozko Skrablin <jozko.skrablin@gmail.com>
|
||||||
Ben Slavin <benjamin.slavin@gmail.com>
|
Ben Slavin <benjamin.slavin@gmail.com>
|
||||||
sloonz <simon.lipp@insa-lyon.fr>
|
sloonz <simon.lipp@insa-lyon.fr>
|
||||||
SmileyChris <smileychris@gmail.com>
|
|
||||||
Warren Smith <warren@wandrsmith.net>
|
Warren Smith <warren@wandrsmith.net>
|
||||||
smurf@smurf.noris.de
|
smurf@smurf.noris.de
|
||||||
Vsevolod Solovyov
|
Vsevolod Solovyov
|
||||||
|
1
TODO
1
TODO
@ -7,6 +7,7 @@ Required for v1.2
|
|||||||
* Finalize the sql.Query internals
|
* Finalize the sql.Query internals
|
||||||
* Clean up the use of db.backend.query_class()
|
* Clean up the use of db.backend.query_class()
|
||||||
* Verify it still works with GeoDjango
|
* Verify it still works with GeoDjango
|
||||||
|
* Modify the admin interface to support multiple databases (doh).
|
||||||
|
|
||||||
Optional for v1.2
|
Optional for v1.2
|
||||||
~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~
|
||||||
|
@ -127,7 +127,7 @@ class UserSettingsHolder(object):
|
|||||||
return getattr(self.default_settings, name)
|
return getattr(self.default_settings, name)
|
||||||
|
|
||||||
def __dir__(self):
|
def __dir__(self):
|
||||||
return dir(self) + dir(self.default_settings)
|
return self.__dict__.keys() + dir(self.default_settings)
|
||||||
|
|
||||||
# For Python < 2.6:
|
# For Python < 2.6:
|
||||||
__members__ = property(lambda self: self.__dir__())
|
__members__ = property(lambda self: self.__dir__())
|
||||||
|
@ -175,6 +175,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
|
|||||||
'django.core.context_processors.i18n',
|
'django.core.context_processors.i18n',
|
||||||
'django.core.context_processors.media',
|
'django.core.context_processors.media',
|
||||||
# 'django.core.context_processors.request',
|
# 'django.core.context_processors.request',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
)
|
)
|
||||||
|
|
||||||
# Output to use in template system for invalid (e.g. misspelled) variables.
|
# Output to use in template system for invalid (e.g. misspelled) variables.
|
||||||
@ -311,6 +312,7 @@ MIDDLEWARE_CLASSES = (
|
|||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
# 'django.middleware.http.ConditionalGetMiddleware',
|
# 'django.middleware.http.ConditionalGetMiddleware',
|
||||||
# 'django.middleware.gzip.GZipMiddleware',
|
# 'django.middleware.gzip.GZipMiddleware',
|
||||||
)
|
)
|
||||||
@ -396,6 +398,16 @@ CSRF_FAILURE_VIEW = 'django.views.csrf.csrf_failure'
|
|||||||
CSRF_COOKIE_NAME = 'csrftoken'
|
CSRF_COOKIE_NAME = 'csrftoken'
|
||||||
CSRF_COOKIE_DOMAIN = None
|
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 #
|
# TESTING #
|
||||||
###########
|
###########
|
||||||
|
@ -66,6 +66,7 @@ MIDDLEWARE_CLASSES = (
|
|||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
)
|
)
|
||||||
|
|
||||||
ROOT_URLCONF = '{{ project_name }}.urls'
|
ROOT_URLCONF = '{{ project_name }}.urls'
|
||||||
@ -81,4 +82,5 @@ INSTALLED_APPS = (
|
|||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.sites',
|
'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 widgets
|
||||||
from django.contrib.admin import helpers
|
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.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.views.decorators.csrf import csrf_protect
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.db import models, transaction
|
from django.db import models, transaction
|
||||||
@ -541,9 +542,9 @@ class ModelAdmin(BaseModelAdmin):
|
|||||||
def message_user(self, request, message):
|
def message_user(self, request, message):
|
||||||
"""
|
"""
|
||||||
Send a message to the user. The default implementation
|
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):
|
def save_form(self, request, form, change):
|
||||||
"""
|
"""
|
||||||
|
@ -452,7 +452,7 @@ class AdminSite(object):
|
|||||||
import warnings
|
import warnings
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"AdminSite.root() is deprecated; use include(admin.site.urls) instead.",
|
"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.conf import settings
|
||||||
from django.utils.importlib import import_module
|
from django.utils.importlib import import_module
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.contrib import messages
|
||||||
|
|
||||||
|
|
||||||
def template_validator(request):
|
def template_validator(request):
|
||||||
@ -23,7 +24,7 @@ def template_validator(request):
|
|||||||
form = TemplateValidatorForm(settings_modules, site_list,
|
form = TemplateValidatorForm(settings_modules, site_list,
|
||||||
data=request.POST)
|
data=request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
request.user.message_set.create(message='The template is valid.')
|
messages.info(request, 'The template is valid.')
|
||||||
else:
|
else:
|
||||||
form = TemplateValidatorForm(settings_modules, site_list)
|
form = TemplateValidatorForm(settings_modules, site_list)
|
||||||
return render_to_response('admin/template_validator.html', {
|
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 import admin
|
||||||
from django.contrib.auth.forms import UserCreationForm, UserChangeForm, AdminPasswordChangeForm
|
from django.contrib.auth.forms import UserCreationForm, UserChangeForm, AdminPasswordChangeForm
|
||||||
from django.contrib.auth.models import User, Group
|
from django.contrib.auth.models import User, Group
|
||||||
|
from django.contrib import messages
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.http import HttpResponseRedirect, Http404
|
from django.http import HttpResponseRedirect, Http404
|
||||||
from django.shortcuts import render_to_response, get_object_or_404
|
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}
|
msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': 'user', 'obj': new_user}
|
||||||
self.log_addition(request, new_user)
|
self.log_addition(request, new_user)
|
||||||
if "_addanother" in request.POST:
|
if "_addanother" in request.POST:
|
||||||
request.user.message_set.create(message=msg)
|
messages.success(request, msg)
|
||||||
return HttpResponseRedirect(request.path)
|
return HttpResponseRedirect(request.path)
|
||||||
elif '_popup' in request.REQUEST:
|
elif '_popup' in request.REQUEST:
|
||||||
return self.response_add(request, new_user)
|
return self.response_add(request, new_user)
|
||||||
else:
|
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)
|
return HttpResponseRedirect('../%s/' % new_user.id)
|
||||||
else:
|
else:
|
||||||
form = self.add_form()
|
form = self.add_form()
|
||||||
@ -104,7 +106,7 @@ class UserAdmin(admin.ModelAdmin):
|
|||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
new_user = form.save()
|
new_user = form.save()
|
||||||
msg = ugettext('Password changed successfully.')
|
msg = ugettext('Password changed successfully.')
|
||||||
request.user.message_set.create(message=msg)
|
messages.success(request, msg)
|
||||||
return HttpResponseRedirect('..')
|
return HttpResponseRedirect('..')
|
||||||
else:
|
else:
|
||||||
form = self.change_password_form(user)
|
form = self.change_password_form(user)
|
||||||
|
@ -288,6 +288,14 @@ class User(models.Model):
|
|||||||
raise SiteProfileNotAvailable
|
raise SiteProfileNotAvailable
|
||||||
return self._profile_cache
|
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):
|
class Message(models.Model):
|
||||||
"""
|
"""
|
||||||
The message system is a lightweight way to queue messages for given
|
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
|
actions. For example, "The poll Foo was created successfully." is a
|
||||||
message.
|
message.
|
||||||
"""
|
"""
|
||||||
user = models.ForeignKey(User)
|
user = models.ForeignKey(User, related_name='_message_set')
|
||||||
message = models.TextField(_('message'))
|
message = models.TextField(_('message'))
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
|
@ -317,10 +317,13 @@ class BaseGenericInlineFormSet(BaseModelFormSet):
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
if self.instance is None or self.instance.pk is None:
|
if self.instance is None or self.instance.pk is None:
|
||||||
return self.model._default_manager.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_field.name: ContentType.objects.get_for_model(self.instance),
|
||||||
self.ct_fk_field.name: self.instance.pk,
|
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):
|
def save_new(self, form, commit=True):
|
||||||
# Avoid a circular import.
|
# 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.conf import settings
|
||||||
from django.middleware.csrf import get_token
|
from django.middleware.csrf import get_token
|
||||||
from django.utils.functional import lazy, memoize, SimpleLazyObject
|
from django.utils.functional import lazy, memoize, SimpleLazyObject
|
||||||
|
from django.contrib import messages
|
||||||
|
|
||||||
def auth(request):
|
def auth(request):
|
||||||
"""
|
"""
|
||||||
@ -37,8 +38,8 @@ def auth(request):
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
'user': SimpleLazyObject(get_user),
|
'user': SimpleLazyObject(get_user),
|
||||||
'messages': lazy(memoize(lambda: get_user().get_and_delete_messages(), {}, 0), list)(),
|
'messages': messages.get_messages(request),
|
||||||
'perms': lazy(lambda: PermWrapper(get_user()), PermWrapper)(),
|
'perms': lazy(lambda: PermWrapper(get_user()), PermWrapper)(),
|
||||||
}
|
}
|
||||||
|
|
||||||
def csrf(request):
|
def csrf(request):
|
||||||
|
@ -105,6 +105,6 @@ class SMTPConnection(_SMTPConnection):
|
|||||||
import warnings
|
import warnings
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
'mail.SMTPConnection is deprecated; use mail.get_connection() instead.',
|
'mail.SMTPConnection is deprecated; use mail.get_connection() instead.',
|
||||||
DeprecationWarning
|
PendingDeprecationWarning
|
||||||
)
|
)
|
||||||
super(SMTPConnection, self).__init__(*args, **kwds)
|
super(SMTPConnection, self).__init__(*args, **kwds)
|
||||||
|
@ -110,6 +110,36 @@ class QuerySet(object):
|
|||||||
return False
|
return False
|
||||||
return True
|
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):
|
def __getitem__(self, k):
|
||||||
"""
|
"""
|
||||||
Retrieves an item or slice from the set of results.
|
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.db.models.sql.where import WhereNode, Constraint, EverythingNode, AND, OR
|
||||||
from django.core.exceptions import FieldError
|
from django.core.exceptions import FieldError
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['Query']
|
__all__ = ['Query']
|
||||||
|
|
||||||
class Query(object):
|
class Query(object):
|
||||||
|
@ -6,6 +6,7 @@ from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
|
|||||||
from django.utils.translation import ugettext
|
from django.utils.translation import ugettext
|
||||||
from django.contrib.auth.views import redirect_to_login
|
from django.contrib.auth.views import redirect_to_login
|
||||||
from django.views.generic import GenericViewError
|
from django.views.generic import GenericViewError
|
||||||
|
from django.contrib import messages
|
||||||
|
|
||||||
|
|
||||||
def apply_extra_context(extra_context, context):
|
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)
|
form = form_class(request.POST, request.FILES)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
new_object = form.save()
|
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)
|
return redirect(post_save_redirect, new_object)
|
||||||
else:
|
else:
|
||||||
form = form_class()
|
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)
|
form = form_class(request.POST, request.FILES, instance=obj)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
obj = form.save()
|
obj = form.save()
|
||||||
if request.user.is_authenticated():
|
msg = ugettext("The %(verbose_name)s was updated successfully.") %\
|
||||||
request.user.message_set.create(message=ugettext("The %(verbose_name)s was updated successfully.") % {"verbose_name": model._meta.verbose_name})
|
{"verbose_name": model._meta.verbose_name}
|
||||||
|
messages.success(request, msg, fail_silently=True)
|
||||||
return redirect(post_save_redirect, obj)
|
return redirect(post_save_redirect, obj)
|
||||||
else:
|
else:
|
||||||
form = form_class(instance=obj)
|
form = form_class(instance=obj)
|
||||||
@ -194,8 +198,9 @@ def delete_object(request, model, post_delete_redirect, object_id=None,
|
|||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
obj.delete()
|
obj.delete()
|
||||||
if request.user.is_authenticated():
|
msg = ugettext("The %(verbose_name)s was deleted.") %\
|
||||||
request.user.message_set.create(message=ugettext("The %(verbose_name)s was deleted.") % {"verbose_name": model._meta.verbose_name})
|
{"verbose_name": model._meta.verbose_name}
|
||||||
|
messages.success(request, msg, fail_silently=True)
|
||||||
return HttpResponseRedirect(post_delete_redirect)
|
return HttpResponseRedirect(post_delete_redirect)
|
||||||
else:
|
else:
|
||||||
if not template_name:
|
if not template_name:
|
||||||
|
@ -171,6 +171,7 @@ Other batteries included
|
|||||||
* :ref:`Internationalization <topics-i18n>`
|
* :ref:`Internationalization <topics-i18n>`
|
||||||
* :ref:`Jython support <howto-jython>`
|
* :ref:`Jython support <howto-jython>`
|
||||||
* :ref:`"Local flavor" <ref-contrib-localflavor>`
|
* :ref:`"Local flavor" <ref-contrib-localflavor>`
|
||||||
|
* :ref:`Messages <ref-contrib-messages>`
|
||||||
* :ref:`Pagination <topics-pagination>`
|
* :ref:`Pagination <topics-pagination>`
|
||||||
* :ref:`Redirects <ref-contrib-redirects>`
|
* :ref:`Redirects <ref-contrib-redirects>`
|
||||||
* :ref:`Serialization <topics-serialization>`
|
* :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 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
|
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
|
multiple databases. In 1.4, the support functions that allow methods
|
||||||
with the old prototype to continue working will be removed.
|
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
|
* 2.0
|
||||||
* ``django.views.defaults.shortcut()``. This function has been moved
|
* ``django.views.defaults.shortcut()``. This function has been moved
|
||||||
to ``django.contrib.contenttypes.views.shortcut()`` as part of the
|
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
|
``@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.
|
page doesn't also contain internal forms that require the token.
|
||||||
|
|
||||||
|
.. _ref-csrf-upgrading-notes:
|
||||||
|
|
||||||
Upgrading notes
|
Upgrading notes
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@ those packages have.
|
|||||||
formtools/index
|
formtools/index
|
||||||
humanize
|
humanize
|
||||||
localflavor
|
localflavor
|
||||||
|
messages
|
||||||
redirects
|
redirects
|
||||||
sitemaps
|
sitemaps
|
||||||
sites
|
sites
|
||||||
@ -150,6 +151,17 @@ read the source code in django/contrib/markup/templatetags/markup.py.
|
|||||||
.. _Markdown: http://en.wikipedia.org/wiki/Markdown
|
.. _Markdown: http://en.wikipedia.org/wiki/Markdown
|
||||||
.. _ReST (ReStructured Text): http://en.wikipedia.org/wiki/ReStructuredText
|
.. _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
|
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
|
content for each user. See the :ref:`internationalization documentation
|
||||||
<topics-i18n>`.
|
<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
|
Session middleware
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
@ -896,6 +896,43 @@ Bad: ``"http://www.example.com/static"``
|
|||||||
|
|
||||||
.. setting:: MIDDLEWARE_CLASSES
|
.. 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
|
MIDDLEWARE_CLASSES
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
@ -904,10 +941,16 @@ Default::
|
|||||||
('django.middleware.common.CommonMiddleware',
|
('django.middleware.common.CommonMiddleware',
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
'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`.
|
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
|
.. setting:: MONTH_DAY_FORMAT
|
||||||
|
|
||||||
MONTH_DAY_FORMAT
|
MONTH_DAY_FORMAT
|
||||||
@ -1155,12 +1198,18 @@ Default::
|
|||||||
("django.core.context_processors.auth",
|
("django.core.context_processors.auth",
|
||||||
"django.core.context_processors.debug",
|
"django.core.context_processors.debug",
|
||||||
"django.core.context_processors.i18n",
|
"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``.
|
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
|
These callables take a request object as their argument and return a dictionary
|
||||||
of items to be merged into the context.
|
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
|
.. setting:: TEMPLATE_DEBUG
|
||||||
|
|
||||||
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.auth",
|
||||||
"django.core.context_processors.debug",
|
"django.core.context_processors.debug",
|
||||||
"django.core.context_processors.i18n",
|
"django.core.context_processors.i18n",
|
||||||
"django.core.context_processors.media")
|
"django.core.context_processors.media",
|
||||||
|
"django.contrib.messages.context_processors.messages")
|
||||||
|
|
||||||
.. versionadded:: 1.2
|
.. versionadded:: 1.2
|
||||||
In addition to these, ``RequestContext`` always uses
|
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
|
in case of accidental misconfiguration, it is deliberately hardcoded in and
|
||||||
cannot be turned off by the :setting:`TEMPLATE_CONTEXT_PROCESSORS` setting.
|
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
|
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
|
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
|
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 user (or an ``AnonymousUser`` instance, if the client isn't
|
||||||
logged in).
|
logged in).
|
||||||
|
|
||||||
* ``messages`` -- A list of messages (as strings) for the currently
|
* ``messages`` -- A list of messages (as strings) that have been set
|
||||||
logged-in user. Behind the scenes, this calls
|
via the :ref:`messages framework <ref-contrib-messages>`.
|
||||||
``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``.
|
|
||||||
|
|
||||||
* ``perms`` -- An instance of
|
* ``perms`` -- An instance of
|
||||||
``django.core.context_processors.PermWrapper``, representing the
|
``django.core.context_processors.PermWrapper``, representing the
|
||||||
permissions that the currently logged-in user has.
|
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
|
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;
|
:class:`~django.http.HttpRequest`. Note that this processor is not enabled by default;
|
||||||
you'll have to activate it.
|
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
|
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:
|
changes that developers must be aware of:
|
||||||
|
|
||||||
* ``CsrfResponseMiddleware`` and ``CsrfMiddleware`` have been deprecated, and
|
* ``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.
|
should be inserted into forms.
|
||||||
|
|
||||||
* All contrib apps use a ``csrf_protect`` decorator to protect the view. This
|
* 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
|
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
|
have used custom templates for contrib views, you MUST READ THE :ref:`UPGRADE
|
||||||
INSTRUCTIONS to fix those templates.
|
INSTRUCTIONS <ref-csrf-upgrading-notes>` to fix those templates.
|
||||||
|
|
||||||
* ``CsrfViewMiddleware`` is included in :setting:`MIDDLEWARE_CLASSES` by
|
* ``CsrfViewMiddleware`` is included in :setting:`MIDDLEWARE_CLASSES` by
|
||||||
default. This turns on CSRF protection by default, so that views that accept
|
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
|
* All of the CSRF has moved from contrib to core (with backwards compatible
|
||||||
imports in the old locations, which are deprecated).
|
imports in the old locations, which are deprecated).
|
||||||
|
|
||||||
LazyObject
|
``LazyObject``
|
||||||
----------
|
--------------
|
||||||
|
|
||||||
``LazyObject`` is an undocumented utility class used for lazily wrapping other
|
``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
|
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
|
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
|
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
|
CSRF', where an attacking site tricks a user's browser into logging
|
||||||
into a site with someone else's credentials, is also covered.
|
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
|
You can now :ref:`configure the way that Django sends e-mail
|
||||||
<topic-email-backends>`. Instead of using SMTP to send all email, you
|
<topic-email-backends>`. Instead of using SMTP to send all e-mail, you
|
||||||
can now choose a configurable email backend to send messages. If your
|
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
|
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
|
Django's standard :ref:`mail sending methods<topics-email>` to use
|
||||||
those facilities.
|
those facilities.
|
||||||
|
|
||||||
This also makes it easier to debug mail sending - Django ships with
|
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:`file<topic-email-file-backend>`, to the
|
||||||
:ref:`console<topic-email-console-backend>`, or to
|
:ref:`console<topic-email-console-backend>`, or to
|
||||||
:ref:`memory<topic-email-memory-backend>` - you can even configure all
|
: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
|
Support for multiple databases
|
||||||
------------------------------
|
------------------------------
|
||||||
|
@ -23,6 +23,9 @@ The auth system consists of:
|
|||||||
user.
|
user.
|
||||||
* Messages: A simple way to queue messages for given users.
|
* 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
|
Installation
|
||||||
============
|
============
|
||||||
|
|
||||||
@ -1289,6 +1292,11 @@ messages.
|
|||||||
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.
|
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`.
|
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>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
Note that :class:`~django.template.context.RequestContext` calls
|
.. versionchanged:: 1.2
|
||||||
:meth:`~django.contrib.auth.models.User.get_and_delete_messages` behind the
|
The ``messages`` template variable uses a backwards compatible method in the
|
||||||
scenes, so any messages will be deleted even if you don't display them.
|
: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
|
Finally, note that this messages framework only works with users in the user
|
||||||
database. To send messages to anonymous users, use the
|
database. To send messages to anonymous users, use the
|
||||||
:ref:`session framework <topics-http-sessions>`.
|
:ref:`messages framework <ref-contrib-messages>`.
|
||||||
|
|
||||||
.. _authentication-backends:
|
.. _authentication-backends:
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ Sending e-mail
|
|||||||
Although Python makes sending e-mail relatively easy via the `smtplib
|
Although Python makes sending e-mail relatively easy via the `smtplib
|
||||||
library`_, Django provides a couple of light wrappers over it. These wrappers
|
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
|
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.
|
can't use SMTP.
|
||||||
|
|
||||||
The code lives in the ``django.core.mail`` module.
|
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
|
* ``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
|
SMTP server. If this isn't provided, Django will use the value of the
|
||||||
``EMAIL_HOST_PASSWORD`` setting.
|
``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.
|
If unspecified, an instance of the default backend will be used.
|
||||||
See the documentation on :ref:`E-mail backends <topic-email-backends>`
|
See the documentation on :ref:`E-mail backends <topic-email-backends>`
|
||||||
for more details.
|
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.
|
responsible for sending the e-mail.
|
||||||
|
|
||||||
For convenience, :class:`~django.core.mail.EmailMessage` provides a simple
|
For convenience, :class:`~django.core.mail.EmailMessage` provides a simple
|
||||||
``send()`` method for sending a single email. If you need to send multiple
|
``send()`` method for sending a single e-mail. If you need to send multiple
|
||||||
messages, the email backend API :ref:`provides an alternative
|
messages, the e-mail backend API :ref:`provides an alternative
|
||||||
<topics-sending-multiple-emails>`.
|
<topics-sending-multiple-emails>`.
|
||||||
|
|
||||||
EmailMessage Objects
|
EmailMessage Objects
|
||||||
@ -264,7 +264,7 @@ For example::
|
|||||||
The class has the following methods:
|
The class has the following methods:
|
||||||
|
|
||||||
* ``send(fail_silently=False)`` sends the message. If a connection was
|
* ``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
|
Otherwise, an instance of the default backend will be instantiated and
|
||||||
used. If the keyword argument ``fail_silently`` is ``True``, exceptions
|
used. If the keyword argument ``fail_silently`` is ``True``, exceptions
|
||||||
raised while sending the message will be quashed.
|
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:
|
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
|
* ``send_messages(email_messages)`` sends a list of
|
||||||
:class:`~django.core.mail.EmailMessage` objects. If the connection is
|
: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)
|
.. function:: get_connection(backend=None, fail_silently=False, *args, **kwargs)
|
||||||
|
|
||||||
By default, a call to ``get_connection()`` will return an instance of the
|
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.
|
``backend`` argument, an instance of that backend will be instantiated.
|
||||||
|
|
||||||
The ``fail_silently`` argument controls how the backend should handle errors.
|
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.
|
will be silently ignored.
|
||||||
|
|
||||||
All other arguments are passed directly to the constructor of the
|
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
|
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
|
SMTP backend (which is the default), these backends are only useful during
|
||||||
testing and development. If you have special email sending requirements, you
|
testing and development. If you have special e-mail sending requirements, you
|
||||||
can :ref:`write your own email backend <topic-custom-email-backend>`.
|
can :ref:`write your own e-mail backend <topic-custom-email-backend>`.
|
||||||
|
|
||||||
.. _topic-email-smtp-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
|
Prior to version 1.2, Django provided a
|
||||||
:class:`~django.core.mail.SMTPConnection` class. This class provided a way
|
: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
|
to directly control the use of SMTP to send e-mail. This class has been
|
||||||
deprecated in favor of the generic email backend API.
|
deprecated in favor of the generic e-mail backend API.
|
||||||
|
|
||||||
For backwards compatibility :class:`~django.core.mail.SMTPConnection` is
|
For backwards compatibility :class:`~django.core.mail.SMTPConnection` is
|
||||||
still available in ``django.core.mail`` as an alias for the SMTP backend.
|
still available in ``django.core.mail`` as an alias for the SMTP backend.
|
||||||
@ -508,15 +508,15 @@ implementation.
|
|||||||
|
|
||||||
.. _topics-sending-multiple-emails:
|
.. _topics-sending-multiple-emails:
|
||||||
|
|
||||||
Sending multiple emails
|
Sending multiple e-mails
|
||||||
-----------------------
|
------------------------
|
||||||
|
|
||||||
Establishing and closing an SMTP connection (or any other network connection,
|
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
|
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
|
Firstly, you can use the ``send_messages()`` method. ``send_messages()`` takes
|
||||||
a list of :class:`~django.core.mail.EmailMessage` instances (or subclasses),
|
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
|
For example, if you have a function called ``get_notification_email()`` that
|
||||||
returns a list of :class:`~django.core.mail.EmailMessage` objects representing
|
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::
|
a single call to send_messages::
|
||||||
|
|
||||||
from django.core import mail
|
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()
|
messages = get_notification_email()
|
||||||
connection.send_messages(messages)
|
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.
|
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
|
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 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::
|
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
|
# Manually open the connection
|
||||||
connection.open()
|
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',
|
email1 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com',
|
||||||
['to1@example.com'], connection=connection)
|
['to1@example.com'], connection=connection)
|
||||||
email1.send() # Send the email
|
email1.send() # Send the e-mail
|
||||||
|
|
||||||
# Construct two more messages
|
# Construct two more messages
|
||||||
email2 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com',
|
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',
|
email3 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com',
|
||||||
['to3@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])
|
connection.send_messages([email2, email3])
|
||||||
# The connection was already open so send_messages() doesn't close it.
|
# The connection was already open so send_messages() doesn't close it.
|
||||||
# We need to manually close the connection.
|
# 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.
|
correct content.
|
||||||
|
|
||||||
The easiest way to test your project's use of e-mail is to use the ``console``
|
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.
|
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
|
dumps the contents of every SMTP connection to a file that can be inspected
|
||||||
at your leisure.
|
at your leisure.
|
||||||
|
|
||||||
@ -604,7 +604,7 @@ SMTPConnection
|
|||||||
|
|
||||||
.. deprecated:: 1.2
|
.. 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.
|
backend API.
|
||||||
|
|
||||||
For backwards compatibility ``SMTPConnection`` is still available in
|
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)
|
>>> Article.objects.get(id__exact=8) == Article.objects.get(id__exact=7)
|
||||||
False
|
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.
|
# dates() returns a list of available dates of the given scope for the given field.
|
||||||
>>> Article.objects.dates('pub_date', 'year')
|
>>> Article.objects.dates('pub_date', 'year')
|
||||||
[datetime.datetime(2005, 1, 1, 0, 0)]
|
[datetime.datetime(2005, 1, 1, 0, 0)]
|
||||||
|
@ -18,7 +18,7 @@ class Callproc(unittest.TestCase):
|
|||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
class LongString(unittest.TestCase):
|
class LongString(unittest.TestCase):
|
||||||
|
|
||||||
def test_long_string(self):
|
def test_long_string(self):
|
||||||
|
@ -81,6 +81,11 @@ class GenericAdminViewTest(TestCase):
|
|||||||
inline_formset = generic_inlineformset_factory(Media,
|
inline_formset = generic_inlineformset_factory(Media,
|
||||||
exclude=('url',))
|
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):
|
class GenericInlineAdminParametersTest(TestCase):
|
||||||
fixtures = ['users.xml']
|
fixtures = ['users.xml']
|
||||||
|
|
||||||
@ -139,4 +144,4 @@ class GenericInlineAdminParametersTest(TestCase):
|
|||||||
e = self._create_object(EpisodeExclude)
|
e = self._create_object(EpisodeExclude)
|
||||||
response = self.client.get('/generic_inline_admin/admin/generic_inline_admin/episodeexclude/%s/' % e.pk)
|
response = self.client.get('/generic_inline_admin/admin/generic_inline_admin/episodeexclude/%s/' % e.pk)
|
||||||
formset = response.context['inline_admin_formsets'][0].formset
|
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.flatpages',
|
||||||
'django.contrib.redirects',
|
'django.contrib.redirects',
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
'django.contrib.comments',
|
'django.contrib.comments',
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
]
|
]
|
||||||
@ -106,6 +107,7 @@ def django_tests(verbosity, interactive, test_labels):
|
|||||||
settings.MIDDLEWARE_CLASSES = (
|
settings.MIDDLEWARE_CLASSES = (
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
'django.middleware.common.CommonMiddleware',
|
'django.middleware.common.CommonMiddleware',
|
||||||
)
|
)
|
||||||
settings.SITE_ID = 1
|
settings.SITE_ID = 1
|
||||||
|
Loading…
x
Reference in New Issue
Block a user