summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AUTHORS9
-rw-r--r--django/conf/global_settings.py2
-rw-r--r--django/contrib/admin/options.py15
-rw-r--r--django/contrib/admin/sites.py10
-rw-r--r--django/contrib/admin/validation.py685
-rw-r--r--django/contrib/auth/__init__.py11
-rw-r--r--django/contrib/auth/management/__init__.py2
-rw-r--r--django/contrib/auth/tests/test_auth_backends.py53
-rw-r--r--django/contrib/auth/tests/test_management.py2
-rw-r--r--django/contrib/flatpages/views.py2
-rw-r--r--django/contrib/humanize/locale/en/LC_MESSAGES/django.po36
-rw-r--r--django/contrib/humanize/templatetags/humanize.py18
-rw-r--r--django/contrib/humanize/tests.py26
-rw-r--r--django/core/cache/backends/base.py10
-rw-r--r--django/core/cache/backends/db.py15
-rw-r--r--django/core/cache/backends/dummy.py8
-rw-r--r--django/core/cache/backends/filebased.py15
-rw-r--r--django/core/cache/backends/locmem.py19
-rw-r--r--django/core/cache/backends/memcached.py22
-rw-r--r--django/core/management/commands/loaddata.py12
-rw-r--r--django/core/management/validation.py4
-rw-r--r--django/core/xheaders.py24
-rw-r--r--django/db/backends/oracle/introspection.py4
-rw-r--r--django/forms/fields.py22
-rw-r--r--django/forms/models.py37
-rw-r--r--django/middleware/cache.py11
-rw-r--r--django/middleware/csrf.py34
-rw-r--r--django/template/defaultfilters.py28
-rw-r--r--django/test/testcases.py531
-rw-r--r--django/test/utils.py21
-rw-r--r--django/utils/html.py7
-rw-r--r--django/utils/http.py2
-rw-r--r--django/utils/timesince.py7
-rw-r--r--django/utils/translation/trans_real.py29
-rw-r--r--django/views/debug.py8
-rw-r--r--django/views/decorators/csrf.py2
-rw-r--r--django/views/generic/detail.py10
-rw-r--r--django/views/generic/edit.py14
-rw-r--r--django/views/generic/list.py4
-rw-r--r--django/views/i18n.py68
-rw-r--r--docs/faq/admin.txt6
-rw-r--r--docs/howto/deployment/wsgi/uwsgi.txt1
-rw-r--r--docs/howto/static-files/index.txt16
-rw-r--r--docs/internals/deprecation.txt2
-rw-r--r--docs/intro/overview.txt4
-rw-r--r--docs/intro/tutorial01.txt2
-rw-r--r--docs/intro/tutorial02.txt16
-rw-r--r--docs/intro/tutorial03.txt5
-rw-r--r--docs/intro/tutorial04.txt2
-rw-r--r--docs/intro/tutorial05.txt4
-rw-r--r--docs/ref/contrib/contenttypes.txt2
-rwxr-xr-xdocs/ref/contrib/gis/install/create_template_postgis-1.5.sh10
-rw-r--r--docs/ref/contrib/sitemaps.txt40
-rw-r--r--docs/ref/forms/models.txt16
-rw-r--r--docs/ref/models/fields.txt23
-rw-r--r--docs/ref/models/instances.txt10
-rw-r--r--docs/ref/models/options.txt6
-rw-r--r--docs/ref/models/querysets.txt7
-rw-r--r--docs/ref/models/relations.txt8
-rw-r--r--docs/ref/request-response.txt12
-rw-r--r--docs/ref/settings.txt8
-rw-r--r--docs/ref/template-response.txt3
-rw-r--r--docs/releases/1.3-alpha-1.txt2
-rw-r--r--docs/releases/1.3.txt2
-rw-r--r--docs/releases/1.4.txt8
-rw-r--r--docs/releases/1.6.txt42
-rw-r--r--docs/topics/auth/customizing.txt6
-rw-r--r--docs/topics/cache.txt25
-rw-r--r--docs/topics/class-based-views/generic-display.txt6
-rw-r--r--docs/topics/class-based-views/generic-editing.txt1
-rw-r--r--docs/topics/class-based-views/mixins.txt8
-rw-r--r--docs/topics/db/aggregation.txt40
-rw-r--r--docs/topics/db/managers.txt22
-rw-r--r--docs/topics/db/models.txt50
-rw-r--r--docs/topics/db/queries.txt2
-rw-r--r--docs/topics/files.txt2
-rw-r--r--docs/topics/forms/modelforms.txt36
-rw-r--r--docs/topics/http/file-uploads.txt2
-rw-r--r--docs/topics/http/sessions.txt18
-rw-r--r--docs/topics/http/urls.txt31
-rw-r--r--docs/topics/http/views.txt6
-rw-r--r--docs/topics/i18n/translation.txt29
-rw-r--r--docs/topics/python3.txt4
-rw-r--r--docs/topics/testing/overview.txt235
-rw-r--r--tests/admin_validation/tests.py80
-rw-r--r--tests/base/models.py4
-rw-r--r--tests/cache/tests.py36
-rw-r--r--tests/csrf_tests/tests.py51
-rw-r--r--tests/defaultfilters/tests.py68
-rw-r--r--tests/fixtures/tests.py23
-rw-r--r--tests/fixtures_model_package/tests.py6
-rw-r--r--tests/fixtures_regress/tests.py16
-rw-r--r--tests/forms_tests/tests/test_extra.py8
-rw-r--r--tests/forms_tests/tests/test_fields.py9
-rw-r--r--tests/generic_views/test_base.py33
-rw-r--r--tests/generic_views/views.py16
-rw-r--r--tests/i18n/__init__.py17
-rw-r--r--tests/i18n/contenttypes/tests.py4
-rw-r--r--tests/i18n/tests.py97
-rw-r--r--tests/invalid_models/invalid_models/models.py4
-rw-r--r--tests/middleware/tests.py4
-rw-r--r--tests/model_forms_regress/tests.py35
-rw-r--r--tests/model_formsets_regress/tests.py7
-rw-r--r--tests/model_validation/__init__.py (renamed from tests/special_headers/__init__.py)0
-rw-r--r--tests/model_validation/models.py27
-rw-r--r--tests/model_validation/tests.py13
-rw-r--r--tests/modeladmin/tests.py239
-rw-r--r--tests/requests/tests.py14
-rw-r--r--tests/special_headers/fixtures/data.xml20
-rw-r--r--tests/special_headers/models.py5
-rw-r--r--tests/special_headers/templates/special_headers/article_detail.html1
-rw-r--r--tests/special_headers/tests.py62
-rw-r--r--tests/special_headers/urls.py13
-rw-r--r--tests/special_headers/views.py21
-rw-r--r--tests/template_tests/filters.py59
-rw-r--r--tests/transactions/tests.py14
-rw-r--r--tests/transactions_regress/tests.py4
-rw-r--r--tests/utils_tests/test_timesince.py71
-rw-r--r--tests/view_tests/tests/test_debug.py21
119 files changed, 2217 insertions, 1504 deletions
diff --git a/AUTHORS b/AUTHORS
index e7046f4386..7a249da5bf 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -92,6 +92,7 @@ answer newbie questions, and generally made Django that much better:
Randy Barlow <randy@electronsweatshop.com>
Scott Barr <scott@divisionbyzero.com.au>
Jiri Barton
+ Jorge Bastida <me@jorgebastida.com>
Ned Batchelder <http://www.nedbatchelder.com/>
batiste@dosimple.ch
Batman
@@ -123,6 +124,7 @@ answer newbie questions, and generally made Django that much better:
bthomas
btoll@bestweb.net
Jonathan Buchanan <jonathan.buchanan@gmail.com>
+ Jacob Burch <jacobburch@gmail.com>
Keith Bussell <kbussell@gmail.com>
C8E
Chris Cahoon <chris.cahoon@gmail.com>
@@ -162,6 +164,7 @@ answer newbie questions, and generally made Django that much better:
Leah Culver <leah.culver@gmail.com>
Raúl Cumplido <raulcumplido@gmail.com>
flavio.curella@gmail.com
+ Tome Cvitan <tome@cvitan.com>
John D'Agostino <john.dagostino@gmail.com>
dackze+django@gmail.com
Jim Dalton <jim.dalton@gmail.com>
@@ -194,6 +197,7 @@ answer newbie questions, and generally made Django that much better:
J. Clifford Dyer <jcd@sdf.lonestar.org>
Clint Ecker
Nick Efford <nick@efford.org>
+ Marc Egli <frog32@me.com>
eibaan@gmail.com
David Eklund
Julia Elman
@@ -218,6 +222,7 @@ answer newbie questions, and generally made Django that much better:
Stefane Fermgier <sf@fermigier.com>
J. Pablo Fernandez <pupeno@pupeno.com>
Maciej Fijalkowski
+ Leandra Finger <leandra.finger@gmail.com>
Juan Pedro Fisanotti <fisadev@gmail.com>
Ben Firshman <ben@firshman.co.uk>
Matthew Flanagan <http://wadofstuff.blogspot.com>
@@ -274,6 +279,7 @@ answer newbie questions, and generally made Django that much better:
Eric Holscher <http://ericholscher.com>
Ian Holsman <http://feh.holsman.net/>
Kieran Holland <http://www.kieranholland.com>
+ Markus Holtermann <http://markusholtermann.eu>
Sung-Jin Hong <serialx.net@gmail.com>
Leo "hylje" Honkanen <sealage@gmail.com>
Matt Hoskins <skaffenuk@googlemail.com>
@@ -503,6 +509,7 @@ answer newbie questions, and generally made Django that much better:
Bernd Schlapsi
schwank@gmail.com
scott@staplefish.com
+ Olivier Sels <olivier.sels@gmail.com>
Ilya Semenov <semenov@inetss.com>
Aleksandra Sendecka <asendecka@hauru.eu>
serbaut@gmail.com
@@ -527,11 +534,13 @@ answer newbie questions, and generally made Django that much better:
Don Spaulding <donspauldingii@gmail.com>
Calvin Spealman <ironfroggy@gmail.com>
Dane Springmeyer
+ Silvan Spross <silvan.spross@gmail.com>
Bjørn Stabell <bjorn@exoweb.net>
Georgi Stanojevski <glisha@gmail.com>
starrynight <cmorgh@gmail.com>
Vasiliy Stavenko <stavenko@gmail.com>
Thomas Steinacher <http://www.eggdrop.ch/>
+ Emil Stenström <em@kth.se>
Johan C. Stöver <johan@nilling.nl>
Nowell Strite <http://nowell.strite.org/>
Thomas Stromberg <tstromberg@google.com>
diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
index 53aef351c0..596f4ae78a 100644
--- a/django/conf/global_settings.py
+++ b/django/conf/global_settings.py
@@ -131,7 +131,7 @@ LANGUAGES = (
)
# Languages using BiDi (right-to-left) layout
-LANGUAGES_BIDI = ("he", "ar", "fa")
+LANGUAGES_BIDI = ("he", "ar", "fa", "ur")
# If you set this to False, Django will make some optimizations so as not
# to load the internationalization machinery.
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index 7373837bb0..e7edccd585 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -10,6 +10,7 @@ from django.contrib.contenttypes.models import ContentType
from django.contrib.admin import widgets, helpers
from django.contrib.admin.util import (unquote, flatten_fieldsets, get_deleted_objects,
model_format_dict, NestedObjects)
+from django.contrib.admin import validation
from django.contrib.admin.templatetags.admin_static import static
from django.contrib import messages
from django.views.decorators.csrf import csrf_protect
@@ -87,6 +88,14 @@ class BaseModelAdmin(six.with_metaclass(RenameBaseModelAdminMethods)):
readonly_fields = ()
ordering = None
+ # validation
+ validator_class = validation.BaseValidator
+
+ @classmethod
+ def validate(cls, model):
+ validator = cls.validator_class()
+ validator.validate(cls, model)
+
def __init__(self):
overrides = FORMFIELD_FOR_DBFIELD_DEFAULTS.copy()
overrides.update(self.formfield_overrides)
@@ -371,6 +380,9 @@ class ModelAdmin(BaseModelAdmin):
actions_on_bottom = False
actions_selection_counter = True
+ # validation
+ validator_class = validation.ModelAdminValidator
+
def __init__(self, model, admin_site):
self.model = model
self.opts = model._meta
@@ -1447,6 +1459,9 @@ class InlineModelAdmin(BaseModelAdmin):
verbose_name_plural = None
can_delete = True
+ # validation
+ validator_class = validation.InlineValidator
+
def __init__(self, parent_model, admin_site):
self.admin_site = admin_site
self.parent_model = parent_model
diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py
index 414d1b4f72..e0f43dfbfe 100644
--- a/django/contrib/admin/sites.py
+++ b/django/contrib/admin/sites.py
@@ -66,12 +66,6 @@ class AdminSite(object):
if not admin_class:
admin_class = ModelAdmin
- # Don't import the humongous validation code unless required
- if admin_class and settings.DEBUG:
- from django.contrib.admin.validation import validate
- else:
- validate = lambda model, adminclass: None
-
if isinstance(model_or_iterable, ModelBase):
model_or_iterable = [model_or_iterable]
for model in model_or_iterable:
@@ -94,8 +88,8 @@ class AdminSite(object):
options['__module__'] = __name__
admin_class = type("%sAdmin" % model.__name__, (admin_class,), options)
- # Validate (which might be a no-op)
- validate(admin_class, model)
+ if admin_class is not ModelAdmin and settings.DEBUG:
+ admin_class.validate(model)
# Instantiate the admin class to save in the registry
self._registry[model] = admin_class(model, self)
diff --git a/django/contrib/admin/validation.py b/django/contrib/admin/validation.py
index 8d65f96cf1..59c5ad35ef 100644
--- a/django/contrib/admin/validation.py
+++ b/django/contrib/admin/validation.py
@@ -3,358 +3,399 @@ from django.db import models
from django.db.models.fields import FieldDoesNotExist
from django.forms.models import (BaseModelForm, BaseModelFormSet, fields_for_model,
_get_foreign_key)
-from django.contrib.admin import ListFilter, FieldListFilter
from django.contrib.admin.util import get_fields_from_path, NotRelationField
-from django.contrib.admin.options import (flatten_fieldsets, BaseModelAdmin,
- ModelAdmin, HORIZONTAL, VERTICAL)
+"""
+Does basic ModelAdmin option validation. Calls custom validation
+classmethod in the end if it is provided in cls. The signature of the
+custom validation classmethod should be: def validate(cls, model).
+"""
-__all__ = ['validate']
+__all__ = ['BaseValidator', 'InlineValidator']
-def validate(cls, model):
- """
- Does basic ModelAdmin option validation. Calls custom validation
- classmethod in the end if it is provided in cls. The signature of the
- custom validation classmethod should be: def validate(cls, model).
- """
- # Before we can introspect models, they need to be fully loaded so that
- # inter-relations are set up correctly. We force that here.
- models.get_apps()
- opts = model._meta
- validate_base(cls, model)
+class BaseValidator(object):
+ def __init__(self):
+ # Before we can introspect models, they need to be fully loaded so that
+ # inter-relations are set up correctly. We force that here.
+ models.get_apps()
- # list_display
- if hasattr(cls, 'list_display'):
- check_isseq(cls, 'list_display', cls.list_display)
- for idx, field in enumerate(cls.list_display):
- if not callable(field):
- if not hasattr(cls, field):
- if not hasattr(model, field):
- try:
- opts.get_field(field)
- except models.FieldDoesNotExist:
- raise ImproperlyConfigured("%s.list_display[%d], %r is not a callable or an attribute of %r or found in the model %r."
- % (cls.__name__, idx, field, cls.__name__, model._meta.object_name))
- else:
- # getattr(model, field) could be an X_RelatedObjectsDescriptor
- f = fetch_attr(cls, model, opts, "list_display[%d]" % idx, field)
- if isinstance(f, models.ManyToManyField):
- raise ImproperlyConfigured("'%s.list_display[%d]', '%s' is a ManyToManyField which is not supported."
- % (cls.__name__, idx, field))
+ def validate(self, cls, model):
+ for m in dir(self):
+ if m.startswith('validate_'):
+ getattr(self, m)(cls, model)
- # list_display_links
- if hasattr(cls, 'list_display_links'):
- check_isseq(cls, 'list_display_links', cls.list_display_links)
- for idx, field in enumerate(cls.list_display_links):
- if field not in cls.list_display:
- raise ImproperlyConfigured("'%s.list_display_links[%d]' "
- "refers to '%s' which is not defined in 'list_display'."
- % (cls.__name__, idx, field))
+ def check_field_spec(self, cls, model, flds, label):
+ """
+ Validate the fields specification in `flds` from a ModelAdmin subclass
+ `cls` for the `model` model. Use `label` for reporting problems to the user.
- # list_filter
- if hasattr(cls, 'list_filter'):
- check_isseq(cls, 'list_filter', cls.list_filter)
- for idx, item in enumerate(cls.list_filter):
- # There are three options for specifying a filter:
- # 1: 'field' - a basic field filter, possibly w/ relationships (eg, 'field__rel')
- # 2: ('field', SomeFieldListFilter) - a field-based list filter class
- # 3: SomeListFilter - a non-field list filter class
- if callable(item) and not isinstance(item, models.Field):
- # If item is option 3, it should be a ListFilter...
- if not issubclass(item, ListFilter):
- raise ImproperlyConfigured("'%s.list_filter[%d]' is '%s'"
- " which is not a descendant of ListFilter."
- % (cls.__name__, idx, item.__name__))
- # ... but not a FieldListFilter.
- if issubclass(item, FieldListFilter):
- raise ImproperlyConfigured("'%s.list_filter[%d]' is '%s'"
- " which is of type FieldListFilter but is not"
- " associated with a field name."
- % (cls.__name__, idx, item.__name__))
- else:
- if isinstance(item, (tuple, list)):
- # item is option #2
- field, list_filter_class = item
- if not issubclass(list_filter_class, FieldListFilter):
- raise ImproperlyConfigured("'%s.list_filter[%d][1]'"
- " is '%s' which is not of type FieldListFilter."
- % (cls.__name__, idx, list_filter_class.__name__))
- else:
- # item is option #1
- field = item
- # Validate the field string
+ The fields specification can be a ``fields`` option or a ``fields``
+ sub-option from a ``fieldsets`` option component.
+ """
+ for fields in flds:
+ # The entry in fields might be a tuple. If it is a standalone
+ # field, make it into a tuple to make processing easier.
+ if type(fields) != tuple:
+ fields = (fields,)
+ for field in fields:
+ if field in cls.readonly_fields:
+ # Stuff can be put in fields that isn't actually a
+ # model field if it's in readonly_fields,
+ # readonly_fields will handle the validation of such
+ # things.
+ continue
try:
- get_fields_from_path(model, field)
- except (NotRelationField, FieldDoesNotExist):
- raise ImproperlyConfigured("'%s.list_filter[%d]' refers to '%s'"
- " which does not refer to a Field."
+ f = model._meta.get_field(field)
+ except models.FieldDoesNotExist:
+ # If we can't find a field on the model that matches, it could be an
+ # extra field on the form; nothing to check so move on to the next field.
+ continue
+ if isinstance(f, models.ManyToManyField) and not f.rel.through._meta.auto_created:
+ raise ImproperlyConfigured("'%s.%s' "
+ "can't include the ManyToManyField field '%s' because "
+ "'%s' manually specifies a 'through' model." % (
+ cls.__name__, label, field, field))
+
+ def validate_raw_id_fields(self, cls, model):
+ " Validate that raw_id_fields only contains field names that are listed on the model. "
+ if hasattr(cls, 'raw_id_fields'):
+ check_isseq(cls, 'raw_id_fields', cls.raw_id_fields)
+ for idx, field in enumerate(cls.raw_id_fields):
+ f = get_field(cls, model, 'raw_id_fields', field)
+ if not isinstance(f, (models.ForeignKey, models.ManyToManyField)):
+ raise ImproperlyConfigured("'%s.raw_id_fields[%d]', '%s' must "
+ "be either a ForeignKey or ManyToManyField."
% (cls.__name__, idx, field))
- # list_per_page = 100
- if hasattr(cls, 'list_per_page') and not isinstance(cls.list_per_page, int):
- raise ImproperlyConfigured("'%s.list_per_page' should be a integer."
- % cls.__name__)
+ def validate_fields(self, cls, model):
+ " Validate that fields only refer to existing fields, doesn't contain duplicates. "
+ # fields
+ if cls.fields: # default value is None
+ check_isseq(cls, 'fields', cls.fields)
+ self.check_field_spec(cls, model, cls.fields, 'fields')
+ if cls.fieldsets:
+ raise ImproperlyConfigured('Both fieldsets and fields are specified in %s.' % cls.__name__)
+ if len(cls.fields) > len(set(cls.fields)):
+ raise ImproperlyConfigured('There are duplicate field(s) in %s.fields' % cls.__name__)
- # list_max_show_all
- if hasattr(cls, 'list_max_show_all') and not isinstance(cls.list_max_show_all, int):
- raise ImproperlyConfigured("'%s.list_max_show_all' should be an integer."
- % cls.__name__)
+ def validate_fieldsets(self, cls, model):
+ " Validate that fieldsets is properly formatted and doesn't contain duplicates. "
+ from django.contrib.admin.options import flatten_fieldsets
+ if cls.fieldsets: # default value is None
+ check_isseq(cls, 'fieldsets', cls.fieldsets)
+ for idx, fieldset in enumerate(cls.fieldsets):
+ check_isseq(cls, 'fieldsets[%d]' % idx, fieldset)
+ if len(fieldset) != 2:
+ raise ImproperlyConfigured("'%s.fieldsets[%d]' does not "
+ "have exactly two elements." % (cls.__name__, idx))
+ check_isdict(cls, 'fieldsets[%d][1]' % idx, fieldset[1])
+ if 'fields' not in fieldset[1]:
+ raise ImproperlyConfigured("'fields' key is required in "
+ "%s.fieldsets[%d][1] field options dict."
+ % (cls.__name__, idx))
+ self.check_field_spec(cls, model, fieldset[1]['fields'], "fieldsets[%d][1]['fields']" % idx)
+ flattened_fieldsets = flatten_fieldsets(cls.fieldsets)
+ if len(flattened_fieldsets) > len(set(flattened_fieldsets)):
+ raise ImproperlyConfigured('There are duplicate field(s) in %s.fieldsets' % cls.__name__)
- # list_editable
- if hasattr(cls, 'list_editable') and cls.list_editable:
- check_isseq(cls, 'list_editable', cls.list_editable)
- for idx, field_name in enumerate(cls.list_editable):
- try:
- field = opts.get_field_by_name(field_name)[0]
- except models.FieldDoesNotExist:
- raise ImproperlyConfigured("'%s.list_editable[%d]' refers to a "
- "field, '%s', not defined on %s.%s."
- % (cls.__name__, idx, field_name, model._meta.app_label, model.__name__))
- if field_name not in cls.list_display:
- raise ImproperlyConfigured("'%s.list_editable[%d]' refers to "
- "'%s' which is not defined in 'list_display'."
- % (cls.__name__, idx, field_name))
- if field_name in cls.list_display_links:
- raise ImproperlyConfigured("'%s' cannot be in both '%s.list_editable'"
- " and '%s.list_display_links'"
- % (field_name, cls.__name__, cls.__name__))
- if not cls.list_display_links and cls.list_display[0] in cls.list_editable:
- raise ImproperlyConfigured("'%s.list_editable[%d]' refers to"
- " the first field in list_display, '%s', which can't be"
- " used unless list_display_links is set."
- % (cls.__name__, idx, cls.list_display[0]))
- if not field.editable:
- raise ImproperlyConfigured("'%s.list_editable[%d]' refers to a "
- "field, '%s', which isn't editable through the admin."
- % (cls.__name__, idx, field_name))
+ def validate_exclude(self, cls, model):
+ " Validate that exclude is a sequence without duplicates. "
+ if cls.exclude: # default value is None
+ check_isseq(cls, 'exclude', cls.exclude)
+ if len(cls.exclude) > len(set(cls.exclude)):
+ raise ImproperlyConfigured('There are duplicate field(s) in %s.exclude' % cls.__name__)
- # search_fields = ()
- if hasattr(cls, 'search_fields'):
- check_isseq(cls, 'search_fields', cls.search_fields)
+ def validate_form(self, cls, model):
+ " Validate that form subclasses BaseModelForm. "
+ if hasattr(cls, 'form') and not issubclass(cls.form, BaseModelForm):
+ raise ImproperlyConfigured("%s.form does not inherit from "
+ "BaseModelForm." % cls.__name__)
- # date_hierarchy = None
- if cls.date_hierarchy:
- f = get_field(cls, model, opts, 'date_hierarchy', cls.date_hierarchy)
- if not isinstance(f, (models.DateField, models.DateTimeField)):
- raise ImproperlyConfigured("'%s.date_hierarchy is "
- "neither an instance of DateField nor DateTimeField."
- % cls.__name__)
+ def validate_filter_vertical(self, cls, model):
+ " Validate that filter_vertical is a sequence of field names. "
+ if hasattr(cls, 'filter_vertical'):
+ check_isseq(cls, 'filter_vertical', cls.filter_vertical)
+ for idx, field in enumerate(cls.filter_vertical):
+ f = get_field(cls, model, 'filter_vertical', field)
+ if not isinstance(f, models.ManyToManyField):
+ raise ImproperlyConfigured("'%s.filter_vertical[%d]' must be "
+ "a ManyToManyField." % (cls.__name__, idx))
- # ordering = None
- if cls.ordering:
- check_isseq(cls, 'ordering', cls.ordering)
- for idx, field in enumerate(cls.ordering):
- if field == '?' and len(cls.ordering) != 1:
- raise ImproperlyConfigured("'%s.ordering' has the random "
- "ordering marker '?', but contains other fields as "
- "well. Please either remove '?' or the other fields."
- % cls.__name__)
- if field == '?':
- continue
- if field.startswith('-'):
- field = field[1:]
- # Skip ordering in the format field1__field2 (FIXME: checking
- # this format would be nice, but it's a little fiddly).
- if '__' in field:
- continue
- get_field(cls, model, opts, 'ordering[%d]' % idx, field)
+ def validate_filter_horizontal(self, cls, model):
+ " Validate that filter_horizontal is a sequence of field names. "
+ if hasattr(cls, 'filter_horizontal'):
+ check_isseq(cls, 'filter_horizontal', cls.filter_horizontal)
+ for idx, field in enumerate(cls.filter_horizontal):
+ f = get_field(cls, model, 'filter_horizontal', field)
+ if not isinstance(f, models.ManyToManyField):
+ raise ImproperlyConfigured("'%s.filter_horizontal[%d]' must be "
+ "a ManyToManyField." % (cls.__name__, idx))
- if hasattr(cls, "readonly_fields"):
- check_readonly_fields(cls, model, opts)
+ def validate_radio_fields(self, cls, model):
+ " Validate that radio_fields is a dictionary of choice or foreign key fields. "
+ from django.contrib.admin.options import HORIZONTAL, VERTICAL
+ if hasattr(cls, 'radio_fields'):
+ check_isdict(cls, 'radio_fields', cls.radio_fields)
+ for field, val in cls.radio_fields.items():
+ f = get_field(cls, model, 'radio_fields', field)
+ if not (isinstance(f, models.ForeignKey) or f.choices):
+ raise ImproperlyConfigured("'%s.radio_fields['%s']' "
+ "is neither an instance of ForeignKey nor does "
+ "have choices set." % (cls.__name__, field))
+ if not val in (HORIZONTAL, VERTICAL):
+ raise ImproperlyConfigured("'%s.radio_fields['%s']' "
+ "is neither admin.HORIZONTAL nor admin.VERTICAL."
+ % (cls.__name__, field))
- # list_select_related = False
- # save_as = False
- # save_on_top = False
- for attr in ('list_select_related', 'save_as', 'save_on_top'):
- if not isinstance(getattr(cls, attr), bool):
- raise ImproperlyConfigured("'%s.%s' should be a boolean."
- % (cls.__name__, attr))
+ def validate_prepopulated_fields(self, cls, model):
+ " Validate that prepopulated_fields if a dictionary containing allowed field types. "
+ # prepopulated_fields
+ if hasattr(cls, 'prepopulated_fields'):
+ check_isdict(cls, 'prepopulated_fields', cls.prepopulated_fields)
+ for field, val in cls.prepopulated_fields.items():
+ f = get_field(cls, model, 'prepopulated_fields', field)
+ if isinstance(f, (models.DateTimeField, models.ForeignKey,
+ models.ManyToManyField)):
+ raise ImproperlyConfigured("'%s.prepopulated_fields['%s']' "
+ "is either a DateTimeField, ForeignKey or "
+ "ManyToManyField. This isn't allowed."
+ % (cls.__name__, field))
+ check_isseq(cls, "prepopulated_fields['%s']" % field, val)
+ for idx, f in enumerate(val):
+ get_field(cls, model, "prepopulated_fields['%s'][%d]" % (field, idx), f)
+ def validate_ordering(self, cls, model):
+ " Validate that ordering refers to existing fields or is random. "
+ # ordering = None
+ if cls.ordering:
+ check_isseq(cls, 'ordering', cls.ordering)
+ for idx, field in enumerate(cls.ordering):
+ if field == '?' and len(cls.ordering) != 1:
+ raise ImproperlyConfigured("'%s.ordering' has the random "
+ "ordering marker '?', but contains other fields as "
+ "well. Please either remove '?' or the other fields."
+ % cls.__name__)
+ if field == '?':
+ continue
+ if field.startswith('-'):
+ field = field[1:]
+ # Skip ordering in the format field1__field2 (FIXME: checking
+ # this format would be nice, but it's a little fiddly).
+ if '__' in field:
+ continue
+ get_field(cls, model, 'ordering[%d]' % idx, field)
- # inlines = []
- if hasattr(cls, 'inlines'):
- check_isseq(cls, 'inlines', cls.inlines)
- for idx, inline in enumerate(cls.inlines):
- if not issubclass(inline, BaseModelAdmin):
- raise ImproperlyConfigured("'%s.inlines[%d]' does not inherit "
- "from BaseModelAdmin." % (cls.__name__, idx))
- if not inline.model:
- raise ImproperlyConfigured("'model' is a required attribute "
- "of '%s.inlines[%d]'." % (cls.__name__, idx))
- if not issubclass(inline.model, models.Model):
- raise ImproperlyConfigured("'%s.inlines[%d].model' does not "
- "inherit from models.Model." % (cls.__name__, idx))
- validate_base(inline, inline.model)
- validate_inline(inline, cls, model)
+ def validate_readonly_fields(self, cls, model):
+ " Validate that readonly_fields refers to proper attribute or field. "
+ if hasattr(cls, "readonly_fields"):
+ check_isseq(cls, "readonly_fields", cls.readonly_fields)
+ for idx, field in enumerate(cls.readonly_fields):
+ if not callable(field):
+ if not hasattr(cls, field):
+ if not hasattr(model, field):
+ try:
+ model._meta.get_field(field)
+ except models.FieldDoesNotExist:
+ raise ImproperlyConfigured("%s.readonly_fields[%d], %r is not a callable or an attribute of %r or found in the model %r."
+ % (cls.__name__, idx, field, cls.__name__, model._meta.object_name))
-def validate_inline(cls, parent, parent_model):
- # model is already verified to exist and be a Model
- if cls.fk_name: # default value is None
- f = get_field(cls, cls.model, cls.model._meta, 'fk_name', cls.fk_name)
- if not isinstance(f, models.ForeignKey):
- raise ImproperlyConfigured("'%s.fk_name is not an instance of "
- "models.ForeignKey." % cls.__name__)
+class ModelAdminValidator(BaseValidator):
+ def validate_save_as(self, cls, model):
+ " Validate save_as is a boolean. "
+ check_type(cls, 'save_as', bool)
- fk = _get_foreign_key(parent_model, cls.model, fk_name=cls.fk_name, can_fail=True)
+ def validate_save_on_top(self, cls, model):
+ " Validate save_on_top is a boolean. "
+ check_type(cls, 'save_on_top', bool)
- # extra = 3
- if not isinstance(cls.extra, int):
- raise ImproperlyConfigured("'%s.extra' should be a integer."
- % cls.__name__)
+ def validate_inlines(self, cls, model):
+ " Validate inline model admin classes. "
+ from django.contrib.admin.options import BaseModelAdmin
+ if hasattr(cls, 'inlines'):
+ check_isseq(cls, 'inlines', cls.inlines)
+ for idx, inline in enumerate(cls.inlines):
+ if not issubclass(inline, BaseModelAdmin):
+ raise ImproperlyConfigured("'%s.inlines[%d]' does not inherit "
+ "from BaseModelAdmin." % (cls.__name__, idx))
+ if not inline.model:
+ raise ImproperlyConfigured("'model' is a required attribute "
+ "of '%s.inlines[%d]'." % (cls.__name__, idx))
+ if not issubclass(inline.model, models.Model):
+ raise ImproperlyConfigured("'%s.inlines[%d].model' does not "
+ "inherit from models.Model." % (cls.__name__, idx))
+ inline.validate(inline.model)
+ self.check_inline(inline, model)
- # max_num = None
- max_num = getattr(cls, 'max_num', None)
- if max_num is not None and not isinstance(max_num, int):
- raise ImproperlyConfigured("'%s.max_num' should be an integer or None (default)."
- % cls.__name__)
+ def check_inline(self, cls, parent_model):
+ " Validate inline class's fk field is not excluded. "
+ fk = _get_foreign_key(parent_model, cls.model, fk_name=cls.fk_name, can_fail=True)
+ if hasattr(cls, 'exclude') and cls.exclude:
+ if fk and fk.name in cls.exclude:
+ raise ImproperlyConfigured("%s cannot exclude the field "
+ "'%s' - this is the foreign key to the parent model "
+ "%s.%s." % (cls.__name__, fk.name, parent_model._meta.app_label, parent_model.__name__))
- # formset
- if hasattr(cls, 'formset') and not issubclass(cls.formset, BaseModelFormSet):
- raise ImproperlyConfigured("'%s.formset' does not inherit from "
- "BaseModelFormSet." % cls.__name__)
+ def validate_list_display(self, cls, model):
+ " Validate that list_display only contains fields or usable attributes. "
+ if hasattr(cls, 'list_display'):
+ check_isseq(cls, 'list_display', cls.list_display)
+ for idx, field in enumerate(cls.list_display):
+ if not callable(field):
+ if not hasattr(cls, field):
+ if not hasattr(model, field):
+ try:
+ model._meta.get_field(field)
+ except models.FieldDoesNotExist:
+ raise ImproperlyConfigured("%s.list_display[%d], %r is not a callable or an attribute of %r or found in the model %r."
+ % (cls.__name__, idx, field, cls.__name__, model._meta.object_name))
+ else:
+ # getattr(model, field) could be an X_RelatedObjectsDescriptor
+ f = fetch_attr(cls, model, "list_display[%d]" % idx, field)
+ if isinstance(f, models.ManyToManyField):
+ raise ImproperlyConfigured("'%s.list_display[%d]', '%s' is a ManyToManyField which is not supported."
+ % (cls.__name__, idx, field))
- # exclude
- if hasattr(cls, 'exclude') and cls.exclude:
- if fk and fk.name in cls.exclude:
- raise ImproperlyConfigured("%s cannot exclude the field "
- "'%s' - this is the foreign key to the parent model "
- "%s.%s." % (cls.__name__, fk.name, parent_model._meta.app_label, parent_model.__name__))
+ def validate_list_display_links(self, cls, model):
+ " Validate that list_display_links is a unique subset of list_display. "
+ if hasattr(cls, 'list_display_links'):
+ check_isseq(cls, 'list_display_links', cls.list_display_links)
+ for idx, field in enumerate(cls.list_display_links):
+ if field not in cls.list_display:
+ raise ImproperlyConfigured("'%s.list_display_links[%d]' "
+ "refers to '%s' which is not defined in 'list_display'."
+ % (cls.__name__, idx, field))
- if hasattr(cls, "readonly_fields"):
- check_readonly_fields(cls, cls.model, cls.model._meta)
+ def validate_list_filter(self, cls, model):
+ """
+ Validate that list_filter is a sequence of one of three options:
+ 1: 'field' - a basic field filter, possibly w/ relationships (eg, 'field__rel')
+ 2: ('field', SomeFieldListFilter) - a field-based list filter class
+ 3: SomeListFilter - a non-field list filter class
+ """
+ from django.contrib.admin import ListFilter, FieldListFilter
+ if hasattr(cls, 'list_filter'):
+ check_isseq(cls, 'list_filter', cls.list_filter)
+ for idx, item in enumerate(cls.list_filter):
+ if callable(item) and not isinstance(item, models.Field):
+ # If item is option 3, it should be a ListFilter...
+ if not issubclass(item, ListFilter):
+ raise ImproperlyConfigured("'%s.list_filter[%d]' is '%s'"
+ " which is not a descendant of ListFilter."
+ % (cls.__name__, idx, item.__name__))
+ # ... but not a FieldListFilter.
+ if issubclass(item, FieldListFilter):
+ raise ImproperlyConfigured("'%s.list_filter[%d]' is '%s'"
+ " which is of type FieldListFilter but is not"
+ " associated with a field name."
+ % (cls.__name__, idx, item.__name__))
+ else:
+ if isinstance(item, (tuple, list)):
+ # item is option #2
+ field, list_filter_class = item
+ if not issubclass(list_filter_class, FieldListFilter):
+ raise ImproperlyConfigured("'%s.list_filter[%d][1]'"
+ " is '%s' which is not of type FieldListFilter."
+ % (cls.__name__, idx, list_filter_class.__name__))
+ else:
+ # item is option #1
+ field = item
+ # Validate the field string
+ try:
+ get_fields_from_path(model, field)
+ except (NotRelationField, FieldDoesNotExist):
+ raise ImproperlyConfigured("'%s.list_filter[%d]' refers to '%s'"
+ " which does not refer to a Field."
+ % (cls.__name__, idx, field))
-def validate_fields_spec(cls, model, opts, flds, label):
- """
- Validate the fields specification in `flds` from a ModelAdmin subclass
- `cls` for the `model` model. `opts` is `model`'s Meta inner class.
- Use `label` for reporting problems to the user.
+ def validate_list_select_related(self, cls, model):
+ " Validate that list_select_related is a boolean. "
+ check_type(cls, 'list_select_related', bool)
- The fields specification can be a ``fields`` option or a ``fields``
- sub-option from a ``fieldsets`` option component.
- """
- for fields in flds:
- # The entry in fields might be a tuple. If it is a standalone
- # field, make it into a tuple to make processing easier.
- if type(fields) != tuple:
- fields = (fields,)
- for field in fields:
- if field in cls.readonly_fields:
- # Stuff can be put in fields that isn't actually a
- # model field if it's in readonly_fields,
- # readonly_fields will handle the validation of such
- # things.
- continue
- try:
- f = opts.get_field(field)
- except models.FieldDoesNotExist:
- # If we can't find a field on the model that matches, it could be an
- # extra field on the form; nothing to check so move on to the next field.
- continue
- if isinstance(f, models.ManyToManyField) and not f.rel.through._meta.auto_created:
- raise ImproperlyConfigured("'%s.%s' "
- "can't include the ManyToManyField field '%s' because "
- "'%s' manually specifies a 'through' model." % (
- cls.__name__, label, field, field))
+ def validate_list_per_page(self, cls, model):
+ " Validate that list_per_page is an integer. "
+ check_type(cls, 'list_per_page', int)
-def validate_base(cls, model):
- opts = model._meta
+ def validate_list_max_show_all(self, cls, model):
+ " Validate that list_max_show_all is an integer. "
+ check_type(cls, 'list_max_show_all', int)
- # raw_id_fields
- if hasattr(cls, 'raw_id_fields'):
- check_isseq(cls, 'raw_id_fields', cls.raw_id_fields)
- for idx, field in enumerate(cls.raw_id_fields):
- f = get_field(cls, model, opts, 'raw_id_fields', field)
- if not isinstance(f, (models.ForeignKey, models.ManyToManyField)):
- raise ImproperlyConfigured("'%s.raw_id_fields[%d]', '%s' must "
- "be either a ForeignKey or ManyToManyField."
- % (cls.__name__, idx, field))
+ def validate_list_editable(self, cls, model):
+ """
+ Validate that list_editable is a sequence of editable fields from
+ list_display without first element.
+ """
+ if hasattr(cls, 'list_editable') and cls.list_editable:
+ check_isseq(cls, 'list_editable', cls.list_editable)
+ for idx, field_name in enumerate(cls.list_editable):
+ try:
+ field = model._meta.get_field_by_name(field_name)[0]
+ except models.FieldDoesNotExist:
+ raise ImproperlyConfigured("'%s.list_editable[%d]' refers to a "
+ "field, '%s', not defined on %s.%s."
+ % (cls.__name__, idx, field_name, model._meta.app_label, model.__name__))
+ if field_name not in cls.list_display:
+ raise ImproperlyConfigured("'%s.list_editable[%d]' refers to "
+ "'%s' which is not defined in 'list_display'."
+ % (cls.__name__, idx, field_name))
+ if field_name in cls.list_display_links:
+ raise ImproperlyConfigured("'%s' cannot be in both '%s.list_editable'"
+ " and '%s.list_display_links'"
+ % (field_name, cls.__name__, cls.__name__))
+ if not cls.list_display_links and cls.list_display[0] in cls.list_editable:
+ raise ImproperlyConfigured("'%s.list_editable[%d]' refers to"
+ " the first field in list_display, '%s', which can't be"
+ " used unless list_display_links is set."
+ % (cls.__name__, idx, cls.list_display[0]))
+ if not field.editable:
+ raise ImproperlyConfigured("'%s.list_editable[%d]' refers to a "
+ "field, '%s', which isn't editable through the admin."
+ % (cls.__name__, idx, field_name))
- # fields
- if cls.fields: # default value is None
- check_isseq(cls, 'fields', cls.fields)
- validate_fields_spec(cls, model, opts, cls.fields, 'fields')
- if cls.fieldsets:
- raise ImproperlyConfigured('Both fieldsets and fields are specified in %s.' % cls.__name__)
- if len(cls.fields) > len(set(cls.fields)):
- raise ImproperlyConfigured('There are duplicate field(s) in %s.fields' % cls.__name__)
+ def validate_search_fields(self, cls, model):
+ " Validate search_fields is a sequence. "
+ if hasattr(cls, 'search_fields'):
+ check_isseq(cls, 'search_fields', cls.search_fields)
- # fieldsets
- if cls.fieldsets: # default value is None
- check_isseq(cls, 'fieldsets', cls.fieldsets)
- for idx, fieldset in enumerate(cls.fieldsets):
- check_isseq(cls, 'fieldsets[%d]' % idx, fieldset)
- if len(fieldset) != 2:
- raise ImproperlyConfigured("'%s.fieldsets[%d]' does not "
- "have exactly two elements." % (cls.__name__, idx))
- check_isdict(cls, 'fieldsets[%d][1]' % idx, fieldset[1])
- if 'fields' not in fieldset[1]:
- raise ImproperlyConfigured("'fields' key is required in "
- "%s.fieldsets[%d][1] field options dict."
- % (cls.__name__, idx))
- validate_fields_spec(cls, model, opts, fieldset[1]['fields'], "fieldsets[%d][1]['fields']" % idx)
- flattened_fieldsets = flatten_fieldsets(cls.fieldsets)
- if len(flattened_fieldsets) > len(set(flattened_fieldsets)):
- raise ImproperlyConfigured('There are duplicate field(s) in %s.fieldsets' % cls.__name__)
+ def validate_date_hierarchy(self, cls, model):
+ " Validate that date_hierarchy refers to DateField or DateTimeField. "
+ if cls.date_hierarchy:
+ f = get_field(cls, model, 'date_hierarchy', cls.date_hierarchy)
+ if not isinstance(f, (models.DateField, models.DateTimeField)):
+ raise ImproperlyConfigured("'%s.date_hierarchy is "
+ "neither an instance of DateField nor DateTimeField."
+ % cls.__name__)
- # exclude
- if cls.exclude: # default value is None
- check_isseq(cls, 'exclude', cls.exclude)
- if len(cls.exclude) > len(set(cls.exclude)):
- raise ImproperlyConfigured('There are duplicate field(s) in %s.exclude' % cls.__name__)
- # form
- if hasattr(cls, 'form') and not issubclass(cls.form, BaseModelForm):
- raise ImproperlyConfigured("%s.form does not inherit from "
- "BaseModelForm." % cls.__name__)
+class InlineValidator(BaseValidator):
+ def validate_fk_name(self, cls, model):
+ " Validate that fk_name refers to a ForeignKey. "
+ if cls.fk_name: # default value is None
+ f = get_field(cls, model, 'fk_name', cls.fk_name)
+ if not isinstance(f, models.ForeignKey):
+ raise ImproperlyConfigured("'%s.fk_name is not an instance of "
+ "models.ForeignKey." % cls.__name__)
- # filter_vertical
- if hasattr(cls, 'filter_vertical'):
- check_isseq(cls, 'filter_vertical', cls.filter_vertical)
- for idx, field in enumerate(cls.filter_vertical):
- f = get_field(cls, model, opts, 'filter_vertical', field)
- if not isinstance(f, models.ManyToManyField):
- raise ImproperlyConfigured("'%s.filter_vertical[%d]' must be "
- "a ManyToManyField." % (cls.__name__, idx))
+ def validate_extra(self, cls, model):
+ " Validate that extra is an integer. "
+ check_type(cls, 'extra', int)
- # filter_horizontal
- if hasattr(cls, 'filter_horizontal'):
- check_isseq(cls, 'filter_horizontal', cls.filter_horizontal)
- for idx, field in enumerate(cls.filter_horizontal):
- f = get_field(cls, model, opts, 'filter_horizontal', field)
- if not isinstance(f, models.ManyToManyField):
- raise ImproperlyConfigured("'%s.filter_horizontal[%d]' must be "
- "a ManyToManyField." % (cls.__name__, idx))
+ def validate_max_num(self, cls, model):
+ " Validate that max_num is an integer. "
+ check_type(cls, 'max_num', int)
- # radio_fields
- if hasattr(cls, 'radio_fields'):
- check_isdict(cls, 'radio_fields', cls.radio_fields)
- for field, val in cls.radio_fields.items():
- f = get_field(cls, model, opts, 'radio_fields', field)
- if not (isinstance(f, models.ForeignKey) or f.choices):
- raise ImproperlyConfigured("'%s.radio_fields['%s']' "
- "is neither an instance of ForeignKey nor does "
- "have choices set." % (cls.__name__, field))
- if not val in (HORIZONTAL, VERTICAL):
- raise ImproperlyConfigured("'%s.radio_fields['%s']' "
- "is neither admin.HORIZONTAL nor admin.VERTICAL."
- % (cls.__name__, field))
+ def validate_formset(self, cls, model):
+ " Validate formset is a subclass of BaseModelFormSet. "
+ if hasattr(cls, 'formset') and not issubclass(cls.formset, BaseModelFormSet):
+ raise ImproperlyConfigured("'%s.formset' does not inherit from "
+ "BaseModelFormSet." % cls.__name__)
- # prepopulated_fields
- if hasattr(cls, 'prepopulated_fields'):
- check_isdict(cls, 'prepopulated_fields', cls.prepopulated_fields)
- for field, val in cls.prepopulated_fields.items():
- f = get_field(cls, model, opts, 'prepopulated_fields', field)
- if isinstance(f, (models.DateTimeField, models.ForeignKey,
- models.ManyToManyField)):
- raise ImproperlyConfigured("'%s.prepopulated_fields['%s']' "
- "is either a DateTimeField, ForeignKey or "
- "ManyToManyField. This isn't allowed."
- % (cls.__name__, field))
- check_isseq(cls, "prepopulated_fields['%s']" % field, val)
- for idx, f in enumerate(val):
- get_field(cls, model, opts, "prepopulated_fields['%s'][%d]" % (field, idx), f)
+
+def check_type(cls, attr, type_):
+ if getattr(cls, attr, None) is not None and not isinstance(getattr(cls, attr), type_):
+ raise ImproperlyConfigured("'%s.%s' should be a %s."
+ % (cls.__name__, attr, type_.__name__ ))
def check_isseq(cls, label, obj):
if not isinstance(obj, (list, tuple)):
@@ -364,16 +405,16 @@ def check_isdict(cls, label, obj):
if not isinstance(obj, dict):
raise ImproperlyConfigured("'%s.%s' must be a dictionary." % (cls.__name__, label))
-def get_field(cls, model, opts, label, field):
+def get_field(cls, model, label, field):
try:
- return opts.get_field(field)
+ return model._meta.get_field(field)
except models.FieldDoesNotExist:
raise ImproperlyConfigured("'%s.%s' refers to field '%s' that is missing from model '%s.%s'."
% (cls.__name__, label, field, model._meta.app_label, model.__name__))
-def fetch_attr(cls, model, opts, label, field):
+def fetch_attr(cls, model, label, field):
try:
- return opts.get_field(field)
+ return model._meta.get_field(field)
except models.FieldDoesNotExist:
pass
try:
@@ -381,15 +422,3 @@ def fetch_attr(cls, model, opts, label, field):
except AttributeError:
raise ImproperlyConfigured("'%s.%s' refers to '%s' that is neither a field, method or property of model '%s.%s'."
% (cls.__name__, label, field, model._meta.app_label, model.__name__))
-
-def check_readonly_fields(cls, model, opts):
- check_isseq(cls, "readonly_fields", cls.readonly_fields)
- for idx, field in enumerate(cls.readonly_fields):
- if not callable(field):
- if not hasattr(cls, field):
- if not hasattr(model, field):
- try:
- opts.get_field(field)
- except models.FieldDoesNotExist:
- raise ImproperlyConfigured("%s.readonly_fields[%d], %r is not a callable or an attribute of %r or found in the model %r."
- % (cls.__name__, idx, field, cls.__name__, model._meta.object_name))
diff --git a/django/contrib/auth/__init__.py b/django/contrib/auth/__init__.py
index ef9066657d..e032038775 100644
--- a/django/contrib/auth/__init__.py
+++ b/django/contrib/auth/__init__.py
@@ -1,9 +1,11 @@
import re
-from django.contrib.auth.signals import user_logged_in, user_logged_out, user_login_failed
+from django.conf import settings
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
from django.utils.module_loading import import_by_path
+from .signals import user_logged_in, user_logged_out, user_login_failed
+
SESSION_KEY = '_auth_user_id'
BACKEND_SESSION_KEY = '_auth_user_backend'
REDIRECT_FIELD_NAME = 'next'
@@ -14,7 +16,6 @@ def load_backend(path):
def get_backends():
- from django.conf import settings
backends = []
for backend_path in settings.AUTHENTICATION_BACKENDS:
backends.append(load_backend(backend_path))
@@ -106,7 +107,6 @@ def logout(request):
def get_user_model():
"Return the User model that is active in this project"
- from django.conf import settings
from django.db.models import get_model
try:
@@ -120,12 +120,13 @@ def get_user_model():
def get_user(request):
- from django.contrib.auth.models import AnonymousUser
+ from .models import AnonymousUser
try:
user_id = request.session[SESSION_KEY]
backend_path = request.session[BACKEND_SESSION_KEY]
+ assert backend_path in settings.AUTHENTICATION_BACKENDS
backend = load_backend(backend_path)
user = backend.get_user(user_id) or AnonymousUser()
- except KeyError:
+ except (KeyError, AssertionError):
user = AnonymousUser()
return user
diff --git a/django/contrib/auth/management/__init__.py b/django/contrib/auth/management/__init__.py
index 685e50d498..fdf822ff74 100644
--- a/django/contrib/auth/management/__init__.py
+++ b/django/contrib/auth/management/__init__.py
@@ -80,7 +80,7 @@ def create_permissions(app, created_models, verbosity, db=DEFAULT_DB_ALIAS, **kw
for perm in _get_all_permissions(klass._meta, ctype):
searched_perms.append((ctype, perm))
- # Find all the Permissions that have a context_type for a model we're
+ # Find all the Permissions that have a content_type for a model we're
# looking for. We don't need to check for codenames since we already have
# a list of the ones we're going to create.
all_perms = set(auth_app.Permission.objects.using(db).filter(
diff --git a/django/contrib/auth/tests/test_auth_backends.py b/django/contrib/auth/tests/test_auth_backends.py
index bb97c54a11..fc5a80e8dd 100644
--- a/django/contrib/auth/tests/test_auth_backends.py
+++ b/django/contrib/auth/tests/test_auth_backends.py
@@ -2,12 +2,14 @@ from __future__ import unicode_literals
from datetime import date
from django.conf import settings
+from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.models import User, Group, Permission, AnonymousUser
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.contrib.auth.tests.test_custom_user import ExtensionUser, CustomPermissionsUser, CustomUser
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
-from django.contrib.auth import authenticate
+from django.contrib.auth import authenticate, get_user
+from django.http import HttpRequest
from django.test import TestCase
from django.test.utils import override_settings
@@ -402,3 +404,52 @@ class PermissionDeniedBackendTest(TestCase):
settings.AUTHENTICATION_BACKENDS) + (backend, ))
def test_authenticates(self):
self.assertEqual(authenticate(username='test', password='test'), self.user1)
+
+
+class NewModelBackend(ModelBackend):
+ pass
+
+
+@skipIfCustomUser
+class ChangedBackendSettingsTest(TestCase):
+ """
+ Tests for changes in the settings.AUTHENTICATION_BACKENDS
+ """
+ backend = 'django.contrib.auth.tests.test_auth_backends.NewModelBackend'
+
+ TEST_USERNAME = 'test_user'
+ TEST_PASSWORD = 'test_password'
+ TEST_EMAIL = 'test@example.com'
+
+ def setUp(self):
+ User.objects.create_user(self.TEST_USERNAME,
+ self.TEST_EMAIL,
+ self.TEST_PASSWORD)
+
+ @override_settings(AUTHENTICATION_BACKENDS=(backend, ))
+ def test_changed_backend_settings(self):
+ """
+ Tests that removing a backend configured in AUTHENTICATION_BACKENDS
+ make already logged-in users disconnect.
+ """
+
+ # Get a session for the test user
+ self.assertTrue(self.client.login(
+ username=self.TEST_USERNAME,
+ password=self.TEST_PASSWORD)
+ )
+
+ # Prepare a request object
+ request = HttpRequest()
+ request.session = self.client.session
+
+ # Remove NewModelBackend
+ with self.settings(AUTHENTICATION_BACKENDS=(
+ 'django.contrib.auth.backends.ModelBackend',)):
+ # Get the user from the request
+ user = get_user(request)
+
+ # Assert that the user retrieval is successful and the user is
+ # anonymous as the backend is not longer available.
+ self.assertIsNotNone(user)
+ self.assertTrue(user.is_anonymous())
diff --git a/django/contrib/auth/tests/test_management.py b/django/contrib/auth/tests/test_management.py
index 04fd4941ab..fee0a29e7b 100644
--- a/django/contrib/auth/tests/test_management.py
+++ b/django/contrib/auth/tests/test_management.py
@@ -7,6 +7,7 @@ from django.contrib.auth.management.commands import changepassword
from django.contrib.auth.models import User
from django.contrib.auth.tests.test_custom_user import CustomUser
from django.contrib.auth.tests.utils import skipIfCustomUser
+from django.contrib.contenttypes.models import ContentType
from django.core.management import call_command
from django.core.management.base import CommandError
from django.core.management.validation import get_validation_errors
@@ -195,6 +196,7 @@ class PermissionDuplicationTestCase(TestCase):
def tearDown(self):
models.Permission._meta.permissions = self._original_permissions
+ ContentType.objects.clear_cache()
def test_duplicated_permissions(self):
"""
diff --git a/django/contrib/flatpages/views.py b/django/contrib/flatpages/views.py
index 497979e497..20e930f343 100644
--- a/django/contrib/flatpages/views.py
+++ b/django/contrib/flatpages/views.py
@@ -1,7 +1,6 @@
from django.conf import settings
from django.contrib.flatpages.models import FlatPage
from django.contrib.sites.models import get_current_site
-from django.core.xheaders import populate_xheaders
from django.http import Http404, HttpResponse, HttpResponsePermanentRedirect
from django.shortcuts import get_object_or_404
from django.template import loader, RequestContext
@@ -70,5 +69,4 @@ def render_flatpage(request, f):
'flatpage': f,
})
response = HttpResponse(t.render(c))
- populate_xheaders(request, response, FlatPage, f.id)
return response
diff --git a/django/contrib/humanize/locale/en/LC_MESSAGES/django.po b/django/contrib/humanize/locale/en/LC_MESSAGES/django.po
index fc75b677a0..2c3cd0c08d 100644
--- a/django/contrib/humanize/locale/en/LC_MESSAGES/django.po
+++ b/django/contrib/humanize/locale/en/LC_MESSAGES/django.po
@@ -4,7 +4,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Django\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2013-05-02 16:18+0200\n"
+"POT-Creation-Date: 2013-05-18 23:10+0200\n"
"PO-Revision-Date: 2010-05-13 15:35+0200\n"
"Last-Translator: Django team\n"
"Language-Team: English <en@li.org>\n"
@@ -237,54 +237,60 @@ msgctxt "naturaltime"
msgid "%(delta)s ago"
msgstr ""
-#: templatetags/humanize.py:194 templatetags/humanize.py:216
+#: templatetags/humanize.py:194 templatetags/humanize.py:219
msgid "now"
msgstr ""
-#: templatetags/humanize.py:197
+#. Translators: \\u00a0 is non-breaking space
+#: templatetags/humanize.py:198
#, python-format
msgid "a second ago"
-msgid_plural "%(count)s seconds ago"
+msgid_plural "%(count)s\\u00a0seconds ago"
msgstr[0] ""
msgstr[1] ""
-#: templatetags/humanize.py:202
+#. Translators: \\u00a0 is non-breaking space
+#: templatetags/humanize.py:204
#, python-format
msgid "a minute ago"
-msgid_plural "%(count)s minutes ago"
+msgid_plural "%(count)s\\u00a0minutes ago"
msgstr[0] ""
msgstr[1] ""
-#: templatetags/humanize.py:207
+#. Translators: \\u00a0 is non-breaking space
+#: templatetags/humanize.py:210
#, python-format
msgid "an hour ago"
-msgid_plural "%(count)s hours ago"
+msgid_plural "%(count)s\\u00a0hours ago"
msgstr[0] ""
msgstr[1] ""
-#: templatetags/humanize.py:213
+#: templatetags/humanize.py:216
#, python-format
msgctxt "naturaltime"
msgid "%(delta)s from now"
msgstr ""
-#: templatetags/humanize.py:219
+#. Translators: \\u00a0 is non-breaking space
+#: templatetags/humanize.py:223
#, python-format
msgid "a second from now"
-msgid_plural "%(count)s seconds from now"
+msgid_plural "%(count)s\\u00a0seconds from now"
msgstr[0] ""
msgstr[1] ""
-#: templatetags/humanize.py:224
+#. Translators: \\u00a0 is non-breaking space
+#: templatetags/humanize.py:229
#, python-format
msgid "a minute from now"
-msgid_plural "%(count)s minutes from now"
+msgid_plural "%(count)s\\u00a0minutes from now"
msgstr[0] ""
msgstr[1] ""
-#: templatetags/humanize.py:229
+#. Translators: \\u00a0 is non-breaking space
+#: templatetags/humanize.py:235
#, python-format
msgid "an hour from now"
-msgid_plural "%(count)s hours from now"
+msgid_plural "%(count)s\\u00a0hours from now"
msgstr[0] ""
msgstr[1] ""
diff --git a/django/contrib/humanize/templatetags/humanize.py b/django/contrib/humanize/templatetags/humanize.py
index 21f4c452fa..eaee734f75 100644
--- a/django/contrib/humanize/templatetags/humanize.py
+++ b/django/contrib/humanize/templatetags/humanize.py
@@ -194,17 +194,20 @@ def naturaltime(value):
return _('now')
elif delta.seconds < 60:
return ungettext(
- 'a second ago', '%(count)s seconds ago', delta.seconds
+ # Translators: \\u00a0 is non-breaking space
+ 'a second ago', '%(count)s\u00a0seconds ago', delta.seconds
) % {'count': delta.seconds}
elif delta.seconds // 60 < 60:
count = delta.seconds // 60
return ungettext(
- 'a minute ago', '%(count)s minutes ago', count
+ # Translators: \\u00a0 is non-breaking space
+ 'a minute ago', '%(count)s\u00a0minutes ago', count
) % {'count': count}
else:
count = delta.seconds // 60 // 60
return ungettext(
- 'an hour ago', '%(count)s hours ago', count
+ # Translators: \\u00a0 is non-breaking space
+ 'an hour ago', '%(count)s\u00a0hours ago', count
) % {'count': count}
else:
delta = value - now
@@ -216,15 +219,18 @@ def naturaltime(value):
return _('now')
elif delta.seconds < 60:
return ungettext(
- 'a second from now', '%(count)s seconds from now', delta.seconds
+ # Translators: \\u00a0 is non-breaking space
+ 'a second from now', '%(count)s\u00a0seconds from now', delta.seconds
) % {'count': delta.seconds}
elif delta.seconds // 60 < 60:
count = delta.seconds // 60
return ungettext(
- 'a minute from now', '%(count)s minutes from now', count
+ # Translators: \\u00a0 is non-breaking space
+ 'a minute from now', '%(count)s\u00a0minutes from now', count
) % {'count': count}
else:
count = delta.seconds // 60 // 60
return ungettext(
- 'an hour from now', '%(count)s hours from now', count
+ # Translators: \\u00a0 is non-breaking space
+ 'an hour from now', '%(count)s\u00a0hours from now', count
) % {'count': count}
diff --git a/django/contrib/humanize/tests.py b/django/contrib/humanize/tests.py
index 1e1c8424e6..5c39c79ac3 100644
--- a/django/contrib/humanize/tests.py
+++ b/django/contrib/humanize/tests.py
@@ -195,22 +195,22 @@ class HumanizeTests(TestCase):
result_list = [
'now',
'a second ago',
- '30 seconds ago',
+ '30\xa0seconds ago',
'a minute ago',
- '2 minutes ago',
+ '2\xa0minutes ago',
'an hour ago',
- '23 hours ago',
- '1 day ago',
- '1 year, 4 months ago',
+ '23\xa0hours ago',
+ '1\xa0day ago',
+ '1\xa0year, 4\xa0months ago',
'a second from now',
- '30 seconds from now',
+ '30\xa0seconds from now',
'a minute from now',
- '2 minutes from now',
+ '2\xa0minutes from now',
'an hour from now',
- '23 hours from now',
- '1 day from now',
- '2 days, 6 hours from now',
- '1 year, 4 months from now',
+ '23\xa0hours from now',
+ '1\xa0day from now',
+ '2\xa0days, 6\xa0hours from now',
+ '1\xa0year, 4\xa0months from now',
'now',
'now',
]
@@ -218,8 +218,8 @@ class HumanizeTests(TestCase):
# date in naive arithmetic is only 2 days and 5 hours after in
# aware arithmetic.
result_list_with_tz_support = result_list[:]
- assert result_list_with_tz_support[-4] == '2 days, 6 hours from now'
- result_list_with_tz_support[-4] == '2 days, 5 hours from now'
+ assert result_list_with_tz_support[-4] == '2\xa0days, 6\xa0hours from now'
+ result_list_with_tz_support[-4] == '2\xa0days, 5\xa0hours from now'
orig_humanize_datetime, humanize.datetime = humanize.datetime, MockDateTime
try:
diff --git a/django/core/cache/backends/base.py b/django/core/cache/backends/base.py
index 53b0270a57..deb98e7714 100644
--- a/django/core/cache/backends/base.py
+++ b/django/core/cache/backends/base.py
@@ -15,6 +15,10 @@ class CacheKeyWarning(DjangoRuntimeWarning):
pass
+# Stub class to ensure not passing in a `timeout` argument results in
+# the default timeout
+DEFAULT_TIMEOUT = object()
+
# Memcached does not accept keys longer than this.
MEMCACHE_MAX_KEY_LENGTH = 250
@@ -84,7 +88,7 @@ class BaseCache(object):
new_key = self.key_func(key, self.key_prefix, version)
return new_key
- def add(self, key, value, timeout=None, version=None):
+ def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
"""
Set a value in the cache if the key does not already exist. If
timeout is given, that timeout will be used for the key; otherwise
@@ -101,7 +105,7 @@ class BaseCache(object):
"""
raise NotImplementedError
- def set(self, key, value, timeout=None, version=None):
+ def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
"""
Set a value in the cache. If timeout is given, that timeout will be
used for the key; otherwise the default cache timeout will be used.
@@ -163,7 +167,7 @@ class BaseCache(object):
# if a subclass overrides it.
return self.has_key(key)
- def set_many(self, data, timeout=None, version=None):
+ def set_many(self, data, timeout=DEFAULT_TIMEOUT, version=None):
"""
Set a bunch of values in the cache at once from a dict of key/value
pairs. For certain backends (memcached), this is much more efficient
diff --git a/django/core/cache/backends/db.py b/django/core/cache/backends/db.py
index 7749284122..0e59c6d65e 100644
--- a/django/core/cache/backends/db.py
+++ b/django/core/cache/backends/db.py
@@ -9,7 +9,7 @@ except ImportError:
import pickle
from django.conf import settings
-from django.core.cache.backends.base import BaseCache
+from django.core.cache.backends.base import BaseCache, DEFAULT_TIMEOUT
from django.db import connections, transaction, router, DatabaseError
from django.utils import timezone, six
from django.utils.encoding import force_bytes
@@ -65,6 +65,7 @@ class DatabaseCache(BaseDatabaseCache):
if row is None:
return default
now = timezone.now()
+
if row[2] < now:
db = router.db_for_write(self.cache_model_class)
cursor = connections[db].cursor()
@@ -74,18 +75,18 @@ class DatabaseCache(BaseDatabaseCache):
value = connections[db].ops.process_clob(row[1])
return pickle.loads(base64.b64decode(force_bytes(value)))
- def set(self, key, value, timeout=None, version=None):
+ def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
key = self.make_key(key, version=version)
self.validate_key(key)
self._base_set('set', key, value, timeout)
- def add(self, key, value, timeout=None, version=None):
+ def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
key = self.make_key(key, version=version)
self.validate_key(key)
return self._base_set('add', key, value, timeout)
- def _base_set(self, mode, key, value, timeout=None):
- if timeout is None:
+ def _base_set(self, mode, key, value, timeout=DEFAULT_TIMEOUT):
+ if timeout == DEFAULT_TIMEOUT:
timeout = self.default_timeout
db = router.db_for_write(self.cache_model_class)
table = connections[db].ops.quote_name(self._table)
@@ -95,7 +96,9 @@ class DatabaseCache(BaseDatabaseCache):
num = cursor.fetchone()[0]
now = timezone.now()
now = now.replace(microsecond=0)
- if settings.USE_TZ:
+ if timeout is None:
+ exp = datetime.max
+ elif settings.USE_TZ:
exp = datetime.utcfromtimestamp(time.time() + timeout)
else:
exp = datetime.fromtimestamp(time.time() + timeout)
diff --git a/django/core/cache/backends/dummy.py b/django/core/cache/backends/dummy.py
index 9fe9b3b5f5..7ca6114ee4 100644
--- a/django/core/cache/backends/dummy.py
+++ b/django/core/cache/backends/dummy.py
@@ -1,12 +1,12 @@
"Dummy cache backend"
-from django.core.cache.backends.base import BaseCache
+from django.core.cache.backends.base import BaseCache, DEFAULT_TIMEOUT
class DummyCache(BaseCache):
def __init__(self, host, *args, **kwargs):
BaseCache.__init__(self, *args, **kwargs)
- def add(self, key, value, timeout=None, version=None):
+ def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
key = self.make_key(key, version=version)
self.validate_key(key)
return True
@@ -16,7 +16,7 @@ class DummyCache(BaseCache):
self.validate_key(key)
return default
- def set(self, key, value, timeout=None, version=None):
+ def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
key = self.make_key(key, version=version)
self.validate_key(key)
@@ -32,7 +32,7 @@ class DummyCache(BaseCache):
self.validate_key(key)
return False
- def set_many(self, data, timeout=0, version=None):
+ def set_many(self, data, timeout=DEFAULT_TIMEOUT, version=None):
pass
def delete_many(self, keys, version=None):
diff --git a/django/core/cache/backends/filebased.py b/django/core/cache/backends/filebased.py
index 96194d458f..d19eed4a95 100644
--- a/django/core/cache/backends/filebased.py
+++ b/django/core/cache/backends/filebased.py
@@ -9,9 +9,10 @@ try:
except ImportError:
import pickle
-from django.core.cache.backends.base import BaseCache
+from django.core.cache.backends.base import BaseCache, DEFAULT_TIMEOUT
from django.utils.encoding import force_bytes
+
class FileBasedCache(BaseCache):
def __init__(self, dir, params):
BaseCache.__init__(self, params)
@@ -19,7 +20,7 @@ class FileBasedCache(BaseCache):
if not os.path.exists(self._dir):
self._createdir()
- def add(self, key, value, timeout=None, version=None):
+ def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
if self.has_key(key, version=version):
return False
@@ -35,7 +36,7 @@ class FileBasedCache(BaseCache):
with open(fname, 'rb') as f:
exp = pickle.load(f)
now = time.time()
- if exp < now:
+ if exp is not None and exp < now:
self._delete(fname)
else:
return pickle.load(f)
@@ -43,14 +44,14 @@ class FileBasedCache(BaseCache):
pass
return default
- def set(self, key, value, timeout=None, version=None):
+ def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
key = self.make_key(key, version=version)
self.validate_key(key)
fname = self._key_to_file(key)
dirname = os.path.dirname(fname)
- if timeout is None:
+ if timeout == DEFAULT_TIMEOUT:
timeout = self.default_timeout
self._cull()
@@ -60,8 +61,8 @@ class FileBasedCache(BaseCache):
os.makedirs(dirname)
with open(fname, 'wb') as f:
- now = time.time()
- pickle.dump(now + timeout, f, pickle.HIGHEST_PROTOCOL)
+ expiry = None if timeout is None else time.time() + timeout
+ pickle.dump(expiry, f, pickle.HIGHEST_PROTOCOL)
pickle.dump(value, f, pickle.HIGHEST_PROTOCOL)
except (IOError, OSError):
pass
diff --git a/django/core/cache/backends/locmem.py b/django/core/cache/backends/locmem.py
index 76667e9609..1fa17052fd 100644
--- a/django/core/cache/backends/locmem.py
+++ b/django/core/cache/backends/locmem.py
@@ -6,7 +6,7 @@ try:
except ImportError:
import pickle
-from django.core.cache.backends.base import BaseCache
+from django.core.cache.backends.base import BaseCache, DEFAULT_TIMEOUT
from django.utils.synch import RWLock
# Global in-memory store of cache data. Keyed by name, to provide
@@ -23,7 +23,7 @@ class LocMemCache(BaseCache):
self._expire_info = _expire_info.setdefault(name, {})
self._lock = _locks.setdefault(name, RWLock())
- def add(self, key, value, timeout=None, version=None):
+ def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
key = self.make_key(key, version=version)
self.validate_key(key)
with self._lock.writer():
@@ -41,10 +41,8 @@ class LocMemCache(BaseCache):
key = self.make_key(key, version=version)
self.validate_key(key)
with self._lock.reader():
- exp = self._expire_info.get(key)
- if exp is None:
- return default
- elif exp > time.time():
+ exp = self._expire_info.get(key, 0)
+ if exp is None or exp > time.time():
try:
pickled = self._cache[key]
return pickle.loads(pickled)
@@ -58,15 +56,16 @@ class LocMemCache(BaseCache):
pass
return default
- def _set(self, key, value, timeout=None):
+ def _set(self, key, value, timeout=DEFAULT_TIMEOUT):
if len(self._cache) >= self._max_entries:
self._cull()
- if timeout is None:
+ if timeout == DEFAULT_TIMEOUT:
timeout = self.default_timeout
+ expiry = None if timeout is None else time.time() + timeout
self._cache[key] = value
- self._expire_info[key] = time.time() + timeout
+ self._expire_info[key] = expiry
- def set(self, key, value, timeout=None, version=None):
+ def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
key = self.make_key(key, version=version)
self.validate_key(key)
with self._lock.writer():
diff --git a/django/core/cache/backends/memcached.py b/django/core/cache/backends/memcached.py
index c942acd52f..64d1c41dc5 100644
--- a/django/core/cache/backends/memcached.py
+++ b/django/core/cache/backends/memcached.py
@@ -4,7 +4,7 @@ import time
import pickle
from threading import local
-from django.core.cache.backends.base import BaseCache, InvalidCacheBackendError
+from django.core.cache.backends.base import BaseCache, DEFAULT_TIMEOUT
from django.utils import six
from django.utils.encoding import force_str
@@ -36,12 +36,22 @@ class BaseMemcachedCache(BaseCache):
return self._client
- def _get_memcache_timeout(self, timeout):
+ def _get_memcache_timeout(self, timeout=DEFAULT_TIMEOUT):
"""
Memcached deals with long (> 30 days) timeouts in a special
way. Call this function to obtain a safe value for your timeout.
"""
- timeout = timeout or self.default_timeout
+ if timeout == DEFAULT_TIMEOUT:
+ return self.default_timeout
+
+ if timeout is None:
+ # Using 0 in memcache sets a non-expiring timeout.
+ return 0
+ elif int(timeout) == 0:
+ # Other cache backends treat 0 as set-and-expire. To achieve this
+ # in memcache backends, a negative timeout must be passed.
+ timeout = -1
+
if timeout > 2592000: # 60*60*24*30, 30 days
# See http://code.google.com/p/memcached/wiki/FAQ
# "You can set expire times up to 30 days in the future. After that
@@ -56,7 +66,7 @@ class BaseMemcachedCache(BaseCache):
# Python 2 memcache requires the key to be a byte string.
return force_str(super(BaseMemcachedCache, self).make_key(key, version))
- def add(self, key, value, timeout=0, version=None):
+ def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
key = self.make_key(key, version=version)
return self._cache.add(key, value, self._get_memcache_timeout(timeout))
@@ -67,7 +77,7 @@ class BaseMemcachedCache(BaseCache):
return default
return val
- def set(self, key, value, timeout=0, version=None):
+ def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
key = self.make_key(key, version=version)
self._cache.set(key, value, self._get_memcache_timeout(timeout))
@@ -125,7 +135,7 @@ class BaseMemcachedCache(BaseCache):
raise ValueError("Key '%s' not found" % key)
return val
- def set_many(self, data, timeout=0, version=None):
+ def set_many(self, data, timeout=DEFAULT_TIMEOUT, version=None):
safe_data = {}
for key, value in data.items():
key = self.make_key(key, version=version)
diff --git a/django/core/management/commands/loaddata.py b/django/core/management/commands/loaddata.py
index c95d11cf60..ab9f7468c4 100644
--- a/django/core/management/commands/loaddata.py
+++ b/django/core/management/commands/loaddata.py
@@ -4,6 +4,7 @@ import os
import gzip
import zipfile
from optparse import make_option
+import warnings
from django.conf import settings
from django.core import serializers
@@ -162,9 +163,14 @@ class Command(BaseCommand):
else:
fixture_dirs = app_fixtures + list(settings.FIXTURE_DIRS) + ['']
+ label_found = False
for fixture_dir in fixture_dirs:
- self.process_dir(fixture_dir, fixture_name, compression_formats,
- formats)
+ found = self.process_dir(fixture_dir, fixture_name,
+ compression_formats, formats)
+ label_found = label_found or found
+
+ if fixture_name != 'initial_data' and not label_found:
+ warnings.warn("No fixture named '%s' found." % fixture_name)
def process_dir(self, fixture_dir, fixture_name, compression_formats,
serialization_formats):
@@ -242,3 +248,5 @@ class Command(BaseCommand):
raise CommandError(
"No fixture data found for '%s'. (File format may be invalid.)" %
(fixture_name))
+
+ return label_found
diff --git a/django/core/management/validation.py b/django/core/management/validation.py
index 0f0eade569..2040a14582 100644
--- a/django/core/management/validation.py
+++ b/django/core/management/validation.py
@@ -118,8 +118,8 @@ def get_validation_errors(outfile, app=None):
e.add(opts, '"%s": "choices" should be iterable (e.g., a tuple or list).' % f.name)
else:
for c in f.choices:
- if not isinstance(c, (list, tuple)) or len(c) != 2:
- e.add(opts, '"%s": "choices" should be a sequence of two-tuples.' % f.name)
+ if isinstance(c, six.string_types) or not is_iterable(c) or len(c) != 2:
+ e.add(opts, '"%s": "choices" should be a sequence of two-item iterables (e.g. list of 2 item tuples).' % f.name)
if f.db_index not in (None, True, False):
e.add(opts, '"%s": "db_index" should be either None, True or False.' % f.name)
diff --git a/django/core/xheaders.py b/django/core/xheaders.py
deleted file mode 100644
index 3766628c98..0000000000
--- a/django/core/xheaders.py
+++ /dev/null
@@ -1,24 +0,0 @@
-"""
-Pages in Django can are served up with custom HTTP headers containing useful
-information about those pages -- namely, the content type and object ID.
-
-This module contains utility functions for retrieving and doing interesting
-things with these special "X-Headers" (so called because the HTTP spec demands
-that custom headers are prefixed with "X-").
-
-Next time you're at slashdot.org, watch out for X-Fry and X-Bender. :)
-"""
-
-def populate_xheaders(request, response, model, object_id):
- """
- Adds the "X-Object-Type" and "X-Object-Id" headers to the given
- HttpResponse according to the given model and object_id -- but only if the
- given HttpRequest object has an IP address within the INTERNAL_IPS setting
- or if the request is from a logged in staff member.
- """
- from django.conf import settings
- if (request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS
- or (hasattr(request, 'user') and request.user.is_active
- and request.user.is_staff)):
- response['X-Object-Type'] = "%s.%s" % (model._meta.app_label, model._meta.model_name)
- response['X-Object-Id'] = str(object_id)
diff --git a/django/db/backends/oracle/introspection.py b/django/db/backends/oracle/introspection.py
index ff56dca5c2..608901f081 100644
--- a/django/db/backends/oracle/introspection.py
+++ b/django/db/backends/oracle/introspection.py
@@ -1,4 +1,5 @@
from django.db.backends import BaseDatabaseIntrospection, FieldInfo
+from django.utils.encoding import force_text
import cx_Oracle
import re
@@ -48,7 +49,8 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
cursor.execute("SELECT * FROM %s WHERE ROWNUM < 2" % self.connection.ops.quote_name(table_name))
description = []
for desc in cursor.description:
- description.append(FieldInfo(*((desc[0].lower(),) + desc[1:])))
+ name = force_text(desc[0]) # cx_Oracle always returns a 'str' on both Python 2 and 3
+ description.append(FieldInfo(*(name.lower(),) + desc[1:]))
return description
def table_name_converter(self, name):
diff --git a/django/forms/fields.py b/django/forms/fields.py
index 4ce57d34a3..ac68b9f1fc 100644
--- a/django/forms/fields.py
+++ b/django/forms/fields.py
@@ -198,14 +198,15 @@ class Field(object):
result.validators = self.validators[:]
return result
+
class CharField(Field):
def __init__(self, max_length=None, min_length=None, *args, **kwargs):
self.max_length, self.min_length = max_length, min_length
super(CharField, self).__init__(*args, **kwargs)
if min_length is not None:
- self.validators.append(validators.MinLengthValidator(min_length))
+ self.validators.append(validators.MinLengthValidator(int(min_length)))
if max_length is not None:
- self.validators.append(validators.MaxLengthValidator(max_length))
+ self.validators.append(validators.MaxLengthValidator(int(max_length)))
def to_python(self, value):
"Returns a Unicode object."
@@ -220,6 +221,7 @@ class CharField(Field):
attrs.update({'maxlength': str(self.max_length)})
return attrs
+
class IntegerField(Field):
default_error_messages = {
'invalid': _('Enter a whole number.'),
@@ -444,6 +446,7 @@ class TimeField(BaseTemporalField):
def strptime(self, value, format):
return datetime.datetime.strptime(force_str(value), format).time()
+
class DateTimeField(BaseTemporalField):
widget = DateTimeInput
input_formats = formats.get_format_lazy('DATETIME_INPUT_FORMATS')
@@ -482,6 +485,7 @@ class DateTimeField(BaseTemporalField):
def strptime(self, value, format):
return datetime.datetime.strptime(force_str(value), format)
+
class RegexField(CharField):
def __init__(self, regex, max_length=None, min_length=None, error_message=None, *args, **kwargs):
"""
@@ -511,6 +515,7 @@ class RegexField(CharField):
regex = property(_get_regex, _set_regex)
+
class EmailField(CharField):
widget = EmailInput
default_validators = [validators.validate_email]
@@ -519,6 +524,7 @@ class EmailField(CharField):
value = self.to_python(value).strip()
return super(EmailField, self).clean(value)
+
class FileField(Field):
widget = ClearableFileInput
default_error_messages = {
@@ -626,6 +632,7 @@ class ImageField(FileField):
f.seek(0)
return f
+
class URLField(CharField):
widget = URLInput
default_error_messages = {
@@ -670,6 +677,10 @@ class URLField(CharField):
value = urlunsplit(url_fields)
return value
+ def clean(self, value):
+ value = self.to_python(value).strip()
+ return super(URLField, self).clean(value)
+
class BooleanField(Field):
widget = CheckboxInput
@@ -788,6 +799,7 @@ class ChoiceField(Field):
return True
return False
+
class TypedChoiceField(ChoiceField):
def __init__(self, *args, **kwargs):
self.coerce = kwargs.pop('coerce', lambda val: val)
@@ -899,6 +911,7 @@ class ComboField(Field):
value = field.clean(value)
return value
+
class MultiValueField(Field):
"""
A Field that aggregates the logic of multiple Fields.
@@ -1043,6 +1056,7 @@ class FilePathField(ChoiceField):
self.widget.choices = self.choices
+
class SplitDateTimeField(MultiValueField):
widget = SplitDateTimeWidget
hidden_widget = SplitHiddenDateTimeWidget
@@ -1105,3 +1119,7 @@ class GenericIPAddressField(CharField):
class SlugField(CharField):
default_validators = [validators.validate_slug]
+
+ def clean(self, value):
+ value = self.to_python(value).strip()
+ return super(SlugField, self).clean(value)
diff --git a/django/forms/models.py b/django/forms/models.py
index af5cda8faf..93c8b89efe 100644
--- a/django/forms/models.py
+++ b/django/forms/models.py
@@ -136,7 +136,7 @@ def model_to_dict(instance, fields=None, exclude=None):
data[f.name] = f.value_from_object(instance)
return data
-def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_callback=None):
+def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_callback=None, localized_fields=None):
"""
Returns a ``SortedDict`` containing form fields for the given model.
@@ -162,10 +162,12 @@ def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_c
continue
if exclude and f.name in exclude:
continue
+
+ kwargs = {}
if widgets and f.name in widgets:
- kwargs = {'widget': widgets[f.name]}
- else:
- kwargs = {}
+ kwargs['widget'] = widgets[f.name]
+ if localized_fields == ALL_FIELDS or (localized_fields and f.name in localized_fields):
+ kwargs['localize'] = True
if formfield_callback is None:
formfield = f.formfield(**kwargs)
@@ -192,6 +194,7 @@ class ModelFormOptions(object):
self.fields = getattr(options, 'fields', None)
self.exclude = getattr(options, 'exclude', None)
self.widgets = getattr(options, 'widgets', None)
+ self.localized_fields = getattr(options, 'localized_fields', None)
class ModelFormMetaclass(type):
@@ -215,7 +218,7 @@ class ModelFormMetaclass(type):
# We check if a string was passed to `fields` or `exclude`,
# which is likely to be a mistake where the user typed ('foo') instead
# of ('foo',)
- for opt in ['fields', 'exclude']:
+ for opt in ['fields', 'exclude', 'localized_fields']:
value = getattr(opts, opt)
if isinstance(value, six.string_types) and value != ALL_FIELDS:
msg = ("%(model)s.Meta.%(opt)s cannot be a string. "
@@ -242,8 +245,9 @@ class ModelFormMetaclass(type):
# fields from the model"
opts.fields = None
- fields = fields_for_model(opts.model, opts.fields,
- opts.exclude, opts.widgets, formfield_callback)
+ fields = fields_for_model(opts.model, opts.fields, opts.exclude,
+ opts.widgets, formfield_callback, opts.localized_fields)
+
# make sure opts.fields doesn't specify an invalid field
none_model_fields = [k for k, v in six.iteritems(fields) if not v]
missing_fields = set(none_model_fields) - \
@@ -409,7 +413,7 @@ class ModelForm(six.with_metaclass(ModelFormMetaclass, BaseModelForm)):
pass
def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
- formfield_callback=None, widgets=None):
+ formfield_callback=None, widgets=None, localized_fields=None):
"""
Returns a ModelForm containing form fields for the given model.
@@ -423,6 +427,8 @@ def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
``widgets`` is a dictionary of model field names mapped to a widget.
+ ``localized_fields`` is a list of names of fields which should be localized.
+
``formfield_callback`` is a callable that takes a model field and returns
a form field.
"""
@@ -438,6 +444,8 @@ def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
attrs['exclude'] = exclude
if widgets is not None:
attrs['widgets'] = widgets
+ if localized_fields is not None:
+ attrs['localized_fields'] = localized_fields
# If parent form class already has an inner Meta, the Meta we're
# creating needs to inherit from the parent's inner meta.
@@ -726,8 +734,8 @@ class BaseModelFormSet(BaseFormSet):
def modelformset_factory(model, form=ModelForm, formfield_callback=None,
formset=BaseModelFormSet, extra=1, can_delete=False,
- can_order=False, max_num=None, fields=None,
- exclude=None, widgets=None, validate_max=False):
+ can_order=False, max_num=None, fields=None, exclude=None,
+ widgets=None, validate_max=False, localized_fields=None):
"""
Returns a FormSet class for the given Django model class.
"""
@@ -748,7 +756,7 @@ def modelformset_factory(model, form=ModelForm, formfield_callback=None,
form = modelform_factory(model, form=form, fields=fields, exclude=exclude,
formfield_callback=formfield_callback,
- widgets=widgets)
+ widgets=widgets, localized_fields=localized_fields)
FormSet = formset_factory(form, formset, extra=extra, max_num=max_num,
can_order=can_order, can_delete=can_delete,
validate_max=validate_max)
@@ -885,9 +893,9 @@ def _get_foreign_key(parent_model, model, fk_name=None, can_fail=False):
def inlineformset_factory(parent_model, model, form=ModelForm,
formset=BaseInlineFormSet, fk_name=None,
- fields=None, exclude=None,
- extra=3, can_order=False, can_delete=True, max_num=None,
- formfield_callback=None, widgets=None, validate_max=False):
+ fields=None, exclude=None, extra=3, can_order=False,
+ can_delete=True, max_num=None, formfield_callback=None,
+ widgets=None, validate_max=False, localized_fields=None):
"""
Returns an ``InlineFormSet`` for the given kwargs.
@@ -910,6 +918,7 @@ def inlineformset_factory(parent_model, model, form=ModelForm,
'max_num': max_num,
'widgets': widgets,
'validate_max': validate_max,
+ 'localized_fields': localized_fields,
}
FormSet = modelformset_factory(model, **kwargs)
FormSet.fk = fk
diff --git a/django/middleware/cache.py b/django/middleware/cache.py
index 83860e15f3..e13a8c3918 100644
--- a/django/middleware/cache.py
+++ b/django/middleware/cache.py
@@ -29,11 +29,6 @@ More details about how the caching works:
of the response's "Cache-Control" header, falling back to the
CACHE_MIDDLEWARE_SECONDS setting if the section was not found.
-* If CACHE_MIDDLEWARE_ANONYMOUS_ONLY is set to True, only anonymous requests
- (i.e., those not made by a logged-in user) will be cached. This is a simple
- and effective way of avoiding the caching of the Django admin (and any other
- user-specific content).
-
* This middleware expects that a HEAD request is answered with the same response
headers exactly like the corresponding GET request.
@@ -48,6 +43,8 @@ More details about how the caching works:
"""
+import warnings
+
from django.conf import settings
from django.core.cache import get_cache, DEFAULT_CACHE_ALIAS
from django.utils.cache import get_cache_key, learn_cache_key, patch_response_headers, get_max_age
@@ -200,5 +197,9 @@ class CacheMiddleware(UpdateCacheMiddleware, FetchFromCacheMiddleware):
else:
self.cache_anonymous_only = cache_anonymous_only
+ if self.cache_anonymous_only:
+ msg = "CACHE_MIDDLEWARE_ANONYMOUS_ONLY has been deprecated and will be removed in Django 1.8."
+ warnings.warn(msg, PendingDeprecationWarning, stacklevel=1)
+
self.cache = get_cache(self.cache_alias, **cache_kwargs)
self.cache_timeout = self.cache.default_timeout
diff --git a/django/middleware/csrf.py b/django/middleware/csrf.py
index 423034478b..98974f011a 100644
--- a/django/middleware/csrf.py
+++ b/django/middleware/csrf.py
@@ -83,6 +83,13 @@ class CsrfViewMiddleware(object):
return None
def _reject(self, request, reason):
+ logger.warning('Forbidden (%s): %s',
+ reason, request.path,
+ extra={
+ 'status_code': 403,
+ 'request': request,
+ }
+ )
return _get_failure_view()(request, reason=reason)
def process_view(self, request, callback, callback_args, callback_kwargs):
@@ -134,38 +141,18 @@ class CsrfViewMiddleware(object):
# we can use strict Referer checking.
referer = request.META.get('HTTP_REFERER')
if referer is None:
- logger.warning('Forbidden (%s): %s',
- REASON_NO_REFERER, request.path,
- extra={
- 'status_code': 403,
- 'request': request,
- }
- )
return self._reject(request, REASON_NO_REFERER)
# Note that request.get_host() includes the port.
good_referer = 'https://%s/' % request.get_host()
if not same_origin(referer, good_referer):
reason = REASON_BAD_REFERER % (referer, good_referer)
- logger.warning('Forbidden (%s): %s', reason, request.path,
- extra={
- 'status_code': 403,
- 'request': request,
- }
- )
return self._reject(request, reason)
if csrf_token is None:
# No CSRF cookie. For POST requests, we insist on a CSRF cookie,
# and in this way we can avoid all CSRF attacks, including login
# CSRF.
- logger.warning('Forbidden (%s): %s',
- REASON_NO_CSRF_COOKIE, request.path,
- extra={
- 'status_code': 403,
- 'request': request,
- }
- )
return self._reject(request, REASON_NO_CSRF_COOKIE)
# Check non-cookie token for match.
@@ -179,13 +166,6 @@ class CsrfViewMiddleware(object):
request_csrf_token = request.META.get('HTTP_X_CSRFTOKEN', '')
if not constant_time_compare(request_csrf_token, csrf_token):
- logger.warning('Forbidden (%s): %s',
- REASON_BAD_TOKEN, request.path,
- extra={
- 'status_code': 403,
- 'request': request,
- }
- )
return self._reject(request, REASON_BAD_TOKEN)
return self._accept(request)
diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py
index 88526e5a20..4201cfeb67 100644
--- a/django/template/defaultfilters.py
+++ b/django/template/defaultfilters.py
@@ -14,7 +14,7 @@ from django.utils import formats
from django.utils.dateformat import format, time_format
from django.utils.encoding import force_text, iri_to_uri
from django.utils.html import (conditional_escape, escapejs, fix_ampersands,
- escape, urlize as urlize_impl, linebreaks, strip_tags)
+ escape, urlize as urlize_impl, linebreaks, strip_tags, avoid_wrapping)
from django.utils.http import urlquote
from django.utils.text import Truncator, wrap, phone2numeric
from django.utils.safestring import mark_safe, SafeData, mark_for_escaping
@@ -810,7 +810,8 @@ def filesizeformat(bytes):
try:
bytes = float(bytes)
except (TypeError,ValueError,UnicodeDecodeError):
- return ungettext("%(size)d byte", "%(size)d bytes", 0) % {'size': 0}
+ value = ungettext("%(size)d byte", "%(size)d bytes", 0) % {'size': 0}
+ return avoid_wrapping(value)
filesize_number_format = lambda value: formats.number_format(round(value, 1), 1)
@@ -821,16 +822,19 @@ def filesizeformat(bytes):
PB = 1<<50
if bytes < KB:
- return ungettext("%(size)d byte", "%(size)d bytes", bytes) % {'size': bytes}
- if bytes < MB:
- return ugettext("%s KB") % filesize_number_format(bytes / KB)
- if bytes < GB:
- return ugettext("%s MB") % filesize_number_format(bytes / MB)
- if bytes < TB:
- return ugettext("%s GB") % filesize_number_format(bytes / GB)
- if bytes < PB:
- return ugettext("%s TB") % filesize_number_format(bytes / TB)
- return ugettext("%s PB") % filesize_number_format(bytes / PB)
+ value = ungettext("%(size)d byte", "%(size)d bytes", bytes) % {'size': bytes}
+ elif bytes < MB:
+ value = ugettext("%s KB") % filesize_number_format(bytes / KB)
+ elif bytes < GB:
+ value = ugettext("%s MB") % filesize_number_format(bytes / MB)
+ elif bytes < TB:
+ value = ugettext("%s GB") % filesize_number_format(bytes / GB)
+ elif bytes < PB:
+ value = ugettext("%s TB") % filesize_number_format(bytes / TB)
+ else:
+ value = ugettext("%s PB") % filesize_number_format(bytes / PB)
+
+ return avoid_wrapping(value)
@register.filter(is_safe=False)
def pluralize(value, arg='s'):
diff --git a/django/test/testcases.py b/django/test/testcases.py
index 6fe6b9c397..311b50cfb7 100644
--- a/django/test/testcases.py
+++ b/django/test/testcases.py
@@ -231,6 +231,10 @@ class _AssertTemplateNotUsedContext(_AssertTemplateUsedContext):
class SimpleTestCase(ut2.TestCase):
+ # The class we'll use for the test client self.client.
+ # Can be overridden in derived classes.
+ client_class = Client
+
_warn_txt = ("save_warnings_state/restore_warnings_state "
"django.test.*TestCase methods are deprecated. Use Python's "
"warnings.catch_warnings context manager instead.")
@@ -264,10 +268,31 @@ class SimpleTestCase(ut2.TestCase):
return
def _pre_setup(self):
- pass
+ """Performs any pre-test setup. This includes:
+
+ * If the Test Case class has a 'urls' member, replace the
+ ROOT_URLCONF with it.
+ * Clearing the mail test outbox.
+ """
+ self.client = self.client_class()
+ self._urlconf_setup()
+ mail.outbox = []
+
+ def _urlconf_setup(self):
+ set_urlconf(None)
+ if hasattr(self, 'urls'):
+ self._old_root_urlconf = settings.ROOT_URLCONF
+ settings.ROOT_URLCONF = self.urls
+ clear_url_caches()
def _post_teardown(self):
- pass
+ self._urlconf_teardown()
+
+ def _urlconf_teardown(self):
+ set_urlconf(None)
+ if hasattr(self, '_old_root_urlconf'):
+ settings.ROOT_URLCONF = self._old_root_urlconf
+ clear_url_caches()
def save_warnings_state(self):
"""
@@ -291,258 +316,6 @@ class SimpleTestCase(ut2.TestCase):
"""
return override_settings(**kwargs)
- def assertRaisesMessage(self, expected_exception, expected_message,
- callable_obj=None, *args, **kwargs):
- """
- Asserts that the message in a raised exception matches the passed
- value.
-
- Args:
- expected_exception: Exception class expected to be raised.
- expected_message: expected error message string value.
- callable_obj: Function to be called.
- args: Extra args.
- kwargs: Extra kwargs.
- """
- return six.assertRaisesRegex(self, expected_exception,
- re.escape(expected_message), callable_obj, *args, **kwargs)
-
- def assertFieldOutput(self, fieldclass, valid, invalid, field_args=None,
- field_kwargs=None, empty_value=''):
- """
- Asserts that a form field behaves correctly with various inputs.
-
- Args:
- fieldclass: the class of the field to be tested.
- valid: a dictionary mapping valid inputs to their expected
- cleaned values.
- invalid: a dictionary mapping invalid inputs to one or more
- raised error messages.
- field_args: the args passed to instantiate the field
- field_kwargs: the kwargs passed to instantiate the field
- empty_value: the expected clean output for inputs in empty_values
-
- """
- if field_args is None:
- field_args = []
- if field_kwargs is None:
- field_kwargs = {}
- required = fieldclass(*field_args, **field_kwargs)
- optional = fieldclass(*field_args,
- **dict(field_kwargs, required=False))
- # test valid inputs
- for input, output in valid.items():
- self.assertEqual(required.clean(input), output)
- self.assertEqual(optional.clean(input), output)
- # test invalid inputs
- for input, errors in invalid.items():
- with self.assertRaises(ValidationError) as context_manager:
- required.clean(input)
- self.assertEqual(context_manager.exception.messages, errors)
-
- with self.assertRaises(ValidationError) as context_manager:
- optional.clean(input)
- self.assertEqual(context_manager.exception.messages, errors)
- # test required inputs
- error_required = [force_text(required.error_messages['required'])]
- for e in required.empty_values:
- with self.assertRaises(ValidationError) as context_manager:
- required.clean(e)
- self.assertEqual(context_manager.exception.messages,
- error_required)
- self.assertEqual(optional.clean(e), empty_value)
- # test that max_length and min_length are always accepted
- if issubclass(fieldclass, CharField):
- field_kwargs.update({'min_length':2, 'max_length':20})
- self.assertTrue(isinstance(fieldclass(*field_args, **field_kwargs),
- fieldclass))
-
- def assertHTMLEqual(self, html1, html2, msg=None):
- """
- Asserts that two HTML snippets are semantically the same.
- Whitespace in most cases is ignored, and attribute ordering is not
- significant. The passed-in arguments must be valid HTML.
- """
- dom1 = assert_and_parse_html(self, html1, msg,
- 'First argument is not valid HTML:')
- dom2 = assert_and_parse_html(self, html2, msg,
- 'Second argument is not valid HTML:')
-
- if dom1 != dom2:
- standardMsg = '%s != %s' % (
- safe_repr(dom1, True), safe_repr(dom2, True))
- diff = ('\n' + '\n'.join(difflib.ndiff(
- six.text_type(dom1).splitlines(),
- six.text_type(dom2).splitlines())))
- standardMsg = self._truncateMessage(standardMsg, diff)
- self.fail(self._formatMessage(msg, standardMsg))
-
- def assertHTMLNotEqual(self, html1, html2, msg=None):
- """Asserts that two HTML snippets are not semantically equivalent."""
- dom1 = assert_and_parse_html(self, html1, msg,
- 'First argument is not valid HTML:')
- dom2 = assert_and_parse_html(self, html2, msg,
- 'Second argument is not valid HTML:')
-
- if dom1 == dom2:
- standardMsg = '%s == %s' % (
- safe_repr(dom1, True), safe_repr(dom2, True))
- self.fail(self._formatMessage(msg, standardMsg))
-
- def assertInHTML(self, needle, haystack, count = None, msg_prefix=''):
- needle = assert_and_parse_html(self, needle, None,
- 'First argument is not valid HTML:')
- haystack = assert_and_parse_html(self, haystack, None,
- 'Second argument is not valid HTML:')
- real_count = haystack.count(needle)
- if count is not None:
- self.assertEqual(real_count, count,
- msg_prefix + "Found %d instances of '%s' in response"
- " (expected %d)" % (real_count, needle, count))
- else:
- self.assertTrue(real_count != 0,
- msg_prefix + "Couldn't find '%s' in response" % needle)
-
- def assertJSONEqual(self, raw, expected_data, msg=None):
- try:
- data = json.loads(raw)
- except ValueError:
- self.fail("First argument is not valid JSON: %r" % raw)
- if isinstance(expected_data, six.string_types):
- try:
- expected_data = json.loads(expected_data)
- except ValueError:
- self.fail("Second argument is not valid JSON: %r" % expected_data)
- self.assertEqual(data, expected_data, msg=msg)
-
- def assertXMLEqual(self, xml1, xml2, msg=None):
- """
- Asserts that two XML snippets are semantically the same.
- Whitespace in most cases is ignored, and attribute ordering is not
- significant. The passed-in arguments must be valid XML.
- """
- try:
- result = compare_xml(xml1, xml2)
- except Exception as e:
- standardMsg = 'First or second argument is not valid XML\n%s' % e
- self.fail(self._formatMessage(msg, standardMsg))
- else:
- if not result:
- standardMsg = '%s != %s' % (safe_repr(xml1, True), safe_repr(xml2, True))
- self.fail(self._formatMessage(msg, standardMsg))
-
- def assertXMLNotEqual(self, xml1, xml2, msg=None):
- """
- Asserts that two XML snippets are not semantically equivalent.
- Whitespace in most cases is ignored, and attribute ordering is not
- significant. The passed-in arguments must be valid XML.
- """
- try:
- result = compare_xml(xml1, xml2)
- except Exception as e:
- standardMsg = 'First or second argument is not valid XML\n%s' % e
- self.fail(self._formatMessage(msg, standardMsg))
- else:
- if result:
- standardMsg = '%s == %s' % (safe_repr(xml1, True), safe_repr(xml2, True))
- self.fail(self._formatMessage(msg, standardMsg))
-
-
-class TransactionTestCase(SimpleTestCase):
-
- # The class we'll use for the test client self.client.
- # Can be overridden in derived classes.
- client_class = Client
-
- # Subclasses can ask for resetting of auto increment sequence before each
- # test case
- reset_sequences = False
-
- def _pre_setup(self):
- """Performs any pre-test setup. This includes:
-
- * Flushing the database.
- * If the Test Case class has a 'fixtures' member, installing the
- named fixtures.
- * If the Test Case class has a 'urls' member, replace the
- ROOT_URLCONF with it.
- * Clearing the mail test outbox.
- """
- self.client = self.client_class()
- self._fixture_setup()
- self._urlconf_setup()
- mail.outbox = []
-
- def _databases_names(self, include_mirrors=True):
- # If the test case has a multi_db=True flag, act on all databases,
- # including mirrors or not. Otherwise, just on the default DB.
- if getattr(self, 'multi_db', False):
- return [alias for alias in connections
- if include_mirrors or not connections[alias].settings_dict['TEST_MIRROR']]
- else:
- return [DEFAULT_DB_ALIAS]
-
- def _reset_sequences(self, db_name):
- conn = connections[db_name]
- if conn.features.supports_sequence_reset:
- sql_list = \
- conn.ops.sequence_reset_by_name_sql(no_style(),
- conn.introspection.sequence_list())
- if sql_list:
- with transaction.commit_on_success_unless_managed(using=db_name):
- cursor = conn.cursor()
- for sql in sql_list:
- cursor.execute(sql)
-
- def _fixture_setup(self):
- for db_name in self._databases_names(include_mirrors=False):
- # Reset sequences
- if self.reset_sequences:
- self._reset_sequences(db_name)
-
- if hasattr(self, 'fixtures'):
- # We have to use this slightly awkward syntax due to the fact
- # that we're using *args and **kwargs together.
- call_command('loaddata', *self.fixtures,
- **{'verbosity': 0, 'database': db_name, 'skip_validation': True})
-
- def _urlconf_setup(self):
- set_urlconf(None)
- if hasattr(self, 'urls'):
- self._old_root_urlconf = settings.ROOT_URLCONF
- settings.ROOT_URLCONF = self.urls
- clear_url_caches()
-
- def _post_teardown(self):
- """ Performs any post-test things. This includes:
-
- * Putting back the original ROOT_URLCONF if it was changed.
- * Force closing the connection, so that the next test gets
- a clean cursor.
- """
- self._fixture_teardown()
- self._urlconf_teardown()
- # Some DB cursors include SQL statements as part of cursor
- # creation. If you have a test that does rollback, the effect
- # of these statements is lost, which can effect the operation
- # of tests (e.g., losing a timezone setting causing objects to
- # be created with the wrong time).
- # To make sure this doesn't happen, get a clean connection at the
- # start of every test.
- for conn in connections.all():
- conn.close()
-
- def _fixture_teardown(self):
- for db in self._databases_names(include_mirrors=False):
- call_command('flush', verbosity=0, interactive=False, database=db,
- skip_validation=True, reset_sequences=False)
-
- def _urlconf_teardown(self):
- set_urlconf(None)
- if hasattr(self, '_old_root_urlconf'):
- settings.ROOT_URLCONF = self._old_root_urlconf
- clear_url_caches()
-
def assertRedirects(self, response, expected_url, status_code=302,
target_status_code=200, host=None, msg_prefix=''):
"""Asserts that a response redirected to a specific URL, and that the
@@ -787,6 +560,236 @@ class TransactionTestCase(SimpleTestCase):
msg_prefix + "Template '%s' was used unexpectedly in rendering"
" the response" % template_name)
+ def assertRaisesMessage(self, expected_exception, expected_message,
+ callable_obj=None, *args, **kwargs):
+ """
+ Asserts that the message in a raised exception matches the passed
+ value.
+
+ Args:
+ expected_exception: Exception class expected to be raised.
+ expected_message: expected error message string value.
+ callable_obj: Function to be called.
+ args: Extra args.
+ kwargs: Extra kwargs.
+ """
+ return six.assertRaisesRegex(self, expected_exception,
+ re.escape(expected_message), callable_obj, *args, **kwargs)
+
+ def assertFieldOutput(self, fieldclass, valid, invalid, field_args=None,
+ field_kwargs=None, empty_value=''):
+ """
+ Asserts that a form field behaves correctly with various inputs.
+
+ Args:
+ fieldclass: the class of the field to be tested.
+ valid: a dictionary mapping valid inputs to their expected
+ cleaned values.
+ invalid: a dictionary mapping invalid inputs to one or more
+ raised error messages.
+ field_args: the args passed to instantiate the field
+ field_kwargs: the kwargs passed to instantiate the field
+ empty_value: the expected clean output for inputs in empty_values
+
+ """
+ if field_args is None:
+ field_args = []
+ if field_kwargs is None:
+ field_kwargs = {}
+ required = fieldclass(*field_args, **field_kwargs)
+ optional = fieldclass(*field_args,
+ **dict(field_kwargs, required=False))
+ # test valid inputs
+ for input, output in valid.items():
+ self.assertEqual(required.clean(input), output)
+ self.assertEqual(optional.clean(input), output)
+ # test invalid inputs
+ for input, errors in invalid.items():
+ with self.assertRaises(ValidationError) as context_manager:
+ required.clean(input)
+ self.assertEqual(context_manager.exception.messages, errors)
+
+ with self.assertRaises(ValidationError) as context_manager:
+ optional.clean(input)
+ self.assertEqual(context_manager.exception.messages, errors)
+ # test required inputs
+ error_required = [force_text(required.error_messages['required'])]
+ for e in required.empty_values:
+ with self.assertRaises(ValidationError) as context_manager:
+ required.clean(e)
+ self.assertEqual(context_manager.exception.messages,
+ error_required)
+ self.assertEqual(optional.clean(e), empty_value)
+ # test that max_length and min_length are always accepted
+ if issubclass(fieldclass, CharField):
+ field_kwargs.update({'min_length':2, 'max_length':20})
+ self.assertTrue(isinstance(fieldclass(*field_args, **field_kwargs),
+ fieldclass))
+
+ def assertHTMLEqual(self, html1, html2, msg=None):
+ """
+ Asserts that two HTML snippets are semantically the same.
+ Whitespace in most cases is ignored, and attribute ordering is not
+ significant. The passed-in arguments must be valid HTML.
+ """
+ dom1 = assert_and_parse_html(self, html1, msg,
+ 'First argument is not valid HTML:')
+ dom2 = assert_and_parse_html(self, html2, msg,
+ 'Second argument is not valid HTML:')
+
+ if dom1 != dom2:
+ standardMsg = '%s != %s' % (
+ safe_repr(dom1, True), safe_repr(dom2, True))
+ diff = ('\n' + '\n'.join(difflib.ndiff(
+ six.text_type(dom1).splitlines(),
+ six.text_type(dom2).splitlines())))
+ standardMsg = self._truncateMessage(standardMsg, diff)
+ self.fail(self._formatMessage(msg, standardMsg))
+
+ def assertHTMLNotEqual(self, html1, html2, msg=None):
+ """Asserts that two HTML snippets are not semantically equivalent."""
+ dom1 = assert_and_parse_html(self, html1, msg,
+ 'First argument is not valid HTML:')
+ dom2 = assert_and_parse_html(self, html2, msg,
+ 'Second argument is not valid HTML:')
+
+ if dom1 == dom2:
+ standardMsg = '%s == %s' % (
+ safe_repr(dom1, True), safe_repr(dom2, True))
+ self.fail(self._formatMessage(msg, standardMsg))
+
+ def assertInHTML(self, needle, haystack, count=None, msg_prefix=''):
+ needle = assert_and_parse_html(self, needle, None,
+ 'First argument is not valid HTML:')
+ haystack = assert_and_parse_html(self, haystack, None,
+ 'Second argument is not valid HTML:')
+ real_count = haystack.count(needle)
+ if count is not None:
+ self.assertEqual(real_count, count,
+ msg_prefix + "Found %d instances of '%s' in response"
+ " (expected %d)" % (real_count, needle, count))
+ else:
+ self.assertTrue(real_count != 0,
+ msg_prefix + "Couldn't find '%s' in response" % needle)
+
+ def assertJSONEqual(self, raw, expected_data, msg=None):
+ try:
+ data = json.loads(raw)
+ except ValueError:
+ self.fail("First argument is not valid JSON: %r" % raw)
+ if isinstance(expected_data, six.string_types):
+ try:
+ expected_data = json.loads(expected_data)
+ except ValueError:
+ self.fail("Second argument is not valid JSON: %r" % expected_data)
+ self.assertEqual(data, expected_data, msg=msg)
+
+ def assertXMLEqual(self, xml1, xml2, msg=None):
+ """
+ Asserts that two XML snippets are semantically the same.
+ Whitespace in most cases is ignored, and attribute ordering is not
+ significant. The passed-in arguments must be valid XML.
+ """
+ try:
+ result = compare_xml(xml1, xml2)
+ except Exception as e:
+ standardMsg = 'First or second argument is not valid XML\n%s' % e
+ self.fail(self._formatMessage(msg, standardMsg))
+ else:
+ if not result:
+ standardMsg = '%s != %s' % (safe_repr(xml1, True), safe_repr(xml2, True))
+ self.fail(self._formatMessage(msg, standardMsg))
+
+ def assertXMLNotEqual(self, xml1, xml2, msg=None):
+ """
+ Asserts that two XML snippets are not semantically equivalent.
+ Whitespace in most cases is ignored, and attribute ordering is not
+ significant. The passed-in arguments must be valid XML.
+ """
+ try:
+ result = compare_xml(xml1, xml2)
+ except Exception as e:
+ standardMsg = 'First or second argument is not valid XML\n%s' % e
+ self.fail(self._formatMessage(msg, standardMsg))
+ else:
+ if result:
+ standardMsg = '%s == %s' % (safe_repr(xml1, True), safe_repr(xml2, True))
+ self.fail(self._formatMessage(msg, standardMsg))
+
+
+class TransactionTestCase(SimpleTestCase):
+
+ # Subclasses can ask for resetting of auto increment sequence before each
+ # test case
+ reset_sequences = False
+
+ def _pre_setup(self):
+ """Performs any pre-test setup. This includes:
+
+ * Flushing the database.
+ * If the Test Case class has a 'fixtures' member, installing the
+ named fixtures.
+ """
+ super(TransactionTestCase, self)._pre_setup()
+ self._fixture_setup()
+
+ def _databases_names(self, include_mirrors=True):
+ # If the test case has a multi_db=True flag, act on all databases,
+ # including mirrors or not. Otherwise, just on the default DB.
+ if getattr(self, 'multi_db', False):
+ return [alias for alias in connections
+ if include_mirrors or not connections[alias].settings_dict['TEST_MIRROR']]
+ else:
+ return [DEFAULT_DB_ALIAS]
+
+ def _reset_sequences(self, db_name):
+ conn = connections[db_name]
+ if conn.features.supports_sequence_reset:
+ sql_list = \
+ conn.ops.sequence_reset_by_name_sql(no_style(),
+ conn.introspection.sequence_list())
+ if sql_list:
+ with transaction.commit_on_success_unless_managed(using=db_name):
+ cursor = conn.cursor()
+ for sql in sql_list:
+ cursor.execute(sql)
+
+ def _fixture_setup(self):
+ for db_name in self._databases_names(include_mirrors=False):
+ # Reset sequences
+ if self.reset_sequences:
+ self._reset_sequences(db_name)
+
+ if hasattr(self, 'fixtures'):
+ # We have to use this slightly awkward syntax due to the fact
+ # that we're using *args and **kwargs together.
+ call_command('loaddata', *self.fixtures,
+ **{'verbosity': 0, 'database': db_name, 'skip_validation': True})
+
+ def _post_teardown(self):
+ """Performs any post-test things. This includes:
+
+ * Putting back the original ROOT_URLCONF if it was changed.
+ * Force closing the connection, so that the next test gets
+ a clean cursor.
+ """
+ self._fixture_teardown()
+ super(TransactionTestCase, self)._post_teardown()
+ # Some DB cursors include SQL statements as part of cursor
+ # creation. If you have a test that does rollback, the effect
+ # of these statements is lost, which can effect the operation
+ # of tests (e.g., losing a timezone setting causing objects to
+ # be created with the wrong time).
+ # To make sure this doesn't happen, get a clean connection at the
+ # start of every test.
+ for conn in connections.all():
+ conn.close()
+
+ def _fixture_teardown(self):
+ for db_name in self._databases_names(include_mirrors=False):
+ call_command('flush', verbosity=0, interactive=False, database=db_name,
+ skip_validation=True, reset_sequences=False)
+
def assertQuerysetEqual(self, qs, values, transform=repr, ordered=True):
items = six.moves.map(transform, qs)
if not ordered:
@@ -841,15 +844,19 @@ class TestCase(TransactionTestCase):
# Remove this when the legacy transaction management goes away.
disable_transaction_methods()
- for db in self._databases_names(include_mirrors=False):
+ for db_name in self._databases_names(include_mirrors=False):
if hasattr(self, 'fixtures'):
- call_command('loaddata', *self.fixtures,
- **{
- 'verbosity': 0,
- 'commit': False,
- 'database': db,
- 'skip_validation': True,
- })
+ try:
+ call_command('loaddata', *self.fixtures,
+ **{
+ 'verbosity': 0,
+ 'commit': False,
+ 'database': db_name,
+ 'skip_validation': True,
+ })
+ except Exception:
+ self._fixture_teardown()
+ raise
def _fixture_teardown(self):
if not connections_support_transactions():
diff --git a/django/test/utils.py b/django/test/utils.py
index 92cef59f72..fb9221d25c 100644
--- a/django/test/utils.py
+++ b/django/test/utils.py
@@ -1,4 +1,5 @@
import re
+import sys
import warnings
from functools import wraps
from xml.dom.minidom import parseString, Node
@@ -380,3 +381,23 @@ class CaptureQueriesContext(object):
if exc_type is not None:
return
self.final_queries = len(self.connection.queries)
+
+
+class IgnoreDeprecationWarningsMixin(object):
+
+ warning_class = DeprecationWarning
+
+ def setUp(self):
+ super(IgnoreDeprecationWarningsMixin, self).setUp()
+ self.catch_warnings = warnings.catch_warnings()
+ self.catch_warnings.__enter__()
+ warnings.filterwarnings("ignore", category=self.warning_class)
+
+ def tearDown(self):
+ self.catch_warnings.__exit__(*sys.exc_info())
+ super(IgnoreDeprecationWarningsMixin, self).tearDown()
+
+
+class IgnorePendingDeprecationWarningsMixin(IgnoreDeprecationWarningsMixin):
+
+ warning_class = PendingDeprecationWarning
diff --git a/django/utils/html.py b/django/utils/html.py
index 8b28d97d13..edddc48e62 100644
--- a/django/utils/html.py
+++ b/django/utils/html.py
@@ -281,3 +281,10 @@ def clean_html(text):
text = trailing_empty_content_re.sub('', text)
return text
clean_html = allow_lazy(clean_html, six.text_type)
+
+def avoid_wrapping(value):
+ """
+ Avoid text wrapping in the middle of a phrase by adding non-breaking
+ spaces where there previously were normal spaces.
+ """
+ return value.replace(" ", "\xa0")
diff --git a/django/utils/http.py b/django/utils/http.py
index 9897df4fb0..f4911b4ec0 100644
--- a/django/utils/http.py
+++ b/django/utils/http.py
@@ -71,7 +71,7 @@ urlunquote_plus = allow_lazy(urlunquote_plus, six.text_type)
def urlencode(query, doseq=0):
"""
A version of Python's urllib.urlencode() function that can operate on
- unicode strings. The parameters are first case to UTF-8 encoded strings and
+ unicode strings. The parameters are first cast to UTF-8 encoded strings and
then encoded as per normal.
"""
if isinstance(query, MultiValueDict):
diff --git a/django/utils/timesince.py b/django/utils/timesince.py
index d70ab2ffe1..46c387f262 100644
--- a/django/utils/timesince.py
+++ b/django/utils/timesince.py
@@ -2,6 +2,7 @@ from __future__ import unicode_literals
import datetime
+from django.utils.html import avoid_wrapping
from django.utils.timezone import is_aware, utc
from django.utils.translation import ugettext, ungettext_lazy
@@ -40,18 +41,18 @@ def timesince(d, now=None, reversed=False):
since = delta.days * 24 * 60 * 60 + delta.seconds
if since <= 0:
# d is in the future compared to now, stop processing.
- return ugettext('0 minutes')
+ return avoid_wrapping(ugettext('0 minutes'))
for i, (seconds, name) in enumerate(chunks):
count = since // seconds
if count != 0:
break
- result = name % count
+ result = avoid_wrapping(name % count)
if i + 1 < len(chunks):
# Now get the second item
seconds2, name2 = chunks[i + 1]
count2 = (since - (seconds * count)) // seconds2
if count2 != 0:
- result += ugettext(', ') + name2 % count2
+ result += ugettext(', ') + avoid_wrapping(name2 % count2)
return result
def timeuntil(d, now=None):
diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py
index 07353c35ee..26be0ed729 100644
--- a/django/utils/translation/trans_real.py
+++ b/django/utils/translation/trans_real.py
@@ -140,7 +140,7 @@ def translation(language):
# doesn't affect en-gb), even though they will both use the core "en"
# translation. So we have to subvert Python's internal gettext caching.
base_lang = lambda x: x.split('-', 1)[0]
- if base_lang(lang) in [base_lang(trans) for trans in _translations]:
+ if base_lang(lang) in [base_lang(trans) for trans in list(_translations)]:
res._info = res._info.copy()
res._catalog = res._catalog.copy()
@@ -364,10 +364,14 @@ def get_supported_language_variant(lang_code, supported=None):
if supported is None:
from django.conf import settings
supported = dict(settings.LANGUAGES)
- if lang_code and lang_code not in supported:
- lang_code = lang_code.split('-')[0] # e.g. if fr-ca is not supported fallback to fr
- if lang_code and lang_code in supported and check_for_language(lang_code):
- return lang_code
+ if lang_code:
+ # e.g. if fr-CA is not supported, try fr-ca;
+ # if that fails, fallback to fr.
+ variants = (lang_code, lang_code.lower(), lang_code.split('-')[0],
+ lang_code.lower().split('-')[0])
+ for code in variants:
+ if code in supported and check_for_language(code):
+ return code
raise LookupError(lang_code)
def get_language_from_path(path, supported=None):
@@ -438,14 +442,13 @@ def get_language_from_request(request, check_path=False):
# need to check again.
return _accepted[normalized]
- for lang, dirname in ((accept_lang, normalized),
- (accept_lang.split('-')[0], normalized.split('_')[0])):
- if lang.lower() not in supported:
- continue
- for path in all_locale_paths():
- if os.path.exists(os.path.join(path, dirname, 'LC_MESSAGES', 'django.mo')):
- _accepted[normalized] = lang
- return lang
+ try:
+ accept_lang = get_supported_language_variant(accept_lang, supported)
+ except LookupError:
+ continue
+ else:
+ _accepted[normalized] = accept_lang
+ return accept_lang
try:
return get_supported_language_variant(settings.LANGUAGE_CODE, supported)
diff --git a/django/views/debug.py b/django/views/debug.py
index 9b95b524d2..10c07e8f78 100644
--- a/django/views/debug.py
+++ b/django/views/debug.py
@@ -347,7 +347,7 @@ class ExceptionReporter(object):
if source is None:
try:
with open(filename, 'rb') as fp:
- source = fp.readlines()
+ source = fp.read().splitlines()
except (OSError, IOError):
pass
if source is None:
@@ -370,9 +370,9 @@ class ExceptionReporter(object):
lower_bound = max(0, lineno - context_lines)
upper_bound = lineno + context_lines
- pre_context = [line.strip('\n') for line in source[lower_bound:lineno]]
- context_line = source[lineno].strip('\n')
- post_context = [line.strip('\n') for line in source[lineno+1:upper_bound]]
+ pre_context = source[lower_bound:lineno]
+ context_line = source[lineno]
+ post_context = source[lineno+1:upper_bound]
return lower_bound, pre_context, context_line, post_context
diff --git a/django/views/decorators/csrf.py b/django/views/decorators/csrf.py
index 7a7eb6bba6..a6bd7d8526 100644
--- a/django/views/decorators/csrf.py
+++ b/django/views/decorators/csrf.py
@@ -15,7 +15,7 @@ using the decorator multiple times, is harmless and efficient.
class _EnsureCsrfToken(CsrfViewMiddleware):
# We need this to behave just like the CsrfViewMiddleware, but not reject
- # requests.
+ # requests or log warnings.
def _reject(self, request, reason):
return None
diff --git a/django/views/generic/detail.py b/django/views/generic/detail.py
index 58302bbe23..23000641b4 100644
--- a/django/views/generic/detail.py
+++ b/django/views/generic/detail.py
@@ -93,9 +93,11 @@ class SingleObjectMixin(ContextMixin):
Insert the single object into the context dict.
"""
context = {}
- context_object_name = self.get_context_object_name(self.object)
- if context_object_name:
- context[context_object_name] = self.object
+ if self.object:
+ context['object'] = self.object
+ context_object_name = self.get_context_object_name(self.object)
+ if context_object_name:
+ context[context_object_name] = self.object
context.update(kwargs)
return super(SingleObjectMixin, self).get_context_data(**context)
@@ -122,7 +124,7 @@ class SingleObjectTemplateResponseMixin(TemplateResponseMixin):
* the value of ``template_name`` on the view (if provided)
* the contents of the ``template_name_field`` field on the
object instance that the view is operating upon (if available)
- * ``<app_label>/<object_name><template_name_suffix>.html``
+ * ``<app_label>/<object_name><template_name_suffix>.html``
"""
try:
names = super(SingleObjectTemplateResponseMixin, self).get_template_names()
diff --git a/django/views/generic/edit.py b/django/views/generic/edit.py
index e2cc741ffb..cf87aeed27 100644
--- a/django/views/generic/edit.py
+++ b/django/views/generic/edit.py
@@ -136,20 +136,6 @@ class ModelFormMixin(FormMixin, SingleObjectMixin):
self.object = form.save()
return super(ModelFormMixin, self).form_valid(form)
- def get_context_data(self, **kwargs):
- """
- If an object has been supplied, inject it into the context with the
- supplied context_object_name name.
- """
- context = {}
- if self.object:
- context['object'] = self.object
- context_object_name = self.get_context_object_name(self.object)
- if context_object_name:
- context[context_object_name] = self.object
- context.update(kwargs)
- return super(ModelFormMixin, self).get_context_data(**context)
-
class ProcessFormView(View):
"""
diff --git a/django/views/generic/list.py b/django/views/generic/list.py
index 08c4bbcda0..1aff3454f4 100644
--- a/django/views/generic/list.py
+++ b/django/views/generic/list.py
@@ -105,7 +105,7 @@ class MultipleObjectMixin(ContextMixin):
"""
Get the context for this view.
"""
- queryset = kwargs.pop('object_list')
+ queryset = kwargs.pop('object_list', self.object_list)
page_size = self.get_paginate_by(queryset)
context_object_name = self.get_context_object_name(queryset)
if page_size:
@@ -149,7 +149,7 @@ class BaseListView(MultipleObjectMixin, View):
if is_empty:
raise Http404(_("Empty list and '%(class_name)s.allow_empty' is False.")
% {'class_name': self.__class__.__name__})
- context = self.get_context_data(object_list=self.object_list)
+ context = self.get_context_data()
return self.render_to_response(context)
diff --git a/django/views/i18n.py b/django/views/i18n.py
index 37ec10b552..71ac005855 100644
--- a/django/views/i18n.py
+++ b/django/views/i18n.py
@@ -184,38 +184,8 @@ def render_javascript_catalog(catalog=None, plural=None):
return http.HttpResponse(template.render(context), 'text/javascript')
-def null_javascript_catalog(request, domain=None, packages=None):
- """
- Returns "identity" versions of the JavaScript i18n functions -- i.e.,
- versions that don't actually do anything.
- """
- return render_javascript_catalog()
-
-
-def javascript_catalog(request, domain='djangojs', packages=None):
- """
- Returns the selected language catalog as a javascript library.
-
- Receives the list of packages to check for translations in the
- packages parameter either from an infodict or as a +-delimited
- string from the request. Default is 'django.conf'.
-
- Additionally you can override the gettext domain for this view,
- but usually you don't want to do that, as JavaScript messages
- go to the djangojs domain. But this might be needed if you
- deliver your JavaScript source from Django templates.
- """
+def get_javascript_catalog(locale, domain, packages):
default_locale = to_locale(settings.LANGUAGE_CODE)
- locale = to_locale(get_language())
-
- if request.GET and 'language' in request.GET:
- if check_for_language(request.GET['language']):
- locale = to_locale(request.GET['language'])
-
- if packages is None:
- packages = ['django.conf']
- if isinstance(packages, six.string_types):
- packages = packages.split('+')
packages = [p for p in packages if p == 'django.conf' or p in settings.INSTALLED_APPS]
t = {}
paths = []
@@ -296,4 +266,40 @@ def javascript_catalog(request, domain='djangojs', packages=None):
for k, v in pdict.items():
catalog[k] = [v.get(i, '') for i in range(maxcnts[msgid] + 1)]
+ return catalog, plural
+
+
+def null_javascript_catalog(request, domain=None, packages=None):
+ """
+ Returns "identity" versions of the JavaScript i18n functions -- i.e.,
+ versions that don't actually do anything.
+ """
+ return render_javascript_catalog()
+
+
+def javascript_catalog(request, domain='djangojs', packages=None):
+ """
+ Returns the selected language catalog as a javascript library.
+
+ Receives the list of packages to check for translations in the
+ packages parameter either from an infodict or as a +-delimited
+ string from the request. Default is 'django.conf'.
+
+ Additionally you can override the gettext domain for this view,
+ but usually you don't want to do that, as JavaScript messages
+ go to the djangojs domain. But this might be needed if you
+ deliver your JavaScript source from Django templates.
+ """
+ locale = to_locale(get_language())
+
+ if request.GET and 'language' in request.GET:
+ if check_for_language(request.GET['language']):
+ locale = to_locale(request.GET['language'])
+
+ if packages is None:
+ packages = ['django.conf']
+ if isinstance(packages, six.string_types):
+ packages = packages.split('+')
+
+ catalog, plural = get_javascript_catalog(locale, domain, packages)
return render_javascript_catalog(catalog, plural)
diff --git a/docs/faq/admin.txt b/docs/faq/admin.txt
index 1d9a7c7427..ec40754094 100644
--- a/docs/faq/admin.txt
+++ b/docs/faq/admin.txt
@@ -27,12 +27,6 @@ account has :attr:`~django.contrib.auth.models.User.is_active` and
:attr:`~django.contrib.auth.models.User.is_staff` set to True. The admin site
only allows access to users with those two fields both set to True.
-How can I prevent the cache middleware from caching the admin site?
--------------------------------------------------------------------
-
-Set the :setting:`CACHE_MIDDLEWARE_ANONYMOUS_ONLY` setting to ``True``. See the
-:doc:`cache documentation </topics/cache>` for more information.
-
How do I automatically set a field's value to the user who last edited the object in the admin?
-----------------------------------------------------------------------------------------------
diff --git a/docs/howto/deployment/wsgi/uwsgi.txt b/docs/howto/deployment/wsgi/uwsgi.txt
index 5b40d5f2f7..22f39342d6 100644
--- a/docs/howto/deployment/wsgi/uwsgi.txt
+++ b/docs/howto/deployment/wsgi/uwsgi.txt
@@ -62,7 +62,6 @@ Here's an example command to start a uWSGI server::
--processes=5 \ # number of worker processes
--uid=1000 --gid=2000 \ # if root, uwsgi can drop privileges
--harakiri=20 \ # respawn processes taking more than 20 seconds
- --limit-as=128 \ # limit the project to 128 MB
--max-requests=5000 \ # respawn processes after serving 5000 requests
--vacuum \ # clear environment on exit
--home=/path/to/virtual/env \ # optional path to a virtualenv
diff --git a/docs/howto/static-files/index.txt b/docs/howto/static-files/index.txt
index 1fdad94143..3668c5dc41 100644
--- a/docs/howto/static-files/index.txt
+++ b/docs/howto/static-files/index.txt
@@ -35,8 +35,20 @@ Configuring static files
4. Store your static files in a folder called ``static`` in your app. For
example ``my_app/static/my_app/myimage.jpg``.
-Now, if you use ``./manage.py runserver``, all static files should be served
-automatically at the :setting:`STATIC_URL` and be shown correctly.
+.. admonition:: Serving the files
+
+ In addition to these configuration steps, you'll also need to actually
+ serve the static files.
+
+ During development, this will be done automatically if you use
+ :djadmin:`runserver` and :setting:`DEBUG` is set to ``True`` (see
+ :func:`django.contrib.staticfiles.views.serve`).
+
+ This method is **grossly inefficient** and probably **insecure**,
+ so it is **unsuitable for production**.
+
+ See :doc:`/howto/static-files/deployment` for proper strategies to serve
+ static files in production environments.
Your project will probably also have static assets that aren't tied to a
particular app. In addition to using a ``static/`` directory inside your apps,
diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt
index 774de2a2fd..095b6d0a33 100644
--- a/docs/internals/deprecation.txt
+++ b/docs/internals/deprecation.txt
@@ -390,6 +390,8 @@ these changes.
``django.test.testcases.OutputChecker`` will be removed. Instead use the
doctest module from the Python standard library.
+* The ``CACHE_MIDDLEWARE_ANONYMOUS_ONLY`` setting will be removed.
+
2.0
---
diff --git a/docs/intro/overview.txt b/docs/intro/overview.txt
index 8753817256..1a6c8fa19a 100644
--- a/docs/intro/overview.txt
+++ b/docs/intro/overview.txt
@@ -24,6 +24,8 @@ representing your models -- so far, it's been solving two years' worth of
database-schema problems. Here's a quick example, which might be saved in
the file ``mysite/news/models.py``::
+ from django.db import models
+
class Reporter(models.Model):
full_name = models.CharField(max_length=70)
@@ -214,6 +216,8 @@ Generally, a view retrieves data according to the parameters, loads a template
and renders the template with the retrieved data. Here's an example view for
``year_archive`` from above::
+ from django.shortcuts import render_to_response
+
def year_archive(request, year):
a_list = Article.objects.filter(pub_date__year=year)
return render_to_response('news/year_archive.html', {'year': year, 'article_list': a_list})
diff --git a/docs/intro/tutorial01.txt b/docs/intro/tutorial01.txt
index d623bd8451..6e5988b15a 100644
--- a/docs/intro/tutorial01.txt
+++ b/docs/intro/tutorial01.txt
@@ -582,6 +582,8 @@ of this object. Let's fix that by editing the polls model (in the
``Choice``. On Python 3, simply replace ``__unicode__`` by ``__str__`` in the
following example::
+ from django.db import models
+
class Poll(models.Model):
# ...
def __unicode__(self): # Python 3: def __str__(self):
diff --git a/docs/intro/tutorial02.txt b/docs/intro/tutorial02.txt
index 1987c51a67..dd3e86d8ae 100644
--- a/docs/intro/tutorial02.txt
+++ b/docs/intro/tutorial02.txt
@@ -158,6 +158,9 @@ you want when you register the object.
Let's see how this works by re-ordering the fields on the edit form. Replace
the ``admin.site.register(Poll)`` line with::
+ from django.contrib import admin
+ from polls.models import Poll
+
class PollAdmin(admin.ModelAdmin):
fields = ['pub_date', 'question']
@@ -179,6 +182,9 @@ of fields, choosing an intuitive order is an important usability detail.
And speaking of forms with dozens of fields, you might want to split the form
up into fieldsets::
+ from django.contrib import admin
+ from polls.models import Poll
+
class PollAdmin(admin.ModelAdmin):
fieldsets = [
(None, {'fields': ['question']}),
@@ -198,6 +204,9 @@ You can assign arbitrary HTML classes to each fieldset. Django provides a
This is useful when you have a long form that contains a number of fields that
aren't commonly used::
+ from django.contrib import admin
+ from polls.models import Poll
+
class PollAdmin(admin.ModelAdmin):
fieldsets = [
(None, {'fields': ['question']}),
@@ -218,6 +227,7 @@ Yet.
There are two ways to solve this problem. The first is to register ``Choice``
with the admin just as we did with ``Poll``. That's easy::
+ from django.contrib import admin
from polls.models import Choice
admin.site.register(Choice)
@@ -342,6 +352,12 @@ representation of the output.
You can improve that by giving that method (in :file:`polls/models.py`) a few
attributes, as follows::
+ import datetime
+ from django.utils import timezone
+ from django.db import models
+
+ from polls.models import Poll
+
class Poll(models.Model):
# ...
def was_published_recently(self):
diff --git a/docs/intro/tutorial03.txt b/docs/intro/tutorial03.txt
index 86cc5f97e6..0bbfcdd02f 100644
--- a/docs/intro/tutorial03.txt
+++ b/docs/intro/tutorial03.txt
@@ -393,6 +393,9 @@ Now, let's tackle the poll detail view -- the page that displays the question
for a given poll. Here's the view::
from django.http import Http404
+ from django.shortcuts import render
+
+ from polls.models import Poll
# ...
def detail(request, poll_id):
try:
@@ -420,6 +423,8 @@ and raise :exc:`~django.http.Http404` if the object doesn't exist. Django
provides a shortcut. Here's the ``detail()`` view, rewritten::
from django.shortcuts import render, get_object_or_404
+
+ from polls.models import Poll
# ...
def detail(request, poll_id):
poll = get_object_or_404(Poll, pk=poll_id)
diff --git a/docs/intro/tutorial04.txt b/docs/intro/tutorial04.txt
index 9f54243a3e..f81a7d6758 100644
--- a/docs/intro/tutorial04.txt
+++ b/docs/intro/tutorial04.txt
@@ -136,6 +136,8 @@ object. For more on :class:`~django.http.HttpRequest` objects, see the
After somebody votes in a poll, the ``vote()`` view redirects to the results
page for the poll. Let's write that view::
+ from django.shortcuts import get_object_or_404, render
+
def results(request, poll_id):
poll = get_object_or_404(Poll, pk=poll_id)
return render(request, 'polls/results.html', {'poll': poll})
diff --git a/docs/intro/tutorial05.txt b/docs/intro/tutorial05.txt
index a276763d67..39c3785f7c 100644
--- a/docs/intro/tutorial05.txt
+++ b/docs/intro/tutorial05.txt
@@ -503,8 +503,8 @@ of the process of creating polls.
message: "No polls are available." and verifies the ``latest_poll_list`` is
empty. Note that the :class:`django.test.TestCase` class provides some
additional assertion methods. In these examples, we use
-:meth:`~django.test.TestCase.assertContains()` and
-:meth:`~django.test.TestCase.assertQuerysetEqual()`.
+:meth:`~django.test.SimpleTestCase.assertContains()` and
+:meth:`~django.test.TransactionTestCase.assertQuerysetEqual()`.
In ``test_index_view_with_a_past_poll``, we create a poll and verify that it
appears in the list.
diff --git a/docs/ref/contrib/contenttypes.txt b/docs/ref/contrib/contenttypes.txt
index 4fa119bc70..1bb0802442 100644
--- a/docs/ref/contrib/contenttypes.txt
+++ b/docs/ref/contrib/contenttypes.txt
@@ -329,7 +329,7 @@ model:
.. admonition:: Serializing references to ``ContentType`` objects
If you're serializing data (for example, when generating
- :class:`~django.test.TestCase.fixtures`) from a model that implements
+ :class:`~django.test.TransactionTestCase.fixtures`) from a model that implements
generic relations, you should probably be using a natural key to uniquely
identify related :class:`~django.contrib.contenttypes.models.ContentType`
objects. See :ref:`natural keys<topics-serialization-natural-keys>` and
diff --git a/docs/ref/contrib/gis/install/create_template_postgis-1.5.sh b/docs/ref/contrib/gis/install/create_template_postgis-1.5.sh
index 081b5f2656..67c82a8b25 100755
--- a/docs/ref/contrib/gis/install/create_template_postgis-1.5.sh
+++ b/docs/ref/contrib/gis/install/create_template_postgis-1.5.sh
@@ -1,9 +1,15 @@
#!/usr/bin/env bash
-POSTGIS_SQL_PATH=`pg_config --sharedir`/contrib/postgis-1.5
+if [[ `uname -r | grep el6` ]]; then
+ POSTGIS_SQL_PATH=`pg_config --sharedir`/contrib/postgis
+ POSTGIS_SQL_FILE=$POSTGIS_SQL_PATH/postgis-64.sql
+else
+ POSTGIS_SQL_PATH=`pg_config --sharedir`/contrib/postgis-1.5
+ POSTGIS_SQL_FILE=$POSTGIS_SQL_PATH/postgis.sql
+fi
createdb -E UTF8 template_postgis # Create the template spatial database.
createlang -d template_postgis plpgsql # Adding PLPGSQL language support.
psql -d postgres -c "UPDATE pg_database SET datistemplate='true' WHERE datname='template_postgis';"
-psql -d template_postgis -f $POSTGIS_SQL_PATH/postgis.sql # Loading the PostGIS SQL routines
+psql -d template_postgis -f $POSTGIS_SQL_FILE # Loading the PostGIS SQL routines
psql -d template_postgis -f $POSTGIS_SQL_PATH/spatial_ref_sys.sql
psql -d template_postgis -c "GRANT ALL ON geometry_columns TO PUBLIC;" # Enabling users to alter spatial tables.
psql -d template_postgis -c "GRANT ALL ON geography_columns TO PUBLIC;"
diff --git a/docs/ref/contrib/sitemaps.txt b/docs/ref/contrib/sitemaps.txt
index d37ee83378..56a15cb9e0 100644
--- a/docs/ref/contrib/sitemaps.txt
+++ b/docs/ref/contrib/sitemaps.txt
@@ -280,6 +280,46 @@ Here's an example of a :doc:`URLconf </topics/http/urls>` using both::
.. _URLconf: ../url_dispatch/
+Sitemap for static views
+========================
+
+Often you want the search engine crawlers to index views which are neither
+object detail pages nor flatpages. The solution is to explicitly list URL
+names for these views in ``items`` and call
+:func:`~django.core.urlresolvers.reverse` in the ``location`` method of
+the sitemap. For example::
+
+ # sitemaps.py
+ from django.contrib import sitemaps
+ from django.core.urlresolvers import reverse
+
+ class StaticViewSitemap(sitemaps.Sitemap):
+ priority = 0.5
+ changefreq = 'daily'
+
+ def items(self):
+ return ['main', 'about', 'license']
+
+ def location(self, item):
+ return reverse(item)
+
+ # urls.py
+ from django.conf.urls import patterns, url
+ from .sitemaps import StaticViewSitemap
+
+ sitemaps = {
+ 'static': StaticViewSitemap,
+ }
+
+ urlpatterns = patterns('',
+ url(r'^$', 'views.main', name='main'),
+ url(r'^about/$', 'views.about', name='about'),
+ url(r'^license/$', 'views.license', name='license'),
+ # ...
+ url(r'^sitemap\.xml$', 'django.contrib.sitemaps.views.sitemap', {'sitemaps': sitemaps})
+ )
+
+
Creating a sitemap index
========================
diff --git a/docs/ref/forms/models.txt b/docs/ref/forms/models.txt
index 9b3480758a..54d34160a5 100644
--- a/docs/ref/forms/models.txt
+++ b/docs/ref/forms/models.txt
@@ -5,7 +5,7 @@ Model Form Functions
.. module:: django.forms.models
:synopsis: Django's functions for building model forms and formsets.
-.. function:: modelform_factory(model, form=ModelForm, fields=None, exclude=None, formfield_callback=None, widgets=None)
+.. function:: modelform_factory(model, form=ModelForm, fields=None, exclude=None, formfield_callback=None, widgets=None, localized_fields=None)
Returns a :class:`~django.forms.ModelForm` class for the given ``model``.
You can optionally pass a ``form`` argument to use as a starting point for
@@ -20,6 +20,8 @@ Model Form Functions
``widgets`` is a dictionary of model field names mapped to a widget.
+ ``localized_fields`` is a list of names of fields which should be localized.
+
``formfield_callback`` is a callable that takes a model field and returns
a form field.
@@ -33,12 +35,14 @@ Model Form Functions
information. Omitting any definition of the fields to use will result in all
fields being used, but this behaviour is deprecated.
-.. function:: modelformset_factory(model, form=ModelForm, formfield_callback=None, formset=BaseModelFormSet, extra=1, can_delete=False, can_order=False, max_num=None, fields=None, exclude=None, widgets=None, validate_max=False)
+ The ``localized_fields`` parameter was added.
+
+.. function:: modelformset_factory(model, form=ModelForm, formfield_callback=None, formset=BaseModelFormSet, extra=1, can_delete=False, can_order=False, max_num=None, fields=None, exclude=None, widgets=None, validate_max=False, localized_fields=None)
Returns a ``FormSet`` class for the given ``model`` class.
Arguments ``model``, ``form``, ``fields``, ``exclude``,
- ``formfield_callback`` and ``widgets`` are all passed through to
+ ``formfield_callback``, ``widgets`` and ``localized_fields`` are all passed through to
:func:`~django.forms.models.modelform_factory`.
Arguments ``formset``, ``extra``, ``max_num``, ``can_order``,
@@ -50,9 +54,9 @@ Model Form Functions
.. versionchanged:: 1.6
- The ``widgets`` and the ``validate_max`` parameters were added.
+ The ``widgets``, ``validate_max`` and ``localized_fields`` parameters were added.
-.. function:: inlineformset_factory(parent_model, model, form=ModelForm, formset=BaseInlineFormSet, fk_name=None, fields=None, exclude=None, extra=3, can_order=False, can_delete=True, max_num=None, formfield_callback=None, widgets=None, validate_max=False)
+.. function:: inlineformset_factory(parent_model, model, form=ModelForm, formset=BaseInlineFormSet, fk_name=None, fields=None, exclude=None, extra=3, can_order=False, can_delete=True, max_num=None, formfield_callback=None, widgets=None, validate_max=False, localized_fields=None)
Returns an ``InlineFormSet`` using :func:`modelformset_factory` with
defaults of ``formset=BaseInlineFormSet``, ``can_delete=True``, and
@@ -65,4 +69,4 @@ Model Form Functions
.. versionchanged:: 1.6
- The ``widgets`` and the ``validate_max`` parameters were added.
+ The ``widgets``, ``validate_max`` and ``localized_fields`` parameters were added.
diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt
index 99ba78cb09..c5f2609bab 100644
--- a/docs/ref/models/fields.txt
+++ b/docs/ref/models/fields.txt
@@ -80,9 +80,10 @@ If a field has ``blank=False``, the field will be required.
.. attribute:: Field.choices
-An iterable (e.g., a list or tuple) of 2-tuples to use as choices for this
-field. If this is given, the default form widget will be a select box with
-these choices instead of the standard text field.
+An iterable (e.g., a list or tuple) consisting itself of iterables of exactly
+two items (e.g. ``[(A, B), (A, B) ...]``) to use as choices for this field. If
+this is given, the default form widget will be a select box with these choices
+instead of the standard text field.
The first element in each tuple is the actual value to be stored, and the
second element is the human-readable name. For example::
@@ -97,6 +98,8 @@ second element is the human-readable name. For example::
Generally, it's best to define choices inside a model class, and to
define a suitably-named constant for each value::
+ from django.db import models
+
class Student(models.Model):
FRESHMAN = 'FR'
SOPHOMORE = 'SO'
@@ -889,7 +892,8 @@ The value ``0`` is accepted for backward compatibility reasons.
.. class:: PositiveSmallIntegerField([**options])
Like a :class:`PositiveIntegerField`, but only allows values under a certain
-(database-dependent) point.
+(database-dependent) point. Values up to 32767 are safe in all databases
+supported by Django.
``SlugField``
-------------
@@ -917,7 +921,8 @@ of some other value. You can do this automatically in the admin using
.. class:: SmallIntegerField([**options])
Like an :class:`IntegerField`, but only allows values under a certain
-(database-dependent) point.
+(database-dependent) point. Values from -32768 to 32767 are safe in all databases
+supported by Django.
``TextField``
-------------
@@ -994,12 +999,15 @@ relationship with itself -- use ``models.ForeignKey('self')``.
If you need to create a relationship on a model that has not yet been defined,
you can use the name of the model, rather than the model object itself::
+ from django.db import models
+
class Car(models.Model):
manufacturer = models.ForeignKey('Manufacturer')
# ...
class Manufacturer(models.Model):
# ...
+ pass
To refer to models defined in another application, you can explicitly specify
a model with the full application label. For example, if the ``Manufacturer``
@@ -1132,6 +1140,9 @@ The possible values for :attr:`~ForeignKey.on_delete` are found in
necessary to avoid executing queries at the time your models.py is
imported::
+ from django.db import models
+ from django.contrib.auth.models import User
+
def get_sentinel_user():
return User.objects.get_or_create(username='deleted')[0]
@@ -1204,6 +1215,8 @@ that control how the relationship functions.
Only used in the definition of ManyToManyFields on self. Consider the
following model::
+ from django.db import models
+
class Person(models.Model):
friends = models.ManyToManyField("self")
diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt
index b4b162a9ea..f989ff1bec 100644
--- a/docs/ref/models/instances.txt
+++ b/docs/ref/models/instances.txt
@@ -34,6 +34,8 @@ that, you need to :meth:`~Model.save()`.
1. Add a classmethod on the model class::
+ from django.db import models
+
class Book(models.Model):
title = models.CharField(max_length=100)
@@ -105,6 +107,7 @@ individually.
You'll need to call ``full_clean`` manually when you want to run one-step model
validation for your own manually created models. For example::
+ from django.core.exceptions import ValidationError
try:
article.full_clean()
except ValidationError as e:
@@ -132,6 +135,7 @@ automatically provide a value for a field, or to do validation that requires
access to more than a single field::
def clean(self):
+ import datetime
from django.core.exceptions import ValidationError
# Don't allow draft entries to have a pub_date.
if self.status == 'draft' and self.pub_date is not None:
@@ -434,6 +438,8 @@ representation of the model from the ``__unicode__()`` method.
For example::
+ from django.db import models
+
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
@@ -460,6 +466,9 @@ Thus, you should return a nice, human-readable string for the object's
The previous :meth:`~Model.__unicode__()` example could be similarly written
using ``__str__()`` like this::
+ from django.db import models
+ from django.utils.encoding import force_bytes
+
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
@@ -490,6 +499,7 @@ function is usually the best approach.)
For example::
def get_absolute_url(self):
+ from django.core.urlresolvers import reverse
return reverse('people.views.details', args=[str(self.id)])
One place Django uses ``get_absolute_url()`` is in the admin app. If an object
diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt
index 5f9316bd2a..90099d13a3 100644
--- a/docs/ref/models/options.txt
+++ b/docs/ref/models/options.txt
@@ -145,6 +145,12 @@ Django quotes column and table names behind the scenes.
and a question has more than one answer, and the order of answers matters, you'd
do this::
+ from django.db import models
+
+ class Question(models.Model):
+ text = models.TextField()
+ # ...
+
class Answer(models.Model):
question = models.ForeignKey(Question)
# ...
diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt
index 14123cd79a..9677b321c6 100644
--- a/docs/ref/models/querysets.txt
+++ b/docs/ref/models/querysets.txt
@@ -232,6 +232,7 @@ the model field that is being aggregated.
For example, if you were manipulating a list of blogs, you may want
to determine how many entries have been made in each blog::
+ >>> from django.db.models import Count
>>> q = Blog.objects.annotate(Count('entry'))
# The name of the first blog
>>> q[0].name
@@ -699,6 +700,8 @@ And here's ``select_related`` lookup::
``select_related()`` follows foreign keys as far as possible. If you have the
following models::
+ from django.db import models
+
class City(models.Model):
# ...
pass
@@ -814,6 +817,8 @@ that are supported by ``select_related``. It also supports prefetching of
For example, suppose you have these models::
+ from django.db import models
+
class Topping(models.Model):
name = models.CharField(max_length=30)
@@ -1565,6 +1570,7 @@ aggregated.
For example, when you are working with blog entries, you may want to know the
number of authors that have contributed blog entries::
+ >>> from django.db.models import Count
>>> q = Blog.objects.aggregate(Count('entry'))
{'entry__count': 16}
@@ -2042,6 +2048,7 @@ Range test (inclusive).
Example::
+ import datetime
start_date = datetime.date(2005, 1, 1)
end_date = datetime.date(2005, 3, 31)
Entry.objects.filter(pub_date__range=(start_date, end_date))
diff --git a/docs/ref/models/relations.txt b/docs/ref/models/relations.txt
index c923961a19..ffebe37193 100644
--- a/docs/ref/models/relations.txt
+++ b/docs/ref/models/relations.txt
@@ -12,8 +12,11 @@ Related objects reference
* The "other side" of a :class:`~django.db.models.ForeignKey` relation.
That is::
+ from django.db import models
+
class Reporter(models.Model):
- ...
+ # ...
+ pass
class Article(models.Model):
reporter = models.ForeignKey(Reporter)
@@ -24,7 +27,8 @@ Related objects reference
* Both sides of a :class:`~django.db.models.ManyToManyField` relation::
class Topping(models.Model):
- ...
+ # ...
+ pass
class Pizza(models.Model):
toppings = models.ManyToManyField(Topping)
diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt
index 2fac7f2f9c..fc26eabf1a 100644
--- a/docs/ref/request-response.txt
+++ b/docs/ref/request-response.txt
@@ -578,20 +578,20 @@ streaming response if (and only if) no middleware accesses the
instantiated with an iterator. Django will consume and save the content of
the iterator on first access.
-Setting headers
-~~~~~~~~~~~~~~~
+Setting header fields
+~~~~~~~~~~~~~~~~~~~~~
-To set or remove a header in your response, treat it like a dictionary::
+To set or remove a header field in your response, treat it like a dictionary::
>>> response = HttpResponse()
>>> response['Cache-Control'] = 'no-cache'
>>> del response['Cache-Control']
Note that unlike a dictionary, ``del`` doesn't raise ``KeyError`` if the header
-doesn't exist.
+field doesn't exist.
-HTTP headers cannot contain newlines. An attempt to set a header containing a
-newline character (CR or LF) will raise ``BadHeaderError``
+HTTP header fields cannot contain newlines. An attempt to set a header field
+containing a newline character (CR or LF) will raise ``BadHeaderError``
Telling the browser to treat the response as a file attachment
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
index eb470cdd14..8ef59064f7 100644
--- a/docs/ref/settings.txt
+++ b/docs/ref/settings.txt
@@ -280,6 +280,12 @@ CACHE_MIDDLEWARE_ANONYMOUS_ONLY
Default: ``False``
+.. deprecated:: 1.6
+
+ This setting was largely ineffective because of using cookies for sessions
+ and CSRF. See the :doc:`Django 1.6 release notes</releases/1.6>` for more
+ information.
+
If the value of this setting is ``True``, only anonymous requests (i.e., not
those made by a logged-in user) will be cached. Otherwise, the middleware
caches every page that doesn't have GET or POST parameters.
@@ -287,8 +293,6 @@ caches every page that doesn't have GET or POST parameters.
If you set the value of this setting to ``True``, you should make sure you've
activated ``AuthenticationMiddleware``.
-See :doc:`/topics/cache`.
-
.. setting:: CACHE_MIDDLEWARE_KEY_PREFIX
CACHE_MIDDLEWARE_KEY_PREFIX
diff --git a/docs/ref/template-response.txt b/docs/ref/template-response.txt
index cdefe2fae8..4f34d150ed 100644
--- a/docs/ref/template-response.txt
+++ b/docs/ref/template-response.txt
@@ -215,6 +215,7 @@ re-rendered, you can re-evaluate the rendered content, and assign
the content of the response manually::
# Set up a rendered TemplateResponse
+ >>> from django.template.response import TemplateResponse
>>> t = TemplateResponse(request, 'original.html', {})
>>> t.render()
>>> print(t.content)
@@ -256,6 +257,8 @@ To define a post-render callback, just define a function that takes
a single argument -- response -- and register that function with
the template response::
+ from django.template.response import TemplateResponse
+
def my_render_callback(response):
# Do content-sensitive processing
do_post_processing()
diff --git a/docs/releases/1.3-alpha-1.txt b/docs/releases/1.3-alpha-1.txt
index 42947d9a44..634e6afaf2 100644
--- a/docs/releases/1.3-alpha-1.txt
+++ b/docs/releases/1.3-alpha-1.txt
@@ -154,7 +154,7 @@ requests. These include:
requests in tests.
* A new test assertion --
- :meth:`~django.test.TestCase.assertNumQueries` -- making it
+ :meth:`~django.test.TransactionTestCase.assertNumQueries` -- making it
easier to test the database activity associated with a view.
diff --git a/docs/releases/1.3.txt b/docs/releases/1.3.txt
index 89cece941b..45ebb2f1fe 100644
--- a/docs/releases/1.3.txt
+++ b/docs/releases/1.3.txt
@@ -299,7 +299,7 @@ requests. These include:
in tests.
* A new test assertion --
- :meth:`~django.test.TestCase.assertNumQueries` -- making it
+ :meth:`~django.test.TransactionTestCase.assertNumQueries` -- making it
easier to test the database activity associated with a view.
* Support for lookups spanning relations in admin's
diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt
index 83a5f54fc7..a013665ad3 100644
--- a/docs/releases/1.4.txt
+++ b/docs/releases/1.4.txt
@@ -541,8 +541,8 @@ compare HTML directly with the new
:meth:`~django.test.SimpleTestCase.assertHTMLEqual` and
:meth:`~django.test.SimpleTestCase.assertHTMLNotEqual` assertions, or use
the ``html=True`` flag with
-:meth:`~django.test.TestCase.assertContains` and
-:meth:`~django.test.TestCase.assertNotContains` to test whether the
+:meth:`~django.test.SimpleTestCase.assertContains` and
+:meth:`~django.test.SimpleTestCase.assertNotContains` to test whether the
client's response contains a given HTML fragment. See the :ref:`assertions
documentation <assertions>` for more.
@@ -1093,8 +1093,8 @@ wild, because they would confuse browsers too.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It's now possible to check whether a template was used within a block of
-code with :meth:`~django.test.TestCase.assertTemplateUsed` and
-:meth:`~django.test.TestCase.assertTemplateNotUsed`. And they
+code with :meth:`~django.test.SimpleTestCase.assertTemplateUsed` and
+:meth:`~django.test.SimpleTestCase.assertTemplateNotUsed`. And they
can be used as a context manager::
with self.assertTemplateUsed('index.html'):
diff --git a/docs/releases/1.6.txt b/docs/releases/1.6.txt
index 98889254cd..0eab8540b0 100644
--- a/docs/releases/1.6.txt
+++ b/docs/releases/1.6.txt
@@ -234,6 +234,13 @@ Minor features
.. _`Pillow`: https://pypi.python.org/pypi/Pillow
.. _`PIL`: https://pypi.python.org/pypi/PIL
+* :doc:`ModelForm </topics/forms/modelforms/>` accepts a new
+ Meta option: ``localized_fields``. Fields included in this list will be localized
+ (by setting ``localize`` on the form field).
+
+* The ``choices`` argument to model fields now accepts an iterable of iterables
+ instead of requiring an iterable of lists or tuples.
+
Backwards incompatible changes in 1.6
=====================================
@@ -264,9 +271,10 @@ The changes in transaction management may result in additional statements to
create, release or rollback savepoints. This is more likely to happen with
SQLite, since it didn't support savepoints until this release.
-If tests using :meth:`~django.test.TestCase.assertNumQueries` fail because of
-a higher number of queries than expected, check that the extra queries are
-related to savepoints, and adjust the expected number of queries accordingly.
+If tests using :meth:`~django.test.TransactionTestCase.assertNumQueries` fail
+because of a higher number of queries than expected, check that the extra
+queries are related to savepoints, and adjust the expected number of queries
+accordingly.
Autocommit option for PostgreSQL
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -481,6 +489,17 @@ Miscellaneous
changes in 1.6 particularly affect :class:`~django.forms.DecimalField` and
:class:`~django.forms.ModelMultipleChoiceField`.
+* There have been changes in the way timeouts are handled in cache backends.
+ Explicitly passing in ``timeout=None`` no longer results in using the
+ default timeout. It will now set a non-expiring timeout. Passing 0 into the
+ memcache backend no longer uses the default timeout, and now will
+ set-and-expire-immediately the value.
+
+* The ``django.contrib.flatpages`` app used to set custom HTTP headers for
+ debugging purposes. This functionality was not documented and made caching
+ ineffective so it has been removed, along with its generic implementation,
+ previously available in ``django.core.xheaders``.
+
Features deprecated in 1.6
==========================
@@ -551,6 +570,23 @@ If necessary, you can temporarily disable auto-escaping with
:func:`~django.utils.safestring.mark_safe` or :ttag:`{% autoescape off %}
<autoescape>`.
+``CACHE_MIDDLEWARE_ANONYMOUS_ONLY`` setting
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+``CacheMiddleware`` used to provide a way to cache requests only if they
+weren't made by a logged-in user. This mechanism was largely ineffective
+because the middleware correctly takes into account the ``Vary: Cookie`` HTTP
+header, and this header is being set on a variety of occasions, such as:
+
+* accessing the session, or
+* using CSRF protection, which is turned on by default, or
+* using a client-side library which sets cookies, like `Google Analytics`__.
+
+This makes the cache effectively work on a per-session basis regardless of the
+``CACHE_MIDDLEWARE_ANONYMOUS_ONLY`` setting.
+
+__ http://www.google.com/analytics/
+
``SEND_BROKEN_LINK_EMAILS`` setting
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/docs/topics/auth/customizing.txt b/docs/topics/auth/customizing.txt
index 56f3e60350..9a8bab947c 100644
--- a/docs/topics/auth/customizing.txt
+++ b/docs/topics/auth/customizing.txt
@@ -1075,7 +1075,6 @@ code would be required in the app's ``admin.py`` file::
(None, {'fields': ('email', 'password')}),
('Personal info', {'fields': ('date_of_birth',)}),
('Permissions', {'fields': ('is_admin',)}),
- ('Important dates', {'fields': ('last_login',)}),
)
add_fieldsets = (
(None, {
@@ -1092,3 +1091,8 @@ code would be required in the app's ``admin.py`` file::
# ... and, since we're not using Django's builtin permissions,
# unregister the Group model from admin.
admin.site.unregister(Group)
+
+Finally, specify the custom model as the default user model for your project
+using the :setting:`AUTH_USER_MODEL` setting in your ``settings.py``::
+
+ AUTH_USER_MODEL = 'customauth.MyUser'
diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt
index 6b6d57511a..46911a593f 100644
--- a/docs/topics/cache.txt
+++ b/docs/topics/cache.txt
@@ -443,15 +443,9 @@ Then, add the following required settings to your Django settings file:
The cache middleware caches GET and HEAD responses with status 200, where the request
and response headers allow. Responses to requests for the same URL with different
query parameters are considered to be unique pages and are cached separately.
-Optionally, if the :setting:`CACHE_MIDDLEWARE_ANONYMOUS_ONLY`
-setting is ``True``, only anonymous requests (i.e., not those made by a
-logged-in user) will be cached. This is a simple and effective way of disabling
-caching for any user-specific pages (including Django's admin interface). Note
-that if you use :setting:`CACHE_MIDDLEWARE_ANONYMOUS_ONLY`, you should make
-sure you've activated ``AuthenticationMiddleware``. The cache middleware
-expects that a HEAD request is answered with the same response headers as
-the corresponding GET request; in which case it can return a cached GET
-response for HEAD request.
+The cache middleware expects that a HEAD request is answered with the same
+response headers as the corresponding GET request; in which case it can return
+a cached GET response for HEAD request.
Additionally, the cache middleware automatically sets a few headers in each
:class:`~django.http.HttpResponse`:
@@ -707,10 +701,15 @@ The basic interface is ``set(key, value, timeout)`` and ``get(key)``::
>>> cache.get('my_key')
'hello, world!'
-The ``timeout`` argument is optional and defaults to the ``timeout``
-argument of the appropriate backend in the :setting:`CACHES` setting
-(explained above). It's the number of seconds the value should be stored
-in the cache.
+The ``timeout`` argument is optional and defaults to the ``timeout`` argument
+of the appropriate backend in the :setting:`CACHES` setting (explained above).
+It's the number of seconds the value should be stored in the cache. Passing in
+``None`` for ``timeout`` will cache the value forever.
+
+.. versionchanged:: 1.6
+
+ Previously, passing ``None`` explicitly would use the default timeout
+ value.
If the object doesn't exist in the cache, ``cache.get()`` returns ``None``::
diff --git a/docs/topics/class-based-views/generic-display.txt b/docs/topics/class-based-views/generic-display.txt
index 64b998770f..7ffa471e79 100644
--- a/docs/topics/class-based-views/generic-display.txt
+++ b/docs/topics/class-based-views/generic-display.txt
@@ -248,7 +248,7 @@ specify the objects that the view will operate upon -- you can also
specify the list of objects using the ``queryset`` argument::
from django.views.generic import DetailView
- from books.models import Publisher, Book
+ from books.models import Publisher
class PublisherDetail(DetailView):
@@ -326,6 +326,7 @@ various useful things are stored on ``self``; as well as the request
Here, we have a URLconf with a single captured group::
# urls.py
+ from django.conf.urls import patterns
from books.views import PublisherBookList
urlpatterns = patterns('',
@@ -375,6 +376,7 @@ Imagine we had a ``last_accessed`` field on our ``Author`` object that we were
using to keep track of the last time anybody looked at that author::
# models.py
+ from django.db import models
class Author(models.Model):
salutation = models.CharField(max_length=10)
@@ -390,6 +392,7 @@ updated.
First, we'd need to add an author detail bit in the URLconf to point to a
custom view::
+ from django.conf.urls import patterns, url
from books.views import AuthorDetailView
urlpatterns = patterns('',
@@ -401,7 +404,6 @@ Then we'd write our new view -- ``get_object`` is the method that retrieves the
object -- so we simply override it and wrap the call::
from django.views.generic import DetailView
- from django.shortcuts import get_object_or_404
from django.utils import timezone
from books.models import Author
diff --git a/docs/topics/class-based-views/generic-editing.txt b/docs/topics/class-based-views/generic-editing.txt
index 86c5280159..7c4e02cc4e 100644
--- a/docs/topics/class-based-views/generic-editing.txt
+++ b/docs/topics/class-based-views/generic-editing.txt
@@ -222,6 +222,7 @@ works for AJAX requests as well as 'normal' form POSTs::
from django.http import HttpResponse
from django.views.generic.edit import CreateView
+ from myapp.models import Author
class AjaxableResponseMixin(object):
"""
diff --git a/docs/topics/class-based-views/mixins.txt b/docs/topics/class-based-views/mixins.txt
index 9550d2fb86..980e571c85 100644
--- a/docs/topics/class-based-views/mixins.txt
+++ b/docs/topics/class-based-views/mixins.txt
@@ -258,6 +258,7 @@ mixin.
We can hook this into our URLs easily enough::
# urls.py
+ from django.conf.urls import patterns, url
from books.views import RecordInterest
urlpatterns = patterns('',
@@ -440,6 +441,7 @@ Our new ``AuthorDetail`` looks like this::
from django.core.urlresolvers import reverse
from django.views.generic import DetailView
from django.views.generic.edit import FormMixin
+ from books.models import Author
class AuthorInterestForm(forms.Form):
message = forms.CharField()
@@ -546,6 +548,8 @@ template as ``AuthorDisplay`` is using on ``GET``.
.. code-block:: python
+ from django.core.urlresolvers import reverse
+ from django.http import HttpResponseForbidden
from django.views.generic import FormView
from django.views.generic.detail import SingleObjectMixin
@@ -657,6 +661,8 @@ own version of :class:`~django.views.generic.detail.DetailView` by mixing
:class:`~django.views.generic.detail.DetailView` before template
rendering behavior has been mixed in)::
+ from django.views.generic.detail import BaseDetailView
+
class JSONDetailView(JSONResponseMixin, BaseDetailView):
pass
@@ -675,6 +681,8 @@ and override the implementation of
to defer to the appropriate subclass depending on the type of response that the
user requested::
+ from django.views.generic.detail import SingleObjectTemplateResponseMixin
+
class HybridDetailView(JSONResponseMixin, SingleObjectTemplateResponseMixin, BaseDetailView):
def render_to_response(self, context):
# Look for a 'format=json' GET argument
diff --git a/docs/topics/db/aggregation.txt b/docs/topics/db/aggregation.txt
index 125cd0bdee..1024d6b0c2 100644
--- a/docs/topics/db/aggregation.txt
+++ b/docs/topics/db/aggregation.txt
@@ -18,27 +18,29 @@ used to track the inventory for a series of online bookstores:
.. code-block:: python
+ from django.db import models
+
class Author(models.Model):
- name = models.CharField(max_length=100)
- age = models.IntegerField()
+ name = models.CharField(max_length=100)
+ age = models.IntegerField()
class Publisher(models.Model):
- name = models.CharField(max_length=300)
- num_awards = models.IntegerField()
+ name = models.CharField(max_length=300)
+ num_awards = models.IntegerField()
class Book(models.Model):
- name = models.CharField(max_length=300)
- pages = models.IntegerField()
- price = models.DecimalField(max_digits=10, decimal_places=2)
- rating = models.FloatField()
- authors = models.ManyToManyField(Author)
- publisher = models.ForeignKey(Publisher)
- pubdate = models.DateField()
+ name = models.CharField(max_length=300)
+ pages = models.IntegerField()
+ price = models.DecimalField(max_digits=10, decimal_places=2)
+ rating = models.FloatField()
+ authors = models.ManyToManyField(Author)
+ publisher = models.ForeignKey(Publisher)
+ pubdate = models.DateField()
class Store(models.Model):
- name = models.CharField(max_length=300)
- books = models.ManyToManyField(Book)
- registered_users = models.PositiveIntegerField()
+ name = models.CharField(max_length=300)
+ books = models.ManyToManyField(Book)
+ registered_users = models.PositiveIntegerField()
Cheat sheet
===========
@@ -123,7 +125,7 @@ If you want to generate more than one aggregate, you just add another
argument to the ``aggregate()`` clause. So, if we also wanted to know
the maximum and minimum price of all books, we would issue the query::
- >>> from django.db.models import Avg, Max, Min, Count
+ >>> from django.db.models import Avg, Max, Min
>>> Book.objects.aggregate(Avg('price'), Max('price'), Min('price'))
{'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')}
@@ -148,6 +150,7 @@ the number of authors:
.. code-block:: python
# Build an annotated queryset
+ >>> from django.db.models import Count
>>> q = Book.objects.annotate(Count('authors'))
# Interrogate the first object in the queryset
>>> q[0]
@@ -192,6 +195,7 @@ and aggregate the related value.
For example, to find the price range of books offered in each store,
you could use the annotation::
+ >>> from django.db.models import Max, Min
>>> Store.objects.annotate(min_price=Min('books__price'), max_price=Max('books__price'))
This tells Django to retrieve the ``Store`` model, join (through the
@@ -222,7 +226,7 @@ For example, we can ask for all publishers, annotated with their respective
total book stock counters (note how we use ``'book'`` to specify the
``Publisher`` -> ``Book`` reverse foreign key hop)::
- >>> from django.db.models import Count, Min, Sum, Max, Avg
+ >>> from django.db.models import Count, Min, Sum, Avg
>>> Publisher.objects.annotate(Count('book'))
(Every ``Publisher`` in the resulting ``QuerySet`` will have an extra attribute
@@ -269,6 +273,7 @@ constraining the objects for which an annotation is calculated. For example,
you can generate an annotated list of all books that have a title starting
with "Django" using the query::
+ >>> from django.db.models import Count, Avg
>>> Book.objects.filter(name__startswith="Django").annotate(num_authors=Count('authors'))
When used with an ``aggregate()`` clause, a filter has the effect of
@@ -407,6 +412,8 @@ particularly, when counting things.
By way of example, suppose you have a model like this::
+ from django.db import models
+
class Item(models.Model):
name = models.CharField(max_length=10)
data = models.IntegerField()
@@ -457,5 +464,6 @@ For example, if you wanted to calculate the average number of authors per
book you first annotate the set of books with the author count, then
aggregate that author count, referencing the annotation field::
+ >>> from django.db.models import Count, Avg
>>> Book.objects.annotate(num_authors=Count('authors')).aggregate(Avg('num_authors'))
{'num_authors__avg': 1.66}
diff --git a/docs/topics/db/managers.txt b/docs/topics/db/managers.txt
index 2a0f7e4ce0..b940b09d33 100644
--- a/docs/topics/db/managers.txt
+++ b/docs/topics/db/managers.txt
@@ -62,6 +62,8 @@ For example, this custom ``Manager`` offers a method ``with_counts()``, which
returns a list of all ``OpinionPoll`` objects, each with an extra
``num_responses`` attribute that is the result of an aggregate query::
+ from django.db import models
+
class PollManager(models.Manager):
def with_counts(self):
from django.db import connection
@@ -101,6 +103,8 @@ Modifying initial Manager QuerySets
A ``Manager``'s base ``QuerySet`` returns all objects in the system. For
example, using this model::
+ from django.db import models
+
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length=50)
@@ -236,7 +240,7 @@ class, but still customize the default manager. For example, suppose you have
this base class::
class AbstractBase(models.Model):
- ...
+ # ...
objects = CustomManager()
class Meta:
@@ -246,14 +250,15 @@ If you use this directly in a subclass, ``objects`` will be the default
manager if you declare no managers in the base class::
class ChildA(AbstractBase):
- ...
+ # ...
# This class has CustomManager as the default manager.
+ pass
If you want to inherit from ``AbstractBase``, but provide a different default
manager, you can provide the default manager on the child class::
class ChildB(AbstractBase):
- ...
+ # ...
# An explicit default manager.
default_manager = OtherManager()
@@ -274,9 +279,10 @@ it into the inheritance hierarchy *after* the defaults::
abstract = True
class ChildC(AbstractBase, ExtraManager):
- ...
+ # ...
# Default manager is CustomManager, but OtherManager is
# also available via the "extra_manager" attribute.
+ pass
Note that while you can *define* a custom manager on the abstract model, you
can't *invoke* any methods using the abstract model. That is::
@@ -349,8 +355,7 @@ the manager class::
class MyManager(models.Manager):
use_for_related_fields = True
-
- ...
+ # ...
If this attribute is set on the *default* manager for a model (only the
default manager is considered in these situations), Django will use that class
@@ -396,7 +401,8 @@ it, whereas the following will not work::
# BAD: Incorrect code
class MyManager(models.Manager):
- ...
+ # ...
+ pass
# Sets the attribute on an instance of MyManager. Django will
# ignore this setting.
@@ -404,7 +410,7 @@ it, whereas the following will not work::
mgr.use_for_related_fields = True
class MyModel(models.Model):
- ...
+ # ...
objects = mgr
# End of incorrect code.
diff --git a/docs/topics/db/models.txt b/docs/topics/db/models.txt
index dd7714052d..baef01b6fb 100644
--- a/docs/topics/db/models.txt
+++ b/docs/topics/db/models.txt
@@ -90,6 +90,8 @@ attributes. Be careful not to choose field names that conflict with the
Example::
+ from django.db import models
+
class Musician(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
@@ -290,8 +292,11 @@ For example, if a ``Car`` model has a ``Manufacturer`` -- that is, a
``Manufacturer`` makes multiple cars but each ``Car`` only has one
``Manufacturer`` -- use the following definitions::
+ from django.db import models
+
class Manufacturer(models.Model):
# ...
+ pass
class Car(models.Model):
manufacturer = models.ForeignKey(Manufacturer)
@@ -340,8 +345,11 @@ For example, if a ``Pizza`` has multiple ``Topping`` objects -- that is, a
``Topping`` can be on multiple pizzas and each ``Pizza`` has multiple toppings
-- here's how you'd represent that::
+ from django.db import models
+
class Topping(models.Model):
# ...
+ pass
class Pizza(models.Model):
# ...
@@ -403,6 +411,8 @@ intermediate model. The intermediate model is associated with the
that will act as an intermediary. For our musician example, the code would look
something like this::
+ from django.db import models
+
class Person(models.Model):
name = models.CharField(max_length=128)
@@ -583,6 +593,7 @@ It's perfectly OK to relate a model to one from another app. To do this, import
the related model at the top of the file where your model is defined. Then,
just refer to the other model class wherever needed. For example::
+ from django.db import models
from geography.models import ZipCode
class Restaurant(models.Model):
@@ -630,6 +641,8 @@ Meta options
Give your model metadata by using an inner ``class Meta``, like so::
+ from django.db import models
+
class Ox(models.Model):
horn_length = models.IntegerField()
@@ -660,6 +673,8 @@ model.
For example, this model has a few custom methods::
+ from django.db import models
+
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
@@ -729,6 +744,8 @@ A classic use-case for overriding the built-in methods is if you want something
to happen whenever you save an object. For example (see
:meth:`~Model.save` for documentation of the parameters it accepts)::
+ from django.db import models
+
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
@@ -740,6 +757,8 @@ to happen whenever you save an object. For example (see
You can also prevent saving::
+ from django.db import models
+
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
@@ -826,6 +845,8 @@ the child (and Django will raise an exception).
An example::
+ from django.db import models
+
class CommonInfo(models.Model):
name = models.CharField(max_length=100)
age = models.PositiveIntegerField()
@@ -854,14 +875,16 @@ attribute. If a child class does not declare its own :ref:`Meta <meta-options>`
class, it will inherit the parent's :ref:`Meta <meta-options>`. If the child wants to
extend the parent's :ref:`Meta <meta-options>` class, it can subclass it. For example::
+ from django.db import models
+
class CommonInfo(models.Model):
- ...
+ # ...
class Meta:
abstract = True
ordering = ['name']
class Student(CommonInfo):
- ...
+ # ...
class Meta(CommonInfo.Meta):
db_table = 'student_info'
@@ -901,6 +924,8 @@ abstract base class (only), part of the name should contain
For example, given an app ``common/models.py``::
+ from django.db import models
+
class Base(models.Model):
m2m = models.ManyToManyField(OtherModel, related_name="%(app_label)s_%(class)s_related")
@@ -949,6 +974,8 @@ relationship introduces links between the child model and each of its parents
(via an automatically-created :class:`~django.db.models.OneToOneField`).
For example::
+ from django.db import models
+
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
@@ -998,7 +1025,7 @@ If the parent has an ordering and you don't want the child to have any natural
ordering, you can explicitly disable it::
class ChildModel(ParentModel):
- ...
+ # ...
class Meta:
# Remove parent's ordering effect
ordering = []
@@ -1061,15 +1088,21 @@ Proxy models are declared like normal models. You tell Django that it's a
proxy model by setting the :attr:`~django.db.models.Options.proxy` attribute of
the ``Meta`` class to ``True``.
-For example, suppose you want to add a method to the ``Person`` model described
-above. You can do it like this::
+For example, suppose you want to add a method to the ``Person`` model. You can do it like this::
+
+ from django.db import models
+
+ class Person(models.Model):
+ first_name = models.CharField(max_length=30)
+ last_name = models.CharField(max_length=30)
class MyPerson(Person):
class Meta:
proxy = True
def do_something(self):
- ...
+ # ...
+ pass
The ``MyPerson`` class operates on the same database table as its parent
``Person`` class. In particular, any new instances of ``Person`` will also be
@@ -1125,8 +1158,11 @@ classes will still be available.
Continuing our example from above, you could change the default manager used
when you query the ``Person`` model like this::
+ from django.db import models
+
class NewManager(models.Manager):
- ...
+ # ...
+ pass
class MyPerson(Person):
objects = NewManager()
diff --git a/docs/topics/db/queries.txt b/docs/topics/db/queries.txt
index 2553eac27a..a1cb5c79c5 100644
--- a/docs/topics/db/queries.txt
+++ b/docs/topics/db/queries.txt
@@ -17,6 +17,8 @@ models, which comprise a Weblog application:
.. code-block:: python
+ from django.db import models
+
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
diff --git a/docs/topics/files.txt b/docs/topics/files.txt
index fb3cdd4af9..492e6a20b5 100644
--- a/docs/topics/files.txt
+++ b/docs/topics/files.txt
@@ -27,6 +27,8 @@ to deal with that file.
Consider the following model, using an :class:`~django.db.models.ImageField` to
store a photo::
+ from django.db import models
+
class Car(models.Model):
name = models.CharField(max_length=255)
price = models.DecimalField(max_digits=5, decimal_places=2)
diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt
index e58dade736..3cd8c69ab5 100644
--- a/docs/topics/forms/modelforms.txt
+++ b/docs/topics/forms/modelforms.txt
@@ -474,6 +474,24 @@ parameter when declaring the form field::
See the :doc:`form field documentation </ref/forms/fields>` for more information
on fields and their arguments.
+
+Enabling localization of fields
+-------------------------------
+
+.. versionadded:: 1.6
+
+By default, the fields in a ``ModelForm`` will not localize their data. To
+enable localization for fields, you can use the ``localized_fields``
+attribute on the ``Meta`` class.
+
+ >>> class AuthorForm(ModelForm):
+ ... class Meta:
+ ... model = Author
+ ... localized_fields = ('birth_date',)
+
+If ``localized_fields`` is set to the special value ``'__all__'``, all fields
+will be localized.
+
.. _overriding-modelform-clean-method:
Overriding the clean() method
@@ -570,6 +588,10 @@ keyword arguments, or the corresponding attributes on the ``ModelForm`` inner
``Meta`` class. Please see the ``ModelForm`` :ref:`modelforms-selecting-fields`
documentation.
+... or enable localization for specific fields::
+
+ >>> Form = modelform_factory(Author, form=AuthorForm, localized_fields=("birth_date",))
+
.. _model-formsets:
Model formsets
@@ -663,6 +685,20 @@ class of a ``ModelForm`` works::
>>> AuthorFormSet = modelformset_factory(
... Author, widgets={'name': Textarea(attrs={'cols': 80, 'rows': 20})
+Enabling localization for fields with ``localized_fields``
+----------------------------------------------------------
+
+.. versionadded:: 1.6
+
+Using the ``localized_fields`` parameter, you can enable localization for
+fields in the form.
+
+ >>> AuthorFormSet = modelformset_factory(
+ ... Author, localized_fields=('value',))
+
+If ``localized_fields`` is set to the special value ``'__all__'``, all fields
+will be localized.
+
Providing initial values
------------------------
diff --git a/docs/topics/http/file-uploads.txt b/docs/topics/http/file-uploads.txt
index 80bd5f3c44..54d748d961 100644
--- a/docs/topics/http/file-uploads.txt
+++ b/docs/topics/http/file-uploads.txt
@@ -15,6 +15,7 @@ Basic file uploads
Consider a simple form containing a :class:`~django.forms.FileField`::
+ # In forms.py...
from django import forms
class UploadFileForm(forms.Form):
@@ -39,6 +40,7 @@ something like::
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response
+ from .forms import UploadFileForm
# Imaginary function to handle an uploaded file.
from somewhere import handle_uploaded_file
diff --git a/docs/topics/http/sessions.txt b/docs/topics/http/sessions.txt
index acad61eb2a..772ee122d5 100644
--- a/docs/topics/http/sessions.txt
+++ b/docs/topics/http/sessions.txt
@@ -120,11 +120,29 @@ and the :setting:`SECRET_KEY` setting.
.. note::
+ When using cookies-based sessions :mod:`django.contrib.sessions` can be
+ removed from :setting:`INSTALLED_APPS` setting because data is loaded
+ from the key itself and not from the database, so there is no need for the
+ creation and usage of ``django.contrib.sessions.models.Session`` table.
+
+.. note::
+
It's recommended to leave the :setting:`SESSION_COOKIE_HTTPONLY` setting
``True`` to prevent tampering of the stored data from JavaScript.
.. warning::
+ **If the SECRET_KEY is not kept secret, this can lead to arbitrary remote
+ code execution.**
+
+ An attacker in possession of the :setting:`SECRET_KEY` can not only
+ generate falsified session data, which your site will trust, but also
+ remotely execute arbitrary code, as the data is serialized using pickle.
+
+ If you use cookie-based sessions, pay extra care that your secret key is
+ always kept completely secret, for any system which might be remotely
+ accessible.
+
**The session data is signed but not encrypted**
When using the cookies backend the session data can be read by the client.
diff --git a/docs/topics/http/urls.txt b/docs/topics/http/urls.txt
index 9a96199dba..8a3f240307 100644
--- a/docs/topics/http/urls.txt
+++ b/docs/topics/http/urls.txt
@@ -123,6 +123,8 @@ is ``(?P<name>pattern)``, where ``name`` is the name of the group and
Here's the above example URLconf, rewritten to use named groups::
+ from django.conf.urls import patterns, url
+
urlpatterns = patterns('',
url(r'^articles/2003/$', 'news.views.special_case_2003'),
url(r'^articles/(?P<year>\d{4})/$', 'news.views.year_archive'),
@@ -192,6 +194,8 @@ A convenient trick is to specify default parameters for your views' arguments.
Here's an example URLconf and view::
# URLconf
+ from django.conf.urls import patterns, url
+
urlpatterns = patterns('',
url(r'^blog/$', 'blog.views.page'),
url(r'^blog/page(?P<num>\d+)/$', 'blog.views.page'),
@@ -370,11 +374,15 @@ An included URLconf receives any captured parameters from parent URLconfs, so
the following example is valid::
# In settings/urls/main.py
+ from django.conf.urls import include, patterns, url
+
urlpatterns = patterns('',
url(r'^(?P<username>\w+)/blog/', include('foo.urls.blog')),
)
# In foo/urls/blog.py
+ from django.conf.urls import patterns, url
+
urlpatterns = patterns('foo.views',
url(r'^$', 'blog.index'),
url(r'^archive/$', 'blog.archive'),
@@ -397,6 +405,8 @@ function.
For example::
+ from django.conf.urls import patterns, url
+
urlpatterns = patterns('blog.views',
url(r'^blog/(?P<year>\d{4})/$', 'year_archive', {'foo': 'bar'}),
)
@@ -427,11 +437,15 @@ For example, these two URLconf sets are functionally identical:
Set one::
# main.py
+ from django.conf.urls import include, patterns, url
+
urlpatterns = patterns('',
url(r'^blog/', include('inner'), {'blogid': 3}),
)
# inner.py
+ from django.conf.urls import patterns, url
+
urlpatterns = patterns('',
url(r'^archive/$', 'mysite.views.archive'),
url(r'^about/$', 'mysite.views.about'),
@@ -440,11 +454,15 @@ Set one::
Set two::
# main.py
+ from django.conf.urls import include, patterns, url
+
urlpatterns = patterns('',
url(r'^blog/', include('inner')),
)
# inner.py
+ from django.conf.urls import patterns, url
+
urlpatterns = patterns('',
url(r'^archive/$', 'mysite.views.archive', {'blogid': 3}),
url(r'^about/$', 'mysite.views.about', {'blogid': 3}),
@@ -464,6 +482,8 @@ supported -- you can pass any callable object as the view.
For example, given this URLconf in "string" notation::
+ from django.conf.urls import patterns, url
+
urlpatterns = patterns('',
url(r'^archive/$', 'mysite.views.archive'),
url(r'^about/$', 'mysite.views.about'),
@@ -473,6 +493,7 @@ For example, given this URLconf in "string" notation::
You can accomplish the same thing by passing objects rather than strings. Just
be sure to import the objects::
+ from django.conf.urls import patterns, url
from mysite.views import archive, about, contact
urlpatterns = patterns('',
@@ -485,6 +506,7 @@ The following example is functionally identical. It's just a bit more compact
because it imports the module that contains the views, rather than importing
each view individually::
+ from django.conf.urls import patterns, url
from mysite import views
urlpatterns = patterns('',
@@ -501,6 +523,7 @@ the view prefix (as explained in "The view prefix" above) will have no effect.
Note that :doc:`class based views</topics/class-based-views/index>` must be
imported::
+ from django.conf.urls import patterns, url
from mysite.views import ClassBasedView
urlpatterns = patterns('',
@@ -612,6 +635,9 @@ It's fairly common to use the same view function in multiple URL patterns in
your URLconf. For example, these two URL patterns both point to the ``archive``
view::
+ from django.conf.urls import patterns, url
+ from mysite.views import archive
+
urlpatterns = patterns('',
url(r'^archive/(\d{4})/$', archive),
url(r'^archive-summary/(\d{4})/$', archive, {'summary': True}),
@@ -630,6 +656,9 @@ matching.
Here's the above example, rewritten to use named URL patterns::
+ from django.conf.urls import patterns, url
+ from mysite.views import archive
+
urlpatterns = patterns('',
url(r'^archive/(\d{4})/$', archive, name="full-archive"),
url(r'^archive-summary/(\d{4})/$', archive, {'summary': True}, name="arch-summary"),
@@ -803,6 +832,8 @@ However, you can also ``include()`` a 3-tuple containing::
For example::
+ from django.conf.urls import include, patterns, url
+
help_patterns = patterns('',
url(r'^basic/$', 'apps.help.views.views.basic'),
url(r'^advanced/$', 'apps.help.views.views.advanced'),
diff --git a/docs/topics/http/views.txt b/docs/topics/http/views.txt
index f73ec4f5be..2ccedec2f7 100644
--- a/docs/topics/http/views.txt
+++ b/docs/topics/http/views.txt
@@ -70,6 +70,8 @@ documentation. Just return an instance of one of those subclasses instead of
a normal :class:`~django.http.HttpResponse` in order to signify an error. For
example::
+ from django.http import HttpResponse, HttpResponseNotFound
+
def my_view(request):
# ...
if foo:
@@ -83,6 +85,8 @@ the :class:`~django.http.HttpResponse` documentation, you can also pass the
HTTP status code into the constructor for :class:`~django.http.HttpResponse`
to create a return class for any status code you like. For example::
+ from django.http import HttpResponse
+
def my_view(request):
# ...
@@ -110,6 +114,8 @@ standard error page for your application, along with an HTTP error code 404.
Example usage::
from django.http import Http404
+ from django.shortcuts import render_to_response
+ from polls.models import Poll
def detail(request, poll_id):
try:
diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt
index 5b4ffea528..72e000a86f 100644
--- a/docs/topics/i18n/translation.txt
+++ b/docs/topics/i18n/translation.txt
@@ -722,6 +722,31 @@ or with the ``{#`` ... ``#}`` :ref:`one-line comment constructs <template-commen
msgid "Ambiguous translatable block of text"
msgstr ""
+.. templatetag:: language
+
+Switching language in templates
+-------------------------------
+
+If you want to select a language within a template, you can use the
+``language`` template tag:
+
+.. code-block:: html+django
+
+ {% load i18n %}
+
+ {% get_current_language as LANGUAGE_CODE %}
+ <!-- Current language: {{ LANGUAGE_CODE }} -->
+ <p>{% trans "Welcome to our page" %}</p>
+
+ {% language 'en' %}
+ {% get_current_language as LANGUAGE_CODE %}
+ <!-- Current language: {{ LANGUAGE_CODE }} -->
+ <p>{% trans "Welcome to our page" %}</p>
+ {% endlanguage %}
+
+While the first occurrence of "Welcome to our page" uses the current language,
+the second will always be in English.
+
.. _template-translation-vars:
Other tags
@@ -1126,13 +1151,11 @@ active language. Example::
.. _reversing_in_templates:
-.. templatetag:: language
-
Reversing in templates
----------------------
If localized URLs get reversed in templates they always use the current
-language. To link to a URL in another language use the ``language``
+language. To link to a URL in another language use the :ttag:`language`
template tag. It enables the given language in the enclosed template section:
.. code-block:: html+django
diff --git a/docs/topics/python3.txt b/docs/topics/python3.txt
index 22e609c75c..9a0438e9e5 100644
--- a/docs/topics/python3.txt
+++ b/docs/topics/python3.txt
@@ -201,8 +201,8 @@ According to :pep:`3333`:
Specifically, :attr:`HttpResponse.content <django.http.HttpResponse.content>`
contains ``bytes``, which may become an issue if you compare it with a
``str`` in your tests. The preferred solution is to rely on
-:meth:`~django.test.TestCase.assertContains` and
-:meth:`~django.test.TestCase.assertNotContains`. These methods accept a
+:meth:`~django.test.SimpleTestCase.assertContains` and
+:meth:`~django.test.SimpleTestCase.assertNotContains`. These methods accept a
response and a unicode string as arguments.
Coding guidelines
diff --git a/docs/topics/testing/overview.txt b/docs/topics/testing/overview.txt
index fc2b393898..d543099ae6 100644
--- a/docs/topics/testing/overview.txt
+++ b/docs/topics/testing/overview.txt
@@ -21,17 +21,16 @@ module defines tests using a class-based approach.
.. admonition:: unittest2
- Python 2.7 introduced some major changes to the unittest library,
+ Python 2.7 introduced some major changes to the ``unittest`` library,
adding some extremely useful features. To ensure that every Django
project can benefit from these new features, Django ships with a
- copy of unittest2_, a copy of the Python 2.7 unittest library,
- backported for Python 2.6 compatibility.
+ copy of unittest2_, a copy of Python 2.7's ``unittest``, backported for
+ Python 2.6 compatibility.
To access this library, Django provides the
``django.utils.unittest`` module alias. If you are using Python
- 2.7, or you have installed unittest2 locally, Django will map the
- alias to the installed version of the unittest library. Otherwise,
- Django will use its own bundled version of unittest2.
+ 2.7, or you have installed ``unittest2`` locally, Django will map the alias
+ to it. Otherwise, Django will use its own bundled version of ``unittest2``.
To use this alias, simply use::
@@ -41,8 +40,8 @@ module defines tests using a class-based approach.
import unittest
- If you want to continue to use the base unittest library, you can --
- you just won't get any of the nice new unittest2 features.
+ If you want to continue to use the legacy ``unittest`` library, you can --
+ you just won't get any of the nice new ``unittest2`` features.
.. _unittest2: http://pypi.python.org/pypi/unittest2
@@ -858,24 +857,46 @@ SimpleTestCase
.. class:: SimpleTestCase()
-A very thin subclass of :class:`unittest.TestCase`, it extends it with some
-basic functionality like:
+A thin subclass of :class:`unittest.TestCase`, it extends it with some basic
+functionality like:
* Saving and restoring the Python warning machinery state.
-* Checking that a callable :meth:`raises a certain exception <SimpleTestCase.assertRaisesMessage>`.
-* :meth:`Testing form field rendering <SimpleTestCase.assertFieldOutput>`.
-* Testing server :ref:`HTML responses for the presence/lack of a given fragment <assertions>`.
-* The ability to run tests with :ref:`modified settings <overriding-settings>`
+* Some useful assertions like:
+
+ * Checking that a callable :meth:`raises a certain exception
+ <SimpleTestCase.assertRaisesMessage>`.
+ * Testing form field :meth:`rendering and error treatment
+ <SimpleTestCase.assertFieldOutput>`.
+ * Testing :meth:`HTML responses for the presence/lack of a given fragment
+ <SimpleTestCase.assertContains>`.
+ * Verifying that a template :meth:`has/hasn't been used to generate a given
+ response content <SimpleTestCase.assertTemplateUsed>`.
+ * Verifying a HTTP :meth:`redirect <SimpleTestCase.assertRedirects>` is
+ performed by the app.
+ * Robustly testing two :meth:`HTML fragments <SimpleTestCase.assertHTMLEqual>`
+ for equality/inequality or :meth:`containment <SimpleTestCase.assertInHTML>`.
+ * Robustly testing two :meth:`XML fragments <SimpleTestCase.assertXMLEqual>`
+ for equality/inequality.
+ * Robustly testing two :meth:`JSON fragments <SimpleTestCase.assertJSONEqual>`
+ for equality.
+
+* The ability to run tests with :ref:`modified settings <overriding-settings>`.
+* Using the :attr:`~SimpleTestCase.client` :class:`~django.test.client.Client`.
+* Custom test-time :attr:`URL maps <SimpleTestCase.urls>`.
+
+.. versionchanged:: 1.6
+
+ The latter two features were moved from ``TransactionTestCase`` to
+ ``SimpleTestCase`` in Django 1.6.
If you need any of the other more complex and heavyweight Django-specific
features like:
-* Using the :attr:`~TestCase.client` :class:`~django.test.client.Client`.
* Testing or using the ORM.
-* Database :attr:`~TestCase.fixtures`.
-* Custom test-time :attr:`URL maps <TestCase.urls>`.
+* Database :attr:`~TransactionTestCase.fixtures`.
* Test :ref:`skipping based on database backend features <skipping-tests>`.
-* The remaining specialized :ref:`assert* <assertions>` methods.
+* The remaining specialized :meth:`assert*
+ <TransactionTestCase.assertQuerysetEqual>` methods.
then you should use :class:`~django.test.TransactionTestCase` or
:class:`~django.test.TestCase` instead.
@@ -1137,9 +1158,9 @@ Test cases features
Default test client
~~~~~~~~~~~~~~~~~~~
-.. attribute:: TestCase.client
+.. attribute:: SimpleTestCase.client
-Every test case in a ``django.test.TestCase`` instance has access to an
+Every test case in a ``django.test.*TestCase`` instance has access to an
instance of a Django test client. This client can be accessed as
``self.client``. This client is recreated for each test, so you don't have to
worry about state (such as cookies) carrying over from one test to another.
@@ -1176,10 +1197,10 @@ This means, instead of instantiating a ``Client`` in each test::
Customizing the test client
~~~~~~~~~~~~~~~~~~~~~~~~~~~
-.. attribute:: TestCase.client_class
+.. attribute:: SimpleTestCase.client_class
If you want to use a different ``Client`` class (for example, a subclass
-with customized behavior), use the :attr:`~TestCase.client_class` class
+with customized behavior), use the :attr:`~SimpleTestCase.client_class` class
attribute::
from django.test import TestCase
@@ -1200,11 +1221,12 @@ attribute::
Fixture loading
~~~~~~~~~~~~~~~
-.. attribute:: TestCase.fixtures
+.. attribute:: TransactionTestCase.fixtures
A test case for a database-backed Web site isn't much use if there isn't any
data in the database. To make it easy to put test data into the database,
-Django's custom ``TestCase`` class provides a way of loading **fixtures**.
+Django's custom ``TransactionTestCase`` class provides a way of loading
+**fixtures**.
A fixture is a collection of data that Django knows how to import into a
database. For example, if your site has user accounts, you might set up a
@@ -1273,7 +1295,7 @@ or by the order of test execution.
URLconf configuration
~~~~~~~~~~~~~~~~~~~~~
-.. attribute:: TestCase.urls
+.. attribute:: SimpleTestCase.urls
If your application provides views, you may want to include tests that use the
test client to exercise those views. However, an end user is free to deploy the
@@ -1282,9 +1304,9 @@ tests can't rely upon the fact that your views will be available at a
particular URL.
In order to provide a reliable URL space for your test,
-``django.test.TestCase`` provides the ability to customize the URLconf
+``django.test.*TestCase`` classes provide the ability to customize the URLconf
configuration for the duration of the execution of a test suite. If your
-``TestCase`` instance defines an ``urls`` attribute, the ``TestCase`` will use
+``*TestCase`` instance defines an ``urls`` attribute, the ``*TestCase`` will use
the value of that attribute as the :setting:`ROOT_URLCONF` for the duration
of that test.
@@ -1307,7 +1329,7 @@ URLconf for the duration of the test case.
Multi-database support
~~~~~~~~~~~~~~~~~~~~~~
-.. attribute:: TestCase.multi_db
+.. attribute:: TransactionTestCase.multi_db
Django sets up a test database corresponding to every database that is
defined in the :setting:`DATABASES` definition in your settings
@@ -1340,12 +1362,12 @@ This test case will flush *all* the test databases before running
Overriding settings
~~~~~~~~~~~~~~~~~~~
-.. method:: TestCase.settings
+.. method:: SimpleTestCase.settings
For testing purposes it's often useful to change a setting temporarily and
revert to the original value after running the testing code. For this use case
Django provides a standard Python context manager (see :pep:`343`)
-:meth:`~django.test.TestCase.settings`, which can be used like this::
+:meth:`~django.test.SimpleTestCase.settings`, which can be used like this::
from django.test import TestCase
@@ -1435,8 +1457,8 @@ MEDIA_ROOT, DEFAULT_FILE_STORAGE Default file storage
Emptying the test outbox
~~~~~~~~~~~~~~~~~~~~~~~~
-If you use Django's custom ``TestCase`` class, the test runner will clear the
-contents of the test email outbox at the start of each test case.
+If you use any of Django's custom ``TestCase`` classes, the test runner will
+clear thecontents of the test email outbox at the start of each test case.
For more detail on email services during tests, see `Email services`_ below.
@@ -1486,8 +1508,22 @@ your test suite.
self.assertFieldOutput(EmailField, {'a@a.com': 'a@a.com'}, {'aaa': [u'Enter a valid email address.']})
+.. method:: SimpleTestCase.assertFormError(response, form, field, errors, msg_prefix='')
+
+ Asserts that a field on a form raises the provided list of errors when
+ rendered on the form.
+
+ ``form`` is the name the ``Form`` instance was given in the template
+ context.
+
+ ``field`` is the name of the field on the form to check. If ``field``
+ has a value of ``None``, non-field errors (errors you can access via
+ ``form.non_field_errors()``) will be checked.
+
+ ``errors`` is an error string, or a list of error strings, that are
+ expected as a result of form validation.
-.. method:: TestCase.assertContains(response, text, count=None, status_code=200, msg_prefix='', html=False)
+.. method:: SimpleTestCase.assertContains(response, text, count=None, status_code=200, msg_prefix='', html=False)
Asserts that a ``Response`` instance produced the given ``status_code`` and
that ``text`` appears in the content of the response. If ``count`` is
@@ -1499,7 +1535,7 @@ your test suite.
attribute ordering is not significant. See
:meth:`~SimpleTestCase.assertHTMLEqual` for more details.
-.. method:: TestCase.assertNotContains(response, text, status_code=200, msg_prefix='', html=False)
+.. method:: SimpleTestCase.assertNotContains(response, text, status_code=200, msg_prefix='', html=False)
Asserts that a ``Response`` instance produced the given ``status_code`` and
that ``text`` does not appears in the content of the response.
@@ -1510,22 +1546,7 @@ your test suite.
attribute ordering is not significant. See
:meth:`~SimpleTestCase.assertHTMLEqual` for more details.
-.. method:: TestCase.assertFormError(response, form, field, errors, msg_prefix='')
-
- Asserts that a field on a form raises the provided list of errors when
- rendered on the form.
-
- ``form`` is the name the ``Form`` instance was given in the template
- context.
-
- ``field`` is the name of the field on the form to check. If ``field``
- has a value of ``None``, non-field errors (errors you can access via
- ``form.non_field_errors()``) will be checked.
-
- ``errors`` is an error string, or a list of error strings, that are
- expected as a result of form validation.
-
-.. method:: TestCase.assertTemplateUsed(response, template_name, msg_prefix='')
+.. method:: SimpleTestCase.assertTemplateUsed(response, template_name, msg_prefix='')
Asserts that the template with the given name was used in rendering the
response.
@@ -1539,15 +1560,15 @@ your test suite.
with self.assertTemplateUsed(template_name='index.html'):
render_to_string('index.html')
-.. method:: TestCase.assertTemplateNotUsed(response, template_name, msg_prefix='')
+.. method:: SimpleTestCase.assertTemplateNotUsed(response, template_name, msg_prefix='')
Asserts that the template with the given name was *not* used in rendering
the response.
You can use this as a context manager in the same way as
- :meth:`~TestCase.assertTemplateUsed`.
+ :meth:`~SimpleTestCase.assertTemplateUsed`.
-.. method:: TestCase.assertRedirects(response, expected_url, status_code=302, target_status_code=200, msg_prefix='')
+.. method:: SimpleTestCase.assertRedirects(response, expected_url, status_code=302, target_status_code=200, msg_prefix='')
Asserts that the response return a ``status_code`` redirect status, it
redirected to ``expected_url`` (including any GET data), and the final
@@ -1557,44 +1578,6 @@ your test suite.
``target_status_code`` will be the url and status code for the final
point of the redirect chain.
-.. method:: TestCase.assertQuerysetEqual(qs, values, transform=repr, ordered=True)
-
- Asserts that a queryset ``qs`` returns a particular list of values ``values``.
-
- The comparison of the contents of ``qs`` and ``values`` is performed using
- the function ``transform``; by default, this means that the ``repr()`` of
- each value is compared. Any other callable can be used if ``repr()`` doesn't
- provide a unique or helpful comparison.
-
- By default, the comparison is also ordering dependent. If ``qs`` doesn't
- provide an implicit ordering, you can set the ``ordered`` parameter to
- ``False``, which turns the comparison into a Python set comparison.
-
- .. versionchanged:: 1.6
-
- The method now checks for undefined order and raises ``ValueError``
- if undefined order is spotted. The ordering is seen as undefined if
- the given ``qs`` isn't ordered and the comparison is against more
- than one ordered values.
-
-.. method:: TestCase.assertNumQueries(num, func, *args, **kwargs)
-
- Asserts that when ``func`` is called with ``*args`` and ``**kwargs`` that
- ``num`` database queries are executed.
-
- If a ``"using"`` key is present in ``kwargs`` it is used as the database
- alias for which to check the number of queries. If you wish to call a
- function with a ``using`` parameter you can do it by wrapping the call with
- a ``lambda`` to add an extra parameter::
-
- self.assertNumQueries(7, lambda: my_function(using=7))
-
- You can also use this as a context manager::
-
- with self.assertNumQueries(2):
- Person.objects.create(name="Aaron")
- Person.objects.create(name="Daniel")
-
.. method:: SimpleTestCase.assertHTMLEqual(html1, html2, msg=None)
Asserts that the strings ``html1`` and ``html2`` are equal. The comparison
@@ -1624,6 +1607,8 @@ your test suite.
``html1`` and ``html2`` must be valid HTML. An ``AssertionError`` will be
raised if one of them cannot be parsed.
+ Output in case of error can be customized with the ``msg`` argument.
+
.. method:: SimpleTestCase.assertHTMLNotEqual(html1, html2, msg=None)
Asserts that the strings ``html1`` and ``html2`` are *not* equal. The
@@ -1633,6 +1618,8 @@ your test suite.
``html1`` and ``html2`` must be valid HTML. An ``AssertionError`` will be
raised if one of them cannot be parsed.
+ Output in case of error can be customized with the ``msg`` argument.
+
.. method:: SimpleTestCase.assertXMLEqual(xml1, xml2, msg=None)
.. versionadded:: 1.5
@@ -1644,6 +1631,8 @@ your test suite.
syntax differences. When unvalid XML is passed in any parameter, an
``AssertionError`` is always raised, even if both string are identical.
+ Output in case of error can be customized with the ``msg`` argument.
+
.. method:: SimpleTestCase.assertXMLNotEqual(xml1, xml2, msg=None)
.. versionadded:: 1.5
@@ -1652,6 +1641,68 @@ your test suite.
comparison is based on XML semantics. See
:meth:`~SimpleTestCase.assertXMLEqual` for details.
+ Output in case of error can be customized with the ``msg`` argument.
+
+.. method:: SimpleTestCase.assertInHTML(needle, haystack, count=None, msg_prefix='')
+
+ .. versionadded:: 1.5
+
+ Asserts that the HTML fragment ``needle`` is contained in the ``haystack`` one.
+
+ If the ``count`` integer argument is specified, then additionally the number
+ of ``needle`` occurrences will be strictly verified.
+
+ Whitespace in most cases is ignored, and attribute ordering is not
+ significant. The passed-in arguments must be valid HTML.
+
+.. method:: SimpleTestCase.assertJSONEqual(raw, expected_data, msg=None)
+
+ .. versionadded:: 1.5
+
+ Asserts that the JSON fragments ``raw`` and ``expected_data`` are equal.
+ Usual JSON non-significant whitespace rules apply as the heavyweight is
+ delegated to the :mod:`json` library.
+
+ Output in case of error can be customized with the ``msg`` argument.
+
+.. method:: TransactionTestCase.assertQuerysetEqual(qs, values, transform=repr, ordered=True)
+
+ Asserts that a queryset ``qs`` returns a particular list of values ``values``.
+
+ The comparison of the contents of ``qs`` and ``values`` is performed using
+ the function ``transform``; by default, this means that the ``repr()`` of
+ each value is compared. Any other callable can be used if ``repr()`` doesn't
+ provide a unique or helpful comparison.
+
+ By default, the comparison is also ordering dependent. If ``qs`` doesn't
+ provide an implicit ordering, you can set the ``ordered`` parameter to
+ ``False``, which turns the comparison into a Python set comparison.
+
+ .. versionchanged:: 1.6
+
+ The method now checks for undefined order and raises ``ValueError``
+ if undefined order is spotted. The ordering is seen as undefined if
+ the given ``qs`` isn't ordered and the comparison is against more
+ than one ordered values.
+
+.. method:: TransactionTestCase.assertNumQueries(num, func, *args, **kwargs)
+
+ Asserts that when ``func`` is called with ``*args`` and ``**kwargs`` that
+ ``num`` database queries are executed.
+
+ If a ``"using"`` key is present in ``kwargs`` it is used as the database
+ alias for which to check the number of queries. If you wish to call a
+ function with a ``using`` parameter you can do it by wrapping the call with
+ a ``lambda`` to add an extra parameter::
+
+ self.assertNumQueries(7, lambda: my_function(using=7))
+
+ You can also use this as a context manager::
+
+ with self.assertNumQueries(2):
+ Person.objects.create(name="Aaron")
+ Person.objects.create(name="Daniel")
+
.. _topics-testing-email:
Email services
@@ -1701,7 +1752,7 @@ and contents::
self.assertEqual(mail.outbox[0].subject, 'Subject here')
As noted :ref:`previously <emptying-test-outbox>`, the test outbox is emptied
-at the start of every test in a Django ``TestCase``. To empty the outbox
+at the start of every test in a Django ``*TestCase``. To empty the outbox
manually, assign the empty list to ``mail.outbox``::
from django.core import mail
diff --git a/tests/admin_validation/tests.py b/tests/admin_validation/tests.py
index 16f73c6390..5eee3e7105 100644
--- a/tests/admin_validation/tests.py
+++ b/tests/admin_validation/tests.py
@@ -2,7 +2,6 @@ from __future__ import absolute_import
from django import forms
from django.contrib import admin
-from django.contrib.admin.validation import validate, validate_inline
from django.core.exceptions import ImproperlyConfigured
from django.test import TestCase
@@ -38,13 +37,13 @@ class ValidationTestCase(TestCase):
"fields": ["title", "original_release"],
}),
]
- validate(SongAdmin, Song)
+ SongAdmin.validate(Song)
def test_custom_modelforms_with_fields_fieldsets(self):
"""
# Regression test for #8027: custom ModelForms with fields/fieldsets
"""
- validate(ValidFields, Song)
+ ValidFields.validate(Song)
def test_custom_get_form_with_fieldsets(self):
"""
@@ -52,7 +51,7 @@ class ValidationTestCase(TestCase):
is overridden.
Refs #19445.
"""
- validate(ValidFormFieldsets, Song)
+ ValidFormFieldsets.validate(Song)
def test_exclude_values(self):
"""
@@ -62,16 +61,16 @@ class ValidationTestCase(TestCase):
exclude = ('foo')
self.assertRaisesMessage(ImproperlyConfigured,
"'ExcludedFields1.exclude' must be a list or tuple.",
- validate,
- ExcludedFields1, Book)
+ ExcludedFields1.validate,
+ Book)
def test_exclude_duplicate_values(self):
class ExcludedFields2(admin.ModelAdmin):
exclude = ('name', 'name')
self.assertRaisesMessage(ImproperlyConfigured,
"There are duplicate field(s) in ExcludedFields2.exclude",
- validate,
- ExcludedFields2, Book)
+ ExcludedFields2.validate,
+ Book)
def test_exclude_in_inline(self):
class ExcludedFieldsInline(admin.TabularInline):
@@ -84,8 +83,8 @@ class ValidationTestCase(TestCase):
self.assertRaisesMessage(ImproperlyConfigured,
"'ExcludedFieldsInline.exclude' must be a list or tuple.",
- validate,
- ExcludedFieldsAlbumAdmin, Album)
+ ExcludedFieldsAlbumAdmin.validate,
+ Album)
def test_exclude_inline_model_admin(self):
"""
@@ -102,8 +101,8 @@ class ValidationTestCase(TestCase):
self.assertRaisesMessage(ImproperlyConfigured,
"SongInline cannot exclude the field 'album' - this is the foreign key to the parent model admin_validation.Album.",
- validate,
- AlbumAdmin, Album)
+ AlbumAdmin.validate,
+ Album)
def test_app_label_in_admin_validation(self):
"""
@@ -114,8 +113,8 @@ class ValidationTestCase(TestCase):
self.assertRaisesMessage(ImproperlyConfigured,
"'RawIdNonexistingAdmin.raw_id_fields' refers to field 'nonexisting' that is missing from model 'admin_validation.Album'.",
- validate,
- RawIdNonexistingAdmin, Album)
+ RawIdNonexistingAdmin.validate,
+ Album)
def test_fk_exclusion(self):
"""
@@ -127,28 +126,35 @@ class ValidationTestCase(TestCase):
model = TwoAlbumFKAndAnE
exclude = ("e",)
fk_name = "album1"
- validate_inline(TwoAlbumFKAndAnEInline, None, Album)
+ class MyAdmin(admin.ModelAdmin):
+ inlines = [TwoAlbumFKAndAnEInline]
+ MyAdmin.validate(Album)
+
def test_inline_self_validation(self):
class TwoAlbumFKAndAnEInline(admin.TabularInline):
model = TwoAlbumFKAndAnE
+ class MyAdmin(admin.ModelAdmin):
+ inlines = [TwoAlbumFKAndAnEInline]
self.assertRaisesMessage(Exception,
"<class 'admin_validation.models.TwoAlbumFKAndAnE'> has more than 1 ForeignKey to <class 'admin_validation.models.Album'>",
- validate_inline,
- TwoAlbumFKAndAnEInline, None, Album)
+ MyAdmin.validate, Album)
def test_inline_with_specified(self):
class TwoAlbumFKAndAnEInline(admin.TabularInline):
model = TwoAlbumFKAndAnE
fk_name = "album1"
- validate_inline(TwoAlbumFKAndAnEInline, None, Album)
+
+ class MyAdmin(admin.ModelAdmin):
+ inlines = [TwoAlbumFKAndAnEInline]
+ MyAdmin.validate(Album)
def test_readonly(self):
class SongAdmin(admin.ModelAdmin):
readonly_fields = ("title",)
- validate(SongAdmin, Song)
+ SongAdmin.validate(Song)
def test_readonly_on_method(self):
def my_function(obj):
@@ -157,7 +163,7 @@ class ValidationTestCase(TestCase):
class SongAdmin(admin.ModelAdmin):
readonly_fields = (my_function,)
- validate(SongAdmin, Song)
+ SongAdmin.validate(Song)
def test_readonly_on_modeladmin(self):
class SongAdmin(admin.ModelAdmin):
@@ -166,13 +172,13 @@ class ValidationTestCase(TestCase):
def readonly_method_on_modeladmin(self, obj):
pass
- validate(SongAdmin, Song)
+ SongAdmin.validate(Song)
def test_readonly_method_on_model(self):
class SongAdmin(admin.ModelAdmin):
readonly_fields = ("readonly_method_on_model",)
- validate(SongAdmin, Song)
+ SongAdmin.validate(Song)
def test_nonexistant_field(self):
class SongAdmin(admin.ModelAdmin):
@@ -180,8 +186,8 @@ class ValidationTestCase(TestCase):
self.assertRaisesMessage(ImproperlyConfigured,
"SongAdmin.readonly_fields[1], 'nonexistant' is not a callable or an attribute of 'SongAdmin' or found in the model 'Song'.",
- validate,
- SongAdmin, Song)
+ SongAdmin.validate,
+ Song)
def test_nonexistant_field_on_inline(self):
class CityInline(admin.TabularInline):
@@ -190,8 +196,8 @@ class ValidationTestCase(TestCase):
self.assertRaisesMessage(ImproperlyConfigured,
"CityInline.readonly_fields[0], 'i_dont_exist' is not a callable or an attribute of 'CityInline' or found in the model 'City'.",
- validate_inline,
- CityInline, None, State)
+ CityInline.validate,
+ City)
def test_extra(self):
class SongAdmin(admin.ModelAdmin):
@@ -199,13 +205,13 @@ class ValidationTestCase(TestCase):
if instance.title == "Born to Run":
return "Best Ever!"
return "Status unknown."
- validate(SongAdmin, Song)
+ SongAdmin.validate(Song)
def test_readonly_lambda(self):
class SongAdmin(admin.ModelAdmin):
readonly_fields = (lambda obj: "test",)
- validate(SongAdmin, Song)
+ SongAdmin.validate(Song)
def test_graceful_m2m_fail(self):
"""
@@ -219,8 +225,8 @@ class ValidationTestCase(TestCase):
self.assertRaisesMessage(ImproperlyConfigured,
"'BookAdmin.fields' can't include the ManyToManyField field 'authors' because 'authors' manually specifies a 'through' model.",
- validate,
- BookAdmin, Book)
+ BookAdmin.validate,
+ Book)
def test_cannot_include_through(self):
class FieldsetBookAdmin(admin.ModelAdmin):
@@ -230,20 +236,20 @@ class ValidationTestCase(TestCase):
)
self.assertRaisesMessage(ImproperlyConfigured,
"'FieldsetBookAdmin.fieldsets[1][1]['fields']' can't include the ManyToManyField field 'authors' because 'authors' manually specifies a 'through' model.",
- validate,
- FieldsetBookAdmin, Book)
+ FieldsetBookAdmin.validate,
+ Book)
def test_nested_fields(self):
class NestedFieldsAdmin(admin.ModelAdmin):
fields = ('price', ('name', 'subtitle'))
- validate(NestedFieldsAdmin, Book)
+ NestedFieldsAdmin.validate(Book)
def test_nested_fieldsets(self):
class NestedFieldsetAdmin(admin.ModelAdmin):
fieldsets = (
('Main', {'fields': ('price', ('name', 'subtitle'))}),
)
- validate(NestedFieldsetAdmin, Book)
+ NestedFieldsetAdmin.validate(Book)
def test_explicit_through_override(self):
"""
@@ -260,7 +266,7 @@ class ValidationTestCase(TestCase):
# If the through model is still a string (and hasn't been resolved to a model)
# the validation will fail.
- validate(BookAdmin, Book)
+ BookAdmin.validate(Book)
def test_non_model_fields(self):
"""
@@ -274,7 +280,7 @@ class ValidationTestCase(TestCase):
form = SongForm
fields = ['title', 'extra_data']
- validate(FieldsOnFormOnlyAdmin, Song)
+ FieldsOnFormOnlyAdmin.validate(Song)
def test_non_model_first_field(self):
"""
@@ -292,4 +298,4 @@ class ValidationTestCase(TestCase):
form = SongForm
fields = ['extra_data', 'title']
- validate(FieldsOnFormOnlyAdmin, Song)
+ FieldsOnFormOnlyAdmin.validate(Song)
diff --git a/tests/base/models.py b/tests/base/models.py
index bddb406820..d47ddcfd66 100644
--- a/tests/base/models.py
+++ b/tests/base/models.py
@@ -14,8 +14,10 @@ class CustomBaseModel(models.base.ModelBase):
class MyModel(six.with_metaclass(CustomBaseModel, models.Model)):
- """Model subclass with a custom base using six.with_metaclass."""
+ """Model subclass with a custom base using six.with_metaclass."""
+# This is done to ensure that for Python2 only, defining metaclasses
+# still does not fail to create the model.
if not six.PY3:
class MyModel(models.Model):
diff --git a/tests/cache/tests.py b/tests/cache/tests.py
index 00c51638b7..231a3bfb50 100644
--- a/tests/cache/tests.py
+++ b/tests/cache/tests.py
@@ -28,8 +28,8 @@ from django.middleware.cache import (FetchFromCacheMiddleware,
from django.template import Template
from django.template.response import TemplateResponse
from django.test import TestCase, TransactionTestCase, RequestFactory
-from django.test.utils import override_settings, six
-from django.utils import timezone, translation, unittest
+from django.test.utils import override_settings, IgnorePendingDeprecationWarningsMixin
+from django.utils import six, timezone, translation, unittest
from django.utils.cache import (patch_vary_headers, get_cache_key,
learn_cache_key, patch_cache_control, patch_response_headers)
from django.utils.encoding import force_text
@@ -441,6 +441,34 @@ class BaseCacheTests(object):
self.assertEqual(self.cache.get('key3'), 'sausage')
self.assertEqual(self.cache.get('key4'), 'lobster bisque')
+ def test_forever_timeout(self):
+ '''
+ Passing in None into timeout results in a value that is cached forever
+ '''
+ self.cache.set('key1', 'eggs', None)
+ self.assertEqual(self.cache.get('key1'), 'eggs')
+
+ self.cache.add('key2', 'ham', None)
+ self.assertEqual(self.cache.get('key2'), 'ham')
+
+ self.cache.set_many({'key3': 'sausage', 'key4': 'lobster bisque'}, None)
+ self.assertEqual(self.cache.get('key3'), 'sausage')
+ self.assertEqual(self.cache.get('key4'), 'lobster bisque')
+
+ def test_zero_timeout(self):
+ '''
+ Passing in None into timeout results in a value that is cached forever
+ '''
+ self.cache.set('key1', 'eggs', 0)
+ self.assertEqual(self.cache.get('key1'), None)
+
+ self.cache.add('key2', 'ham', 0)
+ self.assertEqual(self.cache.get('key2'), None)
+
+ self.cache.set_many({'key3': 'sausage', 'key4': 'lobster bisque'}, 0)
+ self.assertEqual(self.cache.get('key3'), None)
+ self.assertEqual(self.cache.get('key4'), None)
+
def test_float_timeout(self):
# Make sure a timeout given as a float doesn't crash anything.
self.cache.set("key1", "spam", 100.2)
@@ -1564,9 +1592,10 @@ def hello_world_view(request, value):
},
},
)
-class CacheMiddlewareTest(TestCase):
+class CacheMiddlewareTest(IgnorePendingDeprecationWarningsMixin, TestCase):
def setUp(self):
+ super(CacheMiddlewareTest, self).setUp()
self.factory = RequestFactory()
self.default_cache = get_cache('default')
self.other_cache = get_cache('other')
@@ -1574,6 +1603,7 @@ class CacheMiddlewareTest(TestCase):
def tearDown(self):
self.default_cache.clear()
self.other_cache.clear()
+ super(CacheMiddlewareTest, self).tearDown()
def test_constructor(self):
"""
diff --git a/tests/csrf_tests/tests.py b/tests/csrf_tests/tests.py
index b9e8cb5f75..841b24bb42 100644
--- a/tests/csrf_tests/tests.py
+++ b/tests/csrf_tests/tests.py
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
+import logging
from django.conf import settings
from django.core.context_processors import csrf
@@ -78,18 +79,18 @@ class CsrfViewMiddlewareTest(TestCase):
def _check_token_present(self, response, csrf_id=None):
self.assertContains(response, "name='csrfmiddlewaretoken' value='%s'" % (csrf_id or self._csrf_id))
- def test_process_view_token_too_long(self):
- """
- Check that if the token is longer than expected, it is ignored and
- a new token is created.
- """
- req = self._get_GET_no_csrf_cookie_request()
- req.COOKIES[settings.CSRF_COOKIE_NAME] = 'x' * 10000000
- CsrfViewMiddleware().process_view(req, token_view, (), {})
- resp = token_view(req)
- resp2 = CsrfViewMiddleware().process_response(req, resp)
- csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False)
- self.assertEqual(len(csrf_cookie.value), CSRF_KEY_LENGTH)
+ def test_process_view_token_too_long(self):
+ """
+ Check that if the token is longer than expected, it is ignored and
+ a new token is created.
+ """
+ req = self._get_GET_no_csrf_cookie_request()
+ req.COOKIES[settings.CSRF_COOKIE_NAME] = 'x' * 10000000
+ CsrfViewMiddleware().process_view(req, token_view, (), {})
+ resp = token_view(req)
+ resp2 = CsrfViewMiddleware().process_response(req, resp)
+ csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False)
+ self.assertEqual(len(csrf_cookie.value), CSRF_KEY_LENGTH)
def test_process_response_get_token_used(self):
"""
@@ -353,3 +354,29 @@ class CsrfViewMiddlewareTest(TestCase):
resp2 = CsrfViewMiddleware().process_response(req, resp)
self.assertTrue(resp2.cookies.get(settings.CSRF_COOKIE_NAME, False))
self.assertTrue('Cookie' in resp2.get('Vary',''))
+
+ def test_ensures_csrf_cookie_no_logging(self):
+ """
+ Tests that ensure_csrf_cookie doesn't log warnings. See #19436.
+ """
+ @ensure_csrf_cookie
+ def view(request):
+ # Doesn't insert a token or anything
+ return HttpResponse(content="")
+
+ class TestHandler(logging.Handler):
+ def emit(self, record):
+ raise Exception("This shouldn't have happened!")
+
+ logger = logging.getLogger('django.request')
+ test_handler = TestHandler()
+ old_log_level = logger.level
+ try:
+ logger.addHandler(test_handler)
+ logger.setLevel(logging.WARNING)
+
+ req = self._get_GET_no_csrf_cookie_request()
+ resp = view(req)
+ finally:
+ logger.removeHandler(test_handler)
+ logger.setLevel(old_log_level)
diff --git a/tests/defaultfilters/tests.py b/tests/defaultfilters/tests.py
index 21734faf95..be16719c8e 100644
--- a/tests/defaultfilters/tests.py
+++ b/tests/defaultfilters/tests.py
@@ -527,24 +527,26 @@ class DefaultFiltersTests(TestCase):
def test_timesince(self):
# real testing is done in timesince.py, where we can provide our own 'now'
+ # NOTE: \xa0 avoids wrapping between value and unit
self.assertEqual(
timesince_filter(datetime.datetime.now() - datetime.timedelta(1)),
- '1 day')
+ '1\xa0day')
self.assertEqual(
timesince_filter(datetime.datetime(2005, 12, 29),
datetime.datetime(2005, 12, 30)),
- '1 day')
+ '1\xa0day')
def test_timeuntil(self):
+ # NOTE: \xa0 avoids wrapping between value and unit
self.assertEqual(
timeuntil_filter(datetime.datetime.now() + datetime.timedelta(1, 1)),
- '1 day')
+ '1\xa0day')
self.assertEqual(
timeuntil_filter(datetime.datetime(2005, 12, 30),
datetime.datetime(2005, 12, 29)),
- '1 day')
+ '1\xa0day')
def test_default(self):
self.assertEqual(default("val", "default"), 'val')
@@ -574,43 +576,45 @@ class DefaultFiltersTests(TestCase):
'get out of town')
def test_filesizeformat(self):
- self.assertEqual(filesizeformat(1023), '1023 bytes')
- self.assertEqual(filesizeformat(1024), '1.0 KB')
- self.assertEqual(filesizeformat(10*1024), '10.0 KB')
- self.assertEqual(filesizeformat(1024*1024-1), '1024.0 KB')
- self.assertEqual(filesizeformat(1024*1024), '1.0 MB')
- self.assertEqual(filesizeformat(1024*1024*50), '50.0 MB')
- self.assertEqual(filesizeformat(1024*1024*1024-1), '1024.0 MB')
- self.assertEqual(filesizeformat(1024*1024*1024), '1.0 GB')
- self.assertEqual(filesizeformat(1024*1024*1024*1024), '1.0 TB')
- self.assertEqual(filesizeformat(1024*1024*1024*1024*1024), '1.0 PB')
+ # NOTE: \xa0 avoids wrapping between value and unit
+ self.assertEqual(filesizeformat(1023), '1023\xa0bytes')
+ self.assertEqual(filesizeformat(1024), '1.0\xa0KB')
+ self.assertEqual(filesizeformat(10*1024), '10.0\xa0KB')
+ self.assertEqual(filesizeformat(1024*1024-1), '1024.0\xa0KB')
+ self.assertEqual(filesizeformat(1024*1024), '1.0\xa0MB')
+ self.assertEqual(filesizeformat(1024*1024*50), '50.0\xa0MB')
+ self.assertEqual(filesizeformat(1024*1024*1024-1), '1024.0\xa0MB')
+ self.assertEqual(filesizeformat(1024*1024*1024), '1.0\xa0GB')
+ self.assertEqual(filesizeformat(1024*1024*1024*1024), '1.0\xa0TB')
+ self.assertEqual(filesizeformat(1024*1024*1024*1024*1024), '1.0\xa0PB')
self.assertEqual(filesizeformat(1024*1024*1024*1024*1024*2000),
- '2000.0 PB')
- self.assertEqual(filesizeformat(complex(1,-1)), '0 bytes')
- self.assertEqual(filesizeformat(""), '0 bytes')
+ '2000.0\xa0PB')
+ self.assertEqual(filesizeformat(complex(1,-1)), '0\xa0bytes')
+ self.assertEqual(filesizeformat(""), '0\xa0bytes')
self.assertEqual(filesizeformat("\N{GREEK SMALL LETTER ALPHA}"),
- '0 bytes')
+ '0\xa0bytes')
def test_localized_filesizeformat(self):
+ # NOTE: \xa0 avoids wrapping between value and unit
with self.settings(USE_L10N=True):
with translation.override('de', deactivate=True):
- self.assertEqual(filesizeformat(1023), '1023 Bytes')
- self.assertEqual(filesizeformat(1024), '1,0 KB')
- self.assertEqual(filesizeformat(10*1024), '10,0 KB')
- self.assertEqual(filesizeformat(1024*1024-1), '1024,0 KB')
- self.assertEqual(filesizeformat(1024*1024), '1,0 MB')
- self.assertEqual(filesizeformat(1024*1024*50), '50,0 MB')
- self.assertEqual(filesizeformat(1024*1024*1024-1), '1024,0 MB')
- self.assertEqual(filesizeformat(1024*1024*1024), '1,0 GB')
- self.assertEqual(filesizeformat(1024*1024*1024*1024), '1,0 TB')
+ self.assertEqual(filesizeformat(1023), '1023\xa0Bytes')
+ self.assertEqual(filesizeformat(1024), '1,0\xa0KB')
+ self.assertEqual(filesizeformat(10*1024), '10,0\xa0KB')
+ self.assertEqual(filesizeformat(1024*1024-1), '1024,0\xa0KB')
+ self.assertEqual(filesizeformat(1024*1024), '1,0\xa0MB')
+ self.assertEqual(filesizeformat(1024*1024*50), '50,0\xa0MB')
+ self.assertEqual(filesizeformat(1024*1024*1024-1), '1024,0\xa0MB')
+ self.assertEqual(filesizeformat(1024*1024*1024), '1,0\xa0GB')
+ self.assertEqual(filesizeformat(1024*1024*1024*1024), '1,0\xa0TB')
self.assertEqual(filesizeformat(1024*1024*1024*1024*1024),
- '1,0 PB')
+ '1,0\xa0PB')
self.assertEqual(filesizeformat(1024*1024*1024*1024*1024*2000),
- '2000,0 PB')
- self.assertEqual(filesizeformat(complex(1,-1)), '0 Bytes')
- self.assertEqual(filesizeformat(""), '0 Bytes')
+ '2000,0\xa0PB')
+ self.assertEqual(filesizeformat(complex(1,-1)), '0\xa0Bytes')
+ self.assertEqual(filesizeformat(""), '0\xa0Bytes')
self.assertEqual(filesizeformat("\N{GREEK SMALL LETTER ALPHA}"),
- '0 Bytes')
+ '0\xa0Bytes')
def test_pluralize(self):
self.assertEqual(pluralize(1), '')
diff --git a/tests/fixtures/tests.py b/tests/fixtures/tests.py
index 103612198e..f954933046 100644
--- a/tests/fixtures/tests.py
+++ b/tests/fixtures/tests.py
@@ -1,5 +1,7 @@
from __future__ import absolute_import
+import warnings
+
from django.contrib.sites.models import Site
from django.core import management
from django.db import connection, IntegrityError
@@ -137,8 +139,18 @@ class FixtureLoadingTests(DumpDataAssertMixin, TestCase):
'<Book: Music for all ages by Artist formerly known as "Prince" and Django Reinhardt>'
])
- # Load a fixture that doesn't exist
- management.call_command('loaddata', 'unknown.json', verbosity=0, commit=False)
+ # Loading a fixture that doesn't exist emits a warning
+ with warnings.catch_warnings(record=True) as w:
+ management.call_command('loaddata', 'unknown.json', verbosity=0,
+ commit=False)
+ self.assertEqual(len(w), 1)
+ self.assertTrue(w[0].message, "No fixture named 'unknown' found.")
+
+ # An attempt to load a nonexistent 'initial_data' fixture isn't an error
+ with warnings.catch_warnings(record=True) as w:
+ management.call_command('loaddata', 'initial_data.json', verbosity=0,
+ commit=False)
+ self.assertEqual(len(w), 0)
# object list is unaffected
self.assertQuerysetEqual(Article.objects.all(), [
@@ -273,10 +285,11 @@ class FixtureLoadingTests(DumpDataAssertMixin, TestCase):
def test_unmatched_identifier_loading(self):
# Try to load db fixture 3. This won't load because the database identifier doesn't match
- management.call_command('loaddata', 'db_fixture_3', verbosity=0, commit=False)
- self.assertQuerysetEqual(Article.objects.all(), [])
+ with warnings.catch_warnings(record=True):
+ management.call_command('loaddata', 'db_fixture_3', verbosity=0, commit=False)
- management.call_command('loaddata', 'db_fixture_3', verbosity=0, using='default', commit=False)
+ with warnings.catch_warnings(record=True):
+ management.call_command('loaddata', 'db_fixture_3', verbosity=0, using='default', commit=False)
self.assertQuerysetEqual(Article.objects.all(), [])
def test_output_formats(self):
diff --git a/tests/fixtures_model_package/tests.py b/tests/fixtures_model_package/tests.py
index c250f647ce..fbd0271336 100644
--- a/tests/fixtures_model_package/tests.py
+++ b/tests/fixtures_model_package/tests.py
@@ -1,5 +1,7 @@
from __future__ import unicode_literals
+import warnings
+
from django.core import management
from django.db import transaction
from django.test import TestCase, TransactionTestCase
@@ -100,7 +102,9 @@ class FixtureTestCase(TestCase):
)
# Load a fixture that doesn't exist
- management.call_command("loaddata", "unknown.json", verbosity=0, commit=False)
+ with warnings.catch_warnings(record=True):
+ management.call_command("loaddata", "unknown.json", verbosity=0, commit=False)
+
self.assertQuerysetEqual(
Article.objects.all(), [
"Django conquers world!",
diff --git a/tests/fixtures_regress/tests.py b/tests/fixtures_regress/tests.py
index 02e923e386..5114302267 100644
--- a/tests/fixtures_regress/tests.py
+++ b/tests/fixtures_regress/tests.py
@@ -4,6 +4,7 @@ from __future__ import absolute_import, unicode_literals
import os
import re
+import warnings
from django.core.serializers.base import DeserializationError
from django.core import management
@@ -441,13 +442,14 @@ class TestFixtures(TestCase):
def test_loaddata_not_existant_fixture_file(self):
stdout_output = StringIO()
- management.call_command(
- 'loaddata',
- 'this_fixture_doesnt_exist',
- verbosity=2,
- commit=False,
- stdout=stdout_output,
- )
+ with warnings.catch_warnings(record=True):
+ management.call_command(
+ 'loaddata',
+ 'this_fixture_doesnt_exist',
+ verbosity=2,
+ commit=False,
+ stdout=stdout_output,
+ )
self.assertTrue("No xml fixture 'this_fixture_doesnt_exist' in" in
force_text(stdout_output.getvalue()))
diff --git a/tests/forms_tests/tests/test_extra.py b/tests/forms_tests/tests/test_extra.py
index a83cdfc05f..ea0f063c30 100644
--- a/tests/forms_tests/tests/test_extra.py
+++ b/tests/forms_tests/tests/test_extra.py
@@ -569,6 +569,14 @@ class FormsExtraTestCase(TestCase, AssertFormErrorsMixin):
f = GenericIPAddressField(unpack_ipv4=True)
self.assertEqual(f.clean(' ::ffff:0a0a:0a0a'), '10.10.10.10')
+ def test_slugfield_normalization(self):
+ f = SlugField()
+ self.assertEqual(f.clean(' aa-bb-cc '), 'aa-bb-cc')
+
+ def test_urlfield_normalization(self):
+ f = URLField()
+ self.assertEqual(f.clean('http://example.com/ '), 'http://example.com/')
+
def test_smart_text(self):
class Test:
if six.PY3:
diff --git a/tests/forms_tests/tests/test_fields.py b/tests/forms_tests/tests/test_fields.py
index 7516de29b4..47c637befa 100644
--- a/tests/forms_tests/tests/test_fields.py
+++ b/tests/forms_tests/tests/test_fields.py
@@ -125,6 +125,15 @@ class FieldsTests(SimpleTestCase):
self.assertEqual(f.max_length, None)
self.assertEqual(f.min_length, 10)
+ def test_charfield_length_not_int(self):
+ """
+ Ensure that setting min_length or max_length to something that is not a
+ number returns an exception.
+ """
+ self.assertRaises(ValueError, CharField, min_length='a')
+ self.assertRaises(ValueError, CharField, max_length='a')
+ self.assertRaises(ValueError, CharField, 'a')
+
def test_charfield_widget_attrs(self):
"""
Ensure that CharField.widget_attrs() always returns a dictionary.
diff --git a/tests/generic_views/test_base.py b/tests/generic_views/test_base.py
index 0e84e17132..ffdad4b69f 100644
--- a/tests/generic_views/test_base.py
+++ b/tests/generic_views/test_base.py
@@ -411,3 +411,36 @@ class GetContextDataTest(unittest.TestCase):
# test that kwarg overrides values assigned higher up
context = test_view.get_context_data(test_name='test_value')
self.assertEqual(context['test_name'], 'test_value')
+
+ def test_object_at_custom_name_in_context_data(self):
+ # Checks 'pony' key presence in dict returned by get_context_date
+ test_view = views.CustomSingleObjectView()
+ test_view.context_object_name = 'pony'
+ context = test_view.get_context_data()
+ self.assertEqual(context['pony'], test_view.object)
+
+ def test_object_in_get_context_data(self):
+ # Checks 'object' key presence in dict returned by get_context_date #20234
+ test_view = views.CustomSingleObjectView()
+ context = test_view.get_context_data()
+ self.assertEqual(context['object'], test_view.object)
+
+
+class UseMultipleObjectMixinTest(unittest.TestCase):
+ rf = RequestFactory()
+
+ def test_use_queryset_from_view(self):
+ test_view = views.CustomMultipleObjectMixinView()
+ test_view.get(self.rf.get('/'))
+ # Don't pass queryset as argument
+ context = test_view.get_context_data()
+ self.assertEqual(context['object_list'], test_view.queryset)
+
+ def test_overwrite_queryset(self):
+ test_view = views.CustomMultipleObjectMixinView()
+ test_view.get(self.rf.get('/'))
+ queryset = [{'name': 'Lennon'}, {'name': 'Ono'}]
+ self.assertNotEqual(test_view.queryset, queryset)
+ # Overwrite the view's queryset with queryset from kwarg
+ context = test_view.get_context_data(object_list=queryset)
+ self.assertEqual(context['object_list'], queryset)
diff --git a/tests/generic_views/views.py b/tests/generic_views/views.py
index aa8777e8c6..fd331f14b7 100644
--- a/tests/generic_views/views.py
+++ b/tests/generic_views/views.py
@@ -1,7 +1,6 @@
from __future__ import absolute_import
from django.contrib.auth.decorators import login_required
-from django.contrib.messages.views import SuccessMessageMixin
from django.core.paginator import Paginator
from django.core.urlresolvers import reverse, reverse_lazy
from django.utils.decorators import method_decorator
@@ -201,6 +200,17 @@ class BookDetailGetObjectCustomQueryset(BookDetail):
return super(BookDetailGetObjectCustomQueryset,self).get_object(
queryset=Book.objects.filter(pk=2))
+
+class CustomMultipleObjectMixinView(generic.list.MultipleObjectMixin, generic.View):
+ queryset = [
+ {'name': 'John'},
+ {'name': 'Yoko'},
+ ]
+
+ def get(self, request):
+ self.object_list = self.get_queryset()
+
+
class CustomContextView(generic.detail.SingleObjectMixin, generic.View):
model = Book
object = Book(name='dummy')
@@ -216,6 +226,10 @@ class CustomContextView(generic.detail.SingleObjectMixin, generic.View):
def get_context_object_name(self, obj):
return "test_name"
+class CustomSingleObjectView(generic.detail.SingleObjectMixin, generic.View):
+ model = Book
+ object = Book(name="dummy")
+
class BookSigningConfig(object):
model = BookSigning
date_field = 'event_date'
diff --git a/tests/i18n/__init__.py b/tests/i18n/__init__.py
index e69de29bb2..a3e9ce7053 100644
--- a/tests/i18n/__init__.py
+++ b/tests/i18n/__init__.py
@@ -0,0 +1,17 @@
+from threading import local
+
+
+class TransRealMixin(object):
+ """This is the only way to reset the translation machinery. Otherwise
+ the test suite occasionally fails because of global state pollution
+ between tests."""
+ def flush_caches(self):
+ from django.utils.translation import trans_real
+ trans_real._translations = {}
+ trans_real._active = local()
+ trans_real._default = None
+ trans_real._accepted = {}
+
+ def tearDown(self):
+ self.flush_caches()
+ super(TransRealMixin, self).tearDown()
diff --git a/tests/i18n/contenttypes/tests.py b/tests/i18n/contenttypes/tests.py
index 5e8a9823e1..cbac9ec5da 100644
--- a/tests/i18n/contenttypes/tests.py
+++ b/tests/i18n/contenttypes/tests.py
@@ -10,6 +10,8 @@ from django.utils._os import upath
from django.utils import six
from django.utils import translation
+from i18n import TransRealMixin
+
@override_settings(
USE_I18N=True,
@@ -22,7 +24,7 @@ from django.utils import translation
('fr', 'French'),
),
)
-class ContentTypeTests(TestCase):
+class ContentTypeTests(TransRealMixin, TestCase):
def test_verbose_name(self):
company_type = ContentType.objects.get(app_label='i18n', model='company')
with translation.override('en'):
diff --git a/tests/i18n/tests.py b/tests/i18n/tests.py
index 1022c8d2f1..137270f830 100644
--- a/tests/i18n/tests.py
+++ b/tests/i18n/tests.py
@@ -30,7 +30,8 @@ from django.utils.translation import (activate, deactivate,
ngettext, ngettext_lazy,
ungettext, ungettext_lazy,
pgettext, pgettext_lazy,
- npgettext, npgettext_lazy)
+ npgettext, npgettext_lazy,
+ check_for_language)
from .commands.tests import can_run_extraction_tests, can_run_compilation_tests
if can_run_extraction_tests:
@@ -43,6 +44,7 @@ if can_run_compilation_tests:
from .commands.compilation import (PoFileTests, PoFileContentsTests,
PercentRenderingTests, MultipleLocaleCompilationTests,
CompilationErrorHandling)
+from . import TransRealMixin
from .forms import I18nForm, SelectDateForm, SelectDateWidget, CompanyForm
from .models import Company, TestModel
@@ -52,7 +54,8 @@ extended_locale_paths = settings.LOCALE_PATHS + (
os.path.join(here, 'other', 'locale'),
)
-class TranslationTests(TestCase):
+
+class TranslationTests(TransRealMixin, TestCase):
def test_override(self):
activate('de')
@@ -333,10 +336,43 @@ class TranslationTests(TestCase):
self.assertEqual(rendered, 'My other name is James.')
+class TranslationThreadSafetyTests(TestCase):
+ """Specifically not using TransRealMixin here to test threading."""
+
+ def setUp(self):
+ self._old_language = get_language()
+ self._translations = trans_real._translations
+
+ # here we rely on .split() being called inside the _fetch()
+ # in trans_real.translation()
+ class sideeffect_str(str):
+ def split(self, *args, **kwargs):
+ res = str.split(self, *args, **kwargs)
+ trans_real._translations['en-YY'] = None
+ return res
+
+ trans_real._translations = {sideeffect_str('en-XX'): None}
+
+ def tearDown(self):
+ trans_real._translations = self._translations
+ activate(self._old_language)
+
+ def test_bug14894_translation_activate_thread_safety(self):
+ translation_count = len(trans_real._translations)
+ try:
+ translation.activate('pl')
+ except RuntimeError:
+ self.fail('translation.activate() is not thread-safe')
+
+ # make sure sideeffect_str actually added a new translation
+ self.assertLess(translation_count, len(trans_real._translations))
+
+
@override_settings(USE_L10N=True)
-class FormattingTests(TestCase):
+class FormattingTests(TransRealMixin, TestCase):
def setUp(self):
+ super(FormattingTests, self).setUp()
self.n = decimal.Decimal('66666.666')
self.f = 99999.999
self.d = datetime.date(2009, 12, 31)
@@ -738,9 +774,10 @@ class FormattingTests(TestCase):
self.assertEqual(template2.render(context), output2)
self.assertEqual(template3.render(context), output3)
-class MiscTests(TestCase):
+class MiscTests(TransRealMixin, TestCase):
def setUp(self):
+ super(MiscTests, self).setUp()
self.rf = RequestFactory()
def test_parse_spec_http_header(self):
@@ -884,17 +921,15 @@ class MiscTests(TestCase):
self.assertEqual(t_plur.render(Context({'percent': 42, 'num': 4})), '%(percent)s% represents 4 objects')
-class ResolutionOrderI18NTests(TestCase):
+class ResolutionOrderI18NTests(TransRealMixin, TestCase):
def setUp(self):
- # Okay, this is brutal, but we have no other choice to fully reset
- # the translation framework
- trans_real._active = local()
- trans_real._translations = {}
+ super(ResolutionOrderI18NTests, self).setUp()
activate('de')
def tearDown(self):
deactivate()
+ super(ResolutionOrderI18NTests, self).tearDown()
def assertUgettext(self, msgid, msgstr):
result = ugettext(msgid)
@@ -967,15 +1002,17 @@ class TestLanguageInfo(TestCase):
six.assertRaisesRegex(self, KeyError, r"Unknown language code xx-xx and xx\.", get_language_info, 'xx-xx')
-class MultipleLocaleActivationTests(TestCase):
+class MultipleLocaleActivationTests(TransRealMixin, TestCase):
"""
Tests for template rendering behavior when multiple locales are activated
during the lifetime of the same process.
"""
def setUp(self):
+ super(MultipleLocaleActivationTests, self).setUp()
self._old_language = get_language()
def tearDown(self):
+ super(MultipleLocaleActivationTests, self).tearDown()
activate(self._old_language)
def test_single_locale_activation(self):
@@ -1104,7 +1141,7 @@ class MultipleLocaleActivationTests(TestCase):
'django.middleware.common.CommonMiddleware',
),
)
-class LocaleMiddlewareTests(TestCase):
+class LocaleMiddlewareTests(TransRealMixin, TestCase):
urls = 'i18n.urls'
@@ -1114,3 +1151,41 @@ class LocaleMiddlewareTests(TestCase):
self.assertContains(response, "Oui/Non")
response = self.client.get('/en/streaming/')
self.assertContains(response, "Yes/No")
+
+@override_settings(
+ USE_I18N=True,
+ LANGUAGES=(
+ ('bg', 'Bulgarian'),
+ ('en-us', 'English'),
+ ),
+ MIDDLEWARE_CLASSES=(
+ 'django.middleware.locale.LocaleMiddleware',
+ 'django.middleware.common.CommonMiddleware',
+ ),
+)
+class CountrySpecificLanguageTests(TransRealMixin, TestCase):
+
+ urls = 'i18n.urls'
+
+ def setUp(self):
+ super(CountrySpecificLanguageTests, self).setUp()
+ self.rf = RequestFactory()
+
+ def test_check_for_language(self):
+ self.assertTrue(check_for_language('en'))
+ self.assertTrue(check_for_language('en-us'))
+ self.assertTrue(check_for_language('en-US'))
+
+
+ def test_get_language_from_request(self):
+ # issue 19919
+ r = self.rf.get('/')
+ r.COOKIES = {}
+ r.META = {'HTTP_ACCEPT_LANGUAGE': 'en-US,en;q=0.8,bg;q=0.6,ru;q=0.4'}
+ lang = get_language_from_request(r)
+ self.assertEqual('en-us', lang)
+ r = self.rf.get('/')
+ r.COOKIES = {}
+ r.META = {'HTTP_ACCEPT_LANGUAGE': 'bg-bg,en-US;q=0.8,en;q=0.6,ru;q=0.4'}
+ lang = get_language_from_request(r)
+ self.assertEqual('bg', lang)
diff --git a/tests/invalid_models/invalid_models/models.py b/tests/invalid_models/invalid_models/models.py
index 3c21e1ddb8..6ba3f04e5e 100644
--- a/tests/invalid_models/invalid_models/models.py
+++ b/tests/invalid_models/invalid_models/models.py
@@ -375,8 +375,8 @@ invalid_models.fielderrors: "decimalfield3": DecimalFields require a "max_digits
invalid_models.fielderrors: "decimalfield4": DecimalFields require a "max_digits" attribute value that is greater than or equal to the value of the "decimal_places" attribute.
invalid_models.fielderrors: "filefield": FileFields require an "upload_to" attribute.
invalid_models.fielderrors: "choices": "choices" should be iterable (e.g., a tuple or list).
-invalid_models.fielderrors: "choices2": "choices" should be a sequence of two-tuples.
-invalid_models.fielderrors: "choices2": "choices" should be a sequence of two-tuples.
+invalid_models.fielderrors: "choices2": "choices" should be a sequence of two-item iterables (e.g. list of 2 item tuples).
+invalid_models.fielderrors: "choices2": "choices" should be a sequence of two-item iterables (e.g. list of 2 item tuples).
invalid_models.fielderrors: "index": "db_index" should be either None, True or False.
invalid_models.fielderrors: "field_": Field names cannot end with underscores, because this would lead to ambiguous queryset filters.
invalid_models.fielderrors: "nullbool": BooleanFields do not accept null values. Use a NullBooleanField instead.
diff --git a/tests/middleware/tests.py b/tests/middleware/tests.py
index e526da4772..1ff8390f31 100644
--- a/tests/middleware/tests.py
+++ b/tests/middleware/tests.py
@@ -18,14 +18,12 @@ from django.middleware.http import ConditionalGetMiddleware
from django.middleware.gzip import GZipMiddleware
from django.middleware.transaction import TransactionMiddleware
from django.test import TransactionTestCase, TestCase, RequestFactory
-from django.test.utils import override_settings
+from django.test.utils import override_settings, IgnorePendingDeprecationWarningsMixin
from django.utils import six
from django.utils.encoding import force_str
from django.utils.six.moves import xrange
from django.utils.unittest import expectedFailure, skipIf
-from transactions.tests import IgnorePendingDeprecationWarningsMixin
-
from .models import Band
diff --git a/tests/model_forms_regress/tests.py b/tests/model_forms_regress/tests.py
index 0e033e033f..80900a59e0 100644
--- a/tests/model_forms_regress/tests.py
+++ b/tests/model_forms_regress/tests.py
@@ -92,6 +92,41 @@ class OverrideCleanTests(TestCase):
self.assertEqual(form.instance.left, 1)
+
+class PartiallyLocalizedTripleForm(forms.ModelForm):
+ class Meta:
+ model = Triple
+ localized_fields = ('left', 'right',)
+
+
+class FullyLocalizedTripleForm(forms.ModelForm):
+ class Meta:
+ model = Triple
+ localized_fields = "__all__"
+
+class LocalizedModelFormTest(TestCase):
+ def test_model_form_applies_localize_to_some_fields(self):
+ f = PartiallyLocalizedTripleForm({'left': 10, 'middle': 10, 'right': 10})
+ self.assertTrue(f.is_valid())
+ self.assertTrue(f.fields['left'].localize)
+ self.assertFalse(f.fields['middle'].localize)
+ self.assertTrue(f.fields['right'].localize)
+
+ def test_model_form_applies_localize_to_all_fields(self):
+ f = FullyLocalizedTripleForm({'left': 10, 'middle': 10, 'right': 10})
+ self.assertTrue(f.is_valid())
+ self.assertTrue(f.fields['left'].localize)
+ self.assertTrue(f.fields['middle'].localize)
+ self.assertTrue(f.fields['right'].localize)
+
+ def test_model_form_refuses_arbitrary_string(self):
+ with self.assertRaises(TypeError):
+ class BrokenLocalizedTripleForm(forms.ModelForm):
+ class Meta:
+ model = Triple
+ localized_fields = "foo"
+
+
# Regression test for #12960.
# Make sure the cleaned_data returned from ModelForm.clean() is applied to the
# model instance.
diff --git a/tests/model_formsets_regress/tests.py b/tests/model_formsets_regress/tests.py
index 38ebd9d24b..c8fb1e76c1 100644
--- a/tests/model_formsets_regress/tests.py
+++ b/tests/model_formsets_regress/tests.py
@@ -273,6 +273,7 @@ class UserSiteForm(forms.ModelForm):
'id': CustomWidget,
'data': CustomWidget,
}
+ localized_fields = ('data',)
class Callback(object):
@@ -297,19 +298,23 @@ class FormfieldCallbackTests(TestCase):
form = Formset().forms[0]
self.assertTrue(isinstance(form['id'].field.widget, CustomWidget))
self.assertTrue(isinstance(form['data'].field.widget, CustomWidget))
+ self.assertFalse(form.fields['id'].localize)
+ self.assertTrue(form.fields['data'].localize)
def test_modelformset_factory_default(self):
Formset = modelformset_factory(UserSite, form=UserSiteForm)
form = Formset().forms[0]
self.assertTrue(isinstance(form['id'].field.widget, CustomWidget))
self.assertTrue(isinstance(form['data'].field.widget, CustomWidget))
+ self.assertFalse(form.fields['id'].localize)
+ self.assertTrue(form.fields['data'].localize)
def assertCallbackCalled(self, callback):
id_field, user_field, data_field = UserSite._meta.fields
expected_log = [
(id_field, {'widget': CustomWidget}),
(user_field, {}),
- (data_field, {'widget': CustomWidget}),
+ (data_field, {'widget': CustomWidget, 'localize': True}),
]
self.assertEqual(callback.log, expected_log)
diff --git a/tests/special_headers/__init__.py b/tests/model_validation/__init__.py
index e69de29bb2..e69de29bb2 100644
--- a/tests/special_headers/__init__.py
+++ b/tests/model_validation/__init__.py
diff --git a/tests/model_validation/models.py b/tests/model_validation/models.py
new file mode 100644
index 0000000000..9a2a5f7cd0
--- /dev/null
+++ b/tests/model_validation/models.py
@@ -0,0 +1,27 @@
+from django.db import models
+
+
+class ThingItem(object):
+
+ def __init__(self, value, display):
+ self.value = value
+ self.display = display
+
+ def __iter__(self):
+ return (x for x in [self.value, self.display])
+
+ def __len__(self):
+ return 2
+
+
+class Things(object):
+
+ def __iter__(self):
+ return (x for x in [ThingItem(1, 2), ThingItem(3, 4)])
+
+
+class ThingWithIterableChoices(models.Model):
+
+ # Testing choices= Iterable of Iterables
+ # See: https://code.djangoproject.com/ticket/20430
+ thing = models.CharField(max_length=100, blank=True, choices=Things())
diff --git a/tests/model_validation/tests.py b/tests/model_validation/tests.py
new file mode 100644
index 0000000000..ffd0d89412
--- /dev/null
+++ b/tests/model_validation/tests.py
@@ -0,0 +1,13 @@
+from django.core import management
+from django.test import TestCase
+from django.utils import six
+
+
+class ModelValidationTest(TestCase):
+
+ def test_models_validate(self):
+ # All our models should validate properly
+ # Validation Tests:
+ # * choices= Iterable of Iterables
+ # See: https://code.djangoproject.com/ticket/20430
+ management.call_command("validate", stdout=six.StringIO())
diff --git a/tests/modeladmin/tests.py b/tests/modeladmin/tests.py
index 0d933bc1f9..f89f1c20ec 100644
--- a/tests/modeladmin/tests.py
+++ b/tests/modeladmin/tests.py
@@ -7,7 +7,6 @@ from django.conf import settings
from django.contrib.admin.options import (ModelAdmin, TabularInline,
HORIZONTAL, VERTICAL)
from django.contrib.admin.sites import AdminSite
-from django.contrib.admin.validation import validate
from django.contrib.admin.widgets import AdminDateWidget, AdminRadioSelect
from django.contrib.admin import (SimpleListFilter,
BooleanFieldListFilter)
@@ -523,8 +522,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.raw_id_fields' must be a list or tuple.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -534,8 +532,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.raw_id_fields' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -545,15 +542,14 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.raw_id_fields\[0\]', 'name' must be either a ForeignKey or ManyToManyField.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
class ValidationTestModelAdmin(ModelAdmin):
raw_id_fields = ('users',)
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_fieldsets_validation(self):
@@ -563,8 +559,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.fieldsets' must be a list or tuple.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -574,8 +569,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.fieldsets\[0\]' must be a list or tuple.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -585,8 +579,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.fieldsets\[0\]' does not have exactly two elements.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -596,8 +589,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.fieldsets\[0\]\[1\]' must be a dictionary.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -607,15 +599,14 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'fields' key is required in ValidationTestModelAdmin.fieldsets\[0\]\[1\] field options dict.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
class ValidationTestModelAdmin(ModelAdmin):
fieldsets = (("General", {"fields": ("name",)}),)
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
class ValidationTestModelAdmin(ModelAdmin):
fieldsets = (("General", {"fields": ("name",)}),)
@@ -624,8 +615,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"Both fieldsets and fields are specified in ValidationTestModelAdmin.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -635,8 +625,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"There are duplicate field\(s\) in ValidationTestModelAdmin.fieldsets",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -646,8 +635,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"There are duplicate field\(s\) in ValidationTestModelAdmin.fields",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -662,8 +650,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"ValidationTestModelAdmin.form does not inherit from BaseModelForm.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -676,7 +663,7 @@ class ValidationTests(unittest.TestCase):
}),
)
- validate(BandAdmin, Band)
+ BandAdmin.validate(Band)
class AdminBandForm(forms.ModelForm):
delete = forms.BooleanField()
@@ -690,7 +677,7 @@ class ValidationTests(unittest.TestCase):
}),
)
- validate(BandAdmin, Band)
+ BandAdmin.validate(Band)
def test_filter_vertical_validation(self):
@@ -700,8 +687,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.filter_vertical' must be a list or tuple.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -711,8 +697,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.filter_vertical' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -722,15 +707,14 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.filter_vertical\[0\]' must be a ManyToManyField.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
class ValidationTestModelAdmin(ModelAdmin):
filter_vertical = ("users",)
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_filter_horizontal_validation(self):
@@ -740,8 +724,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.filter_horizontal' must be a list or tuple.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -751,8 +734,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.filter_horizontal' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -762,15 +744,14 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.filter_horizontal\[0\]' must be a ManyToManyField.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
class ValidationTestModelAdmin(ModelAdmin):
filter_horizontal = ("users",)
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_radio_fields_validation(self):
@@ -780,8 +761,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.radio_fields' must be a dictionary.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -791,8 +771,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.radio_fields' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -802,8 +781,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.radio_fields\['name'\]' is neither an instance of ForeignKey nor does have choices set.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -813,15 +791,14 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.radio_fields\['state'\]' is neither admin.HORIZONTAL nor admin.VERTICAL.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
class ValidationTestModelAdmin(ModelAdmin):
radio_fields = {"state": VERTICAL}
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_prepopulated_fields_validation(self):
@@ -831,8 +808,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.prepopulated_fields' must be a dictionary.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -842,8 +818,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.prepopulated_fields' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -853,8 +828,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.prepopulated_fields\['slug'\]\[0\]' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -864,15 +838,14 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.prepopulated_fields\['users'\]' is either a DateTimeField, ForeignKey or ManyToManyField. This isn't allowed.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
class ValidationTestModelAdmin(ModelAdmin):
prepopulated_fields = {"slug": ("name",)}
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_list_display_validation(self):
@@ -882,8 +855,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.list_display' must be a list or tuple.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -893,8 +865,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
str_prefix("ValidationTestModelAdmin.list_display\[0\], %(_)s'non_existent_field' is not a callable or an attribute of 'ValidationTestModelAdmin' or found in the model 'ValidationTestModel'."),
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -904,8 +875,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.list_display\[0\]', 'users' is a ManyToManyField which is not supported.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -917,7 +887,7 @@ class ValidationTests(unittest.TestCase):
pass
list_display = ('name', 'decade_published_in', 'a_method', a_callable)
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_list_display_links_validation(self):
@@ -927,8 +897,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.list_display_links' must be a list or tuple.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -938,8 +907,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.list_display_links\[0\]' refers to 'non_existent_field' which is not defined in 'list_display'.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -949,8 +917,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.list_display_links\[0\]' refers to 'name' which is not defined in 'list_display'.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -963,7 +930,7 @@ class ValidationTests(unittest.TestCase):
list_display = ('name', 'decade_published_in', 'a_method', a_callable)
list_display_links = ('name', 'decade_published_in', 'a_method', a_callable)
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_list_filter_validation(self):
@@ -973,8 +940,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.list_filter' must be a list or tuple.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -984,8 +950,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.list_filter\[0\]' refers to 'non_existent_field' which does not refer to a Field.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -998,8 +963,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.list_filter\[0\]' is 'RandomClass' which is not a descendant of ListFilter.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -1009,8 +973,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.list_filter\[0\]\[1\]' is 'RandomClass' which is not of type FieldListFilter.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -1028,8 +991,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.list_filter\[0\]\[1\]' is 'AwesomeFilter' which is not of type FieldListFilter.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -1039,8 +1001,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.list_filter\[0\]' is 'BooleanFieldListFilter' which is of type FieldListFilter but is not associated with a field name.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -1049,7 +1010,7 @@ class ValidationTests(unittest.TestCase):
class ValidationTestModelAdmin(ModelAdmin):
list_filter = ('is_active', AwesomeFilter, ('is_active', BooleanFieldListFilter), 'no')
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_list_per_page_validation(self):
@@ -1058,16 +1019,15 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
- "'ValidationTestModelAdmin.list_per_page' should be a integer.",
- validate,
- ValidationTestModelAdmin,
+ "'ValidationTestModelAdmin.list_per_page' should be a int.",
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
class ValidationTestModelAdmin(ModelAdmin):
list_per_page = 100
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_max_show_all_allowed_validation(self):
@@ -1076,16 +1036,15 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
- "'ValidationTestModelAdmin.list_max_show_all' should be an integer.",
- validate,
- ValidationTestModelAdmin,
+ "'ValidationTestModelAdmin.list_max_show_all' should be a int.",
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
class ValidationTestModelAdmin(ModelAdmin):
list_max_show_all = 200
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_search_fields_validation(self):
@@ -1095,8 +1054,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.search_fields' must be a list or tuple.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -1108,8 +1066,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.date_hierarchy' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -1119,15 +1076,14 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.date_hierarchy is neither an instance of DateField nor DateTimeField.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
class ValidationTestModelAdmin(ModelAdmin):
date_hierarchy = 'pub_date'
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_ordering_validation(self):
@@ -1137,8 +1093,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.ordering' must be a list or tuple.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -1148,8 +1103,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.ordering\[0\]' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -1159,25 +1113,24 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.ordering' has the random ordering marker '\?', but contains other fields as well. Please either remove '\?' or the other fields.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
class ValidationTestModelAdmin(ModelAdmin):
ordering = ('?',)
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
class ValidationTestModelAdmin(ModelAdmin):
ordering = ('band__name',)
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
class ValidationTestModelAdmin(ModelAdmin):
ordering = ('name',)
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_list_select_related_validation(self):
@@ -1186,16 +1139,15 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
- "'ValidationTestModelAdmin.list_select_related' should be a boolean.",
- validate,
- ValidationTestModelAdmin,
+ "'ValidationTestModelAdmin.list_select_related' should be a bool.",
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
class ValidationTestModelAdmin(ModelAdmin):
list_select_related = False
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_save_as_validation(self):
@@ -1204,16 +1156,15 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
- "'ValidationTestModelAdmin.save_as' should be a boolean.",
- validate,
- ValidationTestModelAdmin,
+ "'ValidationTestModelAdmin.save_as' should be a bool.",
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
class ValidationTestModelAdmin(ModelAdmin):
save_as = True
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_save_on_top_validation(self):
@@ -1222,16 +1173,15 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
- "'ValidationTestModelAdmin.save_on_top' should be a boolean.",
- validate,
- ValidationTestModelAdmin,
+ "'ValidationTestModelAdmin.save_on_top' should be a bool.",
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
class ValidationTestModelAdmin(ModelAdmin):
save_on_top = True
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_inlines_validation(self):
@@ -1241,8 +1191,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.inlines' must be a list or tuple.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -1255,8 +1204,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.inlines\[0\]' does not inherit from BaseModelAdmin.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -1269,8 +1217,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'model' is a required attribute of 'ValidationTestModelAdmin.inlines\[0\]'.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -1286,8 +1233,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.inlines\[0\].model' does not inherit from models.Model.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -1297,7 +1243,7 @@ class ValidationTests(unittest.TestCase):
class ValidationTestModelAdmin(ModelAdmin):
inlines = [ValidationTestInline]
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_fields_validation(self):
@@ -1311,8 +1257,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestInline.fields' must be a list or tuple.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -1328,8 +1273,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestInline.fk_name' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestInlineModel'.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -1340,7 +1284,7 @@ class ValidationTests(unittest.TestCase):
class ValidationTestModelAdmin(ModelAdmin):
inlines = [ValidationTestInline]
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_extra_validation(self):
@@ -1353,9 +1297,8 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
- "'ValidationTestInline.extra' should be a integer.",
- validate,
- ValidationTestModelAdmin,
+ "'ValidationTestInline.extra' should be a int.",
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -1366,7 +1309,7 @@ class ValidationTests(unittest.TestCase):
class ValidationTestModelAdmin(ModelAdmin):
inlines = [ValidationTestInline]
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_max_num_validation(self):
@@ -1379,9 +1322,8 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
- "'ValidationTestInline.max_num' should be an integer or None \(default\).",
- validate,
- ValidationTestModelAdmin,
+ "'ValidationTestInline.max_num' should be a int.",
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -1392,7 +1334,7 @@ class ValidationTests(unittest.TestCase):
class ValidationTestModelAdmin(ModelAdmin):
inlines = [ValidationTestInline]
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_formset_validation(self):
@@ -1409,8 +1351,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestInline.formset' does not inherit from BaseModelFormSet.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -1424,4 +1365,4 @@ class ValidationTests(unittest.TestCase):
class ValidationTestModelAdmin(ModelAdmin):
inlines = [ValidationTestInline]
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
diff --git a/tests/requests/tests.py b/tests/requests/tests.py
index 56d58c4c75..676cd05679 100644
--- a/tests/requests/tests.py
+++ b/tests/requests/tests.py
@@ -532,13 +532,13 @@ class RequestsTests(SimpleTestCase):
# There are cases in which the multipart data is related instead of
# being a binary upload, in which case it should still be accessible
# via body.
- payload_data = "\r\n".join([
- '--boundary',
- 'Content-ID: id; name="name"',
- '',
- 'value',
- '--boundary--'
- ''])
+ payload_data = b"\r\n".join([
+ b'--boundary',
+ b'Content-ID: id; name="name"',
+ b'',
+ b'value',
+ b'--boundary--'
+ b''])
payload = FakePayload(payload_data)
request = WSGIRequest({'REQUEST_METHOD': 'POST',
'CONTENT_TYPE': 'multipart/related; boundary=boundary',
diff --git a/tests/special_headers/fixtures/data.xml b/tests/special_headers/fixtures/data.xml
deleted file mode 100644
index 7e60d45199..0000000000
--- a/tests/special_headers/fixtures/data.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<django-objects version="1.0">
- <object pk="100" model="auth.user">
- <field type="CharField" name="username">super</field>
- <field type="CharField" name="first_name">Super</field>
- <field type="CharField" name="last_name">User</field>
- <field type="CharField" name="email">super@example.com</field>
- <field type="CharField" name="password">sha1$995a3$6011485ea3834267d719b4c801409b8b1ddd0158</field>
- <field type="BooleanField" name="is_staff">True</field>
- <field type="BooleanField" name="is_active">True</field>
- <field type="BooleanField" name="is_superuser">True</field>
- <field type="DateTimeField" name="last_login">2007-05-30 13:20:10</field>
- <field type="DateTimeField" name="date_joined">2007-05-30 13:20:10</field>
- <field to="auth.group" name="groups" rel="ManyToManyRel"></field>
- <field to="auth.permission" name="user_permissions" rel="ManyToManyRel"></field>
- </object>
- <object pk="1" model="special_headers.article">
- <field type="TextField" name="text">text</field>
- </object>
-</django-objects>
diff --git a/tests/special_headers/models.py b/tests/special_headers/models.py
deleted file mode 100644
index e05c5a6920..0000000000
--- a/tests/special_headers/models.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from django.db import models
-
-
-class Article(models.Model):
- text = models.TextField()
diff --git a/tests/special_headers/templates/special_headers/article_detail.html b/tests/special_headers/templates/special_headers/article_detail.html
deleted file mode 100644
index 3cbd38cb7e..0000000000
--- a/tests/special_headers/templates/special_headers/article_detail.html
+++ /dev/null
@@ -1 +0,0 @@
-{{ object }}
diff --git a/tests/special_headers/tests.py b/tests/special_headers/tests.py
deleted file mode 100644
index b4b704ae21..0000000000
--- a/tests/special_headers/tests.py
+++ /dev/null
@@ -1,62 +0,0 @@
-from django.contrib.auth.models import User
-from django.test import TestCase
-from django.test.utils import override_settings
-
-
-@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
-class SpecialHeadersTest(TestCase):
- fixtures = ['data.xml']
- urls = 'special_headers.urls'
-
- def test_xheaders(self):
- user = User.objects.get(username='super')
- response = self.client.get('/special_headers/article/1/')
- self.assertFalse('X-Object-Type' in response)
- self.client.login(username='super', password='secret')
- response = self.client.get('/special_headers/article/1/')
- self.assertTrue('X-Object-Type' in response)
- user.is_staff = False
- user.save()
- response = self.client.get('/special_headers/article/1/')
- self.assertFalse('X-Object-Type' in response)
- user.is_staff = True
- user.is_active = False
- user.save()
- response = self.client.get('/special_headers/article/1/')
- self.assertFalse('X-Object-Type' in response)
-
- def test_xview_func(self):
- user = User.objects.get(username='super')
- response = self.client.head('/special_headers/xview/func/')
- self.assertFalse('X-View' in response)
- self.client.login(username='super', password='secret')
- response = self.client.head('/special_headers/xview/func/')
- self.assertTrue('X-View' in response)
- self.assertEqual(response['X-View'], 'special_headers.views.xview')
- user.is_staff = False
- user.save()
- response = self.client.head('/special_headers/xview/func/')
- self.assertFalse('X-View' in response)
- user.is_staff = True
- user.is_active = False
- user.save()
- response = self.client.head('/special_headers/xview/func/')
- self.assertFalse('X-View' in response)
-
- def test_xview_class(self):
- user = User.objects.get(username='super')
- response = self.client.head('/special_headers/xview/class/')
- self.assertFalse('X-View' in response)
- self.client.login(username='super', password='secret')
- response = self.client.head('/special_headers/xview/class/')
- self.assertTrue('X-View' in response)
- self.assertEqual(response['X-View'], 'special_headers.views.XViewClass')
- user.is_staff = False
- user.save()
- response = self.client.head('/special_headers/xview/class/')
- self.assertFalse('X-View' in response)
- user.is_staff = True
- user.is_active = False
- user.save()
- response = self.client.head('/special_headers/xview/class/')
- self.assertFalse('X-View' in response)
diff --git a/tests/special_headers/urls.py b/tests/special_headers/urls.py
deleted file mode 100644
index f7ba141207..0000000000
--- a/tests/special_headers/urls.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# coding: utf-8
-from __future__ import absolute_import
-
-from django.conf.urls import patterns
-
-from . import views
-from .models import Article
-
-urlpatterns = patterns('',
- (r'^special_headers/article/(?P<object_id>\d+)/$', views.xview_xheaders),
- (r'^special_headers/xview/func/$', views.xview_dec(views.xview)),
- (r'^special_headers/xview/class/$', views.xview_dec(views.XViewClass.as_view())),
-)
diff --git a/tests/special_headers/views.py b/tests/special_headers/views.py
deleted file mode 100644
index a8bbd6542e..0000000000
--- a/tests/special_headers/views.py
+++ /dev/null
@@ -1,21 +0,0 @@
-from django.core.xheaders import populate_xheaders
-from django.http import HttpResponse
-from django.utils.decorators import decorator_from_middleware
-from django.views.generic import View
-from django.middleware.doc import XViewMiddleware
-
-from .models import Article
-
-xview_dec = decorator_from_middleware(XViewMiddleware)
-
-def xview(request):
- return HttpResponse()
-
-def xview_xheaders(request, object_id):
- response = HttpResponse()
- populate_xheaders(request, response, Article, 1)
- return response
-
-class XViewClass(View):
- def get(self, request):
- return HttpResponse()
diff --git a/tests/template_tests/filters.py b/tests/template_tests/filters.py
index 7ba1681fd5..68ef15d827 100644
--- a/tests/template_tests/filters.py
+++ b/tests/template_tests/filters.py
@@ -35,59 +35,60 @@ def get_filter_tests():
now_tz_i = datetime.now(FixedOffset((3 * 60) + 15)) # imaginary time zone
today = date.today()
+ # NOTE: \xa0 avoids wrapping between value and unit
return {
# Default compare with datetime.now()
- 'filter-timesince01' : ('{{ a|timesince }}', {'a': datetime.now() + timedelta(minutes=-1, seconds = -10)}, '1 minute'),
- 'filter-timesince02' : ('{{ a|timesince }}', {'a': datetime.now() - timedelta(days=1, minutes = 1)}, '1 day'),
- 'filter-timesince03' : ('{{ a|timesince }}', {'a': datetime.now() - timedelta(hours=1, minutes=25, seconds = 10)}, '1 hour, 25 minutes'),
+ 'filter-timesince01' : ('{{ a|timesince }}', {'a': datetime.now() + timedelta(minutes=-1, seconds = -10)}, '1\xa0minute'),
+ 'filter-timesince02' : ('{{ a|timesince }}', {'a': datetime.now() - timedelta(days=1, minutes = 1)}, '1\xa0day'),
+ 'filter-timesince03' : ('{{ a|timesince }}', {'a': datetime.now() - timedelta(hours=1, minutes=25, seconds = 10)}, '1\xa0hour, 25\xa0minutes'),
# Compare to a given parameter
- 'filter-timesince04' : ('{{ a|timesince:b }}', {'a':now - timedelta(days=2), 'b':now - timedelta(days=1)}, '1 day'),
- 'filter-timesince05' : ('{{ a|timesince:b }}', {'a':now - timedelta(days=2, minutes=1), 'b':now - timedelta(days=2)}, '1 minute'),
+ 'filter-timesince04' : ('{{ a|timesince:b }}', {'a':now - timedelta(days=2), 'b':now - timedelta(days=1)}, '1\xa0day'),
+ 'filter-timesince05' : ('{{ a|timesince:b }}', {'a':now - timedelta(days=2, minutes=1), 'b':now - timedelta(days=2)}, '1\xa0minute'),
# Check that timezone is respected
- 'filter-timesince06' : ('{{ a|timesince:b }}', {'a':now_tz - timedelta(hours=8), 'b':now_tz}, '8 hours'),
+ 'filter-timesince06' : ('{{ a|timesince:b }}', {'a':now_tz - timedelta(hours=8), 'b':now_tz}, '8\xa0hours'),
# Regression for #7443
- 'filter-timesince07': ('{{ earlier|timesince }}', { 'earlier': now - timedelta(days=7) }, '1 week'),
- 'filter-timesince08': ('{{ earlier|timesince:now }}', { 'now': now, 'earlier': now - timedelta(days=7) }, '1 week'),
- 'filter-timesince09': ('{{ later|timesince }}', { 'later': now + timedelta(days=7) }, '0 minutes'),
- 'filter-timesince10': ('{{ later|timesince:now }}', { 'now': now, 'later': now + timedelta(days=7) }, '0 minutes'),
+ 'filter-timesince07': ('{{ earlier|timesince }}', { 'earlier': now - timedelta(days=7) }, '1\xa0week'),
+ 'filter-timesince08': ('{{ earlier|timesince:now }}', { 'now': now, 'earlier': now - timedelta(days=7) }, '1\xa0week'),
+ 'filter-timesince09': ('{{ later|timesince }}', { 'later': now + timedelta(days=7) }, '0\xa0minutes'),
+ 'filter-timesince10': ('{{ later|timesince:now }}', { 'now': now, 'later': now + timedelta(days=7) }, '0\xa0minutes'),
# Ensures that differing timezones are calculated correctly
- 'filter-timesince11' : ('{{ a|timesince }}', {'a': now}, '0 minutes'),
- 'filter-timesince12' : ('{{ a|timesince }}', {'a': now_tz}, '0 minutes'),
- 'filter-timesince13' : ('{{ a|timesince }}', {'a': now_tz_i}, '0 minutes'),
- 'filter-timesince14' : ('{{ a|timesince:b }}', {'a': now_tz, 'b': now_tz_i}, '0 minutes'),
+ 'filter-timesince11' : ('{{ a|timesince }}', {'a': now}, '0\xa0minutes'),
+ 'filter-timesince12' : ('{{ a|timesince }}', {'a': now_tz}, '0\xa0minutes'),
+ 'filter-timesince13' : ('{{ a|timesince }}', {'a': now_tz_i}, '0\xa0minutes'),
+ 'filter-timesince14' : ('{{ a|timesince:b }}', {'a': now_tz, 'b': now_tz_i}, '0\xa0minutes'),
'filter-timesince15' : ('{{ a|timesince:b }}', {'a': now, 'b': now_tz_i}, ''),
'filter-timesince16' : ('{{ a|timesince:b }}', {'a': now_tz_i, 'b': now}, ''),
# Regression for #9065 (two date objects).
- 'filter-timesince17' : ('{{ a|timesince:b }}', {'a': today, 'b': today}, '0 minutes'),
- 'filter-timesince18' : ('{{ a|timesince:b }}', {'a': today, 'b': today + timedelta(hours=24)}, '1 day'),
+ 'filter-timesince17' : ('{{ a|timesince:b }}', {'a': today, 'b': today}, '0\xa0minutes'),
+ 'filter-timesince18' : ('{{ a|timesince:b }}', {'a': today, 'b': today + timedelta(hours=24)}, '1\xa0day'),
# Default compare with datetime.now()
- 'filter-timeuntil01' : ('{{ a|timeuntil }}', {'a':datetime.now() + timedelta(minutes=2, seconds = 10)}, '2 minutes'),
- 'filter-timeuntil02' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(days=1, seconds = 10))}, '1 day'),
- 'filter-timeuntil03' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(hours=8, minutes=10, seconds = 10))}, '8 hours, 10 minutes'),
+ 'filter-timeuntil01' : ('{{ a|timeuntil }}', {'a':datetime.now() + timedelta(minutes=2, seconds = 10)}, '2\xa0minutes'),
+ 'filter-timeuntil02' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(days=1, seconds = 10))}, '1\xa0day'),
+ 'filter-timeuntil03' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(hours=8, minutes=10, seconds = 10))}, '8\xa0hours, 10\xa0minutes'),
# Compare to a given parameter
- 'filter-timeuntil04' : ('{{ a|timeuntil:b }}', {'a':now - timedelta(days=1), 'b':now - timedelta(days=2)}, '1 day'),
- 'filter-timeuntil05' : ('{{ a|timeuntil:b }}', {'a':now - timedelta(days=2), 'b':now - timedelta(days=2, minutes=1)}, '1 minute'),
+ 'filter-timeuntil04' : ('{{ a|timeuntil:b }}', {'a':now - timedelta(days=1), 'b':now - timedelta(days=2)}, '1\xa0day'),
+ 'filter-timeuntil05' : ('{{ a|timeuntil:b }}', {'a':now - timedelta(days=2), 'b':now - timedelta(days=2, minutes=1)}, '1\xa0minute'),
# Regression for #7443
- 'filter-timeuntil06': ('{{ earlier|timeuntil }}', { 'earlier': now - timedelta(days=7) }, '0 minutes'),
- 'filter-timeuntil07': ('{{ earlier|timeuntil:now }}', { 'now': now, 'earlier': now - timedelta(days=7) }, '0 minutes'),
- 'filter-timeuntil08': ('{{ later|timeuntil }}', { 'later': now + timedelta(days=7, hours=1) }, '1 week'),
- 'filter-timeuntil09': ('{{ later|timeuntil:now }}', { 'now': now, 'later': now + timedelta(days=7) }, '1 week'),
+ 'filter-timeuntil06': ('{{ earlier|timeuntil }}', { 'earlier': now - timedelta(days=7) }, '0\xa0minutes'),
+ 'filter-timeuntil07': ('{{ earlier|timeuntil:now }}', { 'now': now, 'earlier': now - timedelta(days=7) }, '0\xa0minutes'),
+ 'filter-timeuntil08': ('{{ later|timeuntil }}', { 'later': now + timedelta(days=7, hours=1) }, '1\xa0week'),
+ 'filter-timeuntil09': ('{{ later|timeuntil:now }}', { 'now': now, 'later': now + timedelta(days=7) }, '1\xa0week'),
# Ensures that differing timezones are calculated correctly
- 'filter-timeuntil10' : ('{{ a|timeuntil }}', {'a': now_tz_i}, '0 minutes'),
- 'filter-timeuntil11' : ('{{ a|timeuntil:b }}', {'a': now_tz_i, 'b': now_tz}, '0 minutes'),
+ 'filter-timeuntil10' : ('{{ a|timeuntil }}', {'a': now_tz_i}, '0\xa0minutes'),
+ 'filter-timeuntil11' : ('{{ a|timeuntil:b }}', {'a': now_tz_i, 'b': now_tz}, '0\xa0minutes'),
# Regression for #9065 (two date objects).
- 'filter-timeuntil12' : ('{{ a|timeuntil:b }}', {'a': today, 'b': today}, '0 minutes'),
- 'filter-timeuntil13' : ('{{ a|timeuntil:b }}', {'a': today, 'b': today - timedelta(hours=24)}, '1 day'),
+ 'filter-timeuntil12' : ('{{ a|timeuntil:b }}', {'a': today, 'b': today}, '0\xa0minutes'),
+ 'filter-timeuntil13' : ('{{ a|timeuntil:b }}', {'a': today, 'b': today - timedelta(hours=24)}, '1\xa0day'),
'filter-addslash01': ("{% autoescape off %}{{ a|addslashes }} {{ b|addslashes }}{% endautoescape %}", {"a": "<a>'", "b": mark_safe("<a>'")}, r"<a>\' <a>\'"),
'filter-addslash02': ("{{ a|addslashes }} {{ b|addslashes }}", {"a": "<a>'", "b": mark_safe("<a>'")}, r"&lt;a&gt;\&#39; <a>\'"),
diff --git a/tests/transactions/tests.py b/tests/transactions/tests.py
index aeb9bc3d2c..0f16a9c805 100644
--- a/tests/transactions/tests.py
+++ b/tests/transactions/tests.py
@@ -5,6 +5,7 @@ import warnings
from django.db import connection, transaction, IntegrityError
from django.test import TransactionTestCase, skipUnlessDBFeature
+from django.test.utils import IgnorePendingDeprecationWarningsMixin
from django.utils import six
from django.utils.unittest import skipIf, skipUnless
@@ -319,19 +320,6 @@ class AtomicMiscTests(TransactionTestCase):
transaction.atomic(Callable())
-class IgnorePendingDeprecationWarningsMixin(object):
-
- def setUp(self):
- super(IgnorePendingDeprecationWarningsMixin, self).setUp()
- self.catch_warnings = warnings.catch_warnings()
- self.catch_warnings.__enter__()
- warnings.filterwarnings("ignore", category=PendingDeprecationWarning)
-
- def tearDown(self):
- self.catch_warnings.__exit__(*sys.exc_info())
- super(IgnorePendingDeprecationWarningsMixin, self).tearDown()
-
-
class TransactionTests(IgnorePendingDeprecationWarningsMixin, TransactionTestCase):
def create_a_reporter_then_fail(self, first, last):
diff --git a/tests/transactions_regress/tests.py b/tests/transactions_regress/tests.py
index fb3f257dab..5339b4a8ea 100644
--- a/tests/transactions_regress/tests.py
+++ b/tests/transactions_regress/tests.py
@@ -4,11 +4,9 @@ from django.db import (connection, connections, transaction, DEFAULT_DB_ALIAS, D
IntegrityError)
from django.db.transaction import commit_on_success, commit_manually, TransactionManagementError
from django.test import TransactionTestCase, skipUnlessDBFeature
-from django.test.utils import override_settings
+from django.test.utils import override_settings, IgnorePendingDeprecationWarningsMixin
from django.utils.unittest import skipIf, skipUnless
-from transactions.tests import IgnorePendingDeprecationWarningsMixin
-
from .models import Mod, M2mA, M2mB, SubMod
class ModelInheritanceTests(TransactionTestCase):
diff --git a/tests/utils_tests/test_timesince.py b/tests/utils_tests/test_timesince.py
index 5e641a42c4..cdb95e6877 100644
--- a/tests/utils_tests/test_timesince.py
+++ b/tests/utils_tests/test_timesince.py
@@ -21,32 +21,33 @@ class TimesinceTests(unittest.TestCase):
def test_equal_datetimes(self):
""" equal datetimes. """
- self.assertEqual(timesince(self.t, self.t), '0 minutes')
+ # NOTE: \xa0 avoids wrapping between value and unit
+ self.assertEqual(timesince(self.t, self.t), '0\xa0minutes')
def test_ignore_microseconds_and_seconds(self):
""" Microseconds and seconds are ignored. """
self.assertEqual(timesince(self.t, self.t+self.onemicrosecond),
- '0 minutes')
+ '0\xa0minutes')
self.assertEqual(timesince(self.t, self.t+self.onesecond),
- '0 minutes')
+ '0\xa0minutes')
def test_other_units(self):
""" Test other units. """
self.assertEqual(timesince(self.t, self.t+self.oneminute),
- '1 minute')
- self.assertEqual(timesince(self.t, self.t+self.onehour), '1 hour')
- self.assertEqual(timesince(self.t, self.t+self.oneday), '1 day')
- self.assertEqual(timesince(self.t, self.t+self.oneweek), '1 week')
+ '1\xa0minute')
+ self.assertEqual(timesince(self.t, self.t+self.onehour), '1\xa0hour')
+ self.assertEqual(timesince(self.t, self.t+self.oneday), '1\xa0day')
+ self.assertEqual(timesince(self.t, self.t+self.oneweek), '1\xa0week')
self.assertEqual(timesince(self.t, self.t+self.onemonth),
- '1 month')
- self.assertEqual(timesince(self.t, self.t+self.oneyear), '1 year')
+ '1\xa0month')
+ self.assertEqual(timesince(self.t, self.t+self.oneyear), '1\xa0year')
def test_multiple_units(self):
""" Test multiple units. """
self.assertEqual(timesince(self.t,
- self.t+2*self.oneday+6*self.onehour), '2 days, 6 hours')
+ self.t+2*self.oneday+6*self.onehour), '2\xa0days, 6\xa0hours')
self.assertEqual(timesince(self.t,
- self.t+2*self.oneweek+2*self.oneday), '2 weeks, 2 days')
+ self.t+2*self.oneweek+2*self.oneday), '2\xa0weeks, 2\xa0days')
def test_display_first_unit(self):
"""
@@ -55,10 +56,10 @@ class TimesinceTests(unittest.TestCase):
"""
self.assertEqual(timesince(self.t,
self.t+2*self.oneweek+3*self.onehour+4*self.oneminute),
- '2 weeks')
+ '2\xa0weeks')
self.assertEqual(timesince(self.t,
- self.t+4*self.oneday+5*self.oneminute), '4 days')
+ self.t+4*self.oneday+5*self.oneminute), '4\xa0days')
def test_display_second_before_first(self):
"""
@@ -66,30 +67,30 @@ class TimesinceTests(unittest.TestCase):
get 0 minutes.
"""
self.assertEqual(timesince(self.t, self.t-self.onemicrosecond),
- '0 minutes')
+ '0\xa0minutes')
self.assertEqual(timesince(self.t, self.t-self.onesecond),
- '0 minutes')
+ '0\xa0minutes')
self.assertEqual(timesince(self.t, self.t-self.oneminute),
- '0 minutes')
+ '0\xa0minutes')
self.assertEqual(timesince(self.t, self.t-self.onehour),
- '0 minutes')
+ '0\xa0minutes')
self.assertEqual(timesince(self.t, self.t-self.oneday),
- '0 minutes')
+ '0\xa0minutes')
self.assertEqual(timesince(self.t, self.t-self.oneweek),
- '0 minutes')
+ '0\xa0minutes')
self.assertEqual(timesince(self.t, self.t-self.onemonth),
- '0 minutes')
+ '0\xa0minutes')
self.assertEqual(timesince(self.t, self.t-self.oneyear),
- '0 minutes')
+ '0\xa0minutes')
self.assertEqual(timesince(self.t,
- self.t-2*self.oneday-6*self.onehour), '0 minutes')
+ self.t-2*self.oneday-6*self.onehour), '0\xa0minutes')
self.assertEqual(timesince(self.t,
- self.t-2*self.oneweek-2*self.oneday), '0 minutes')
+ self.t-2*self.oneweek-2*self.oneday), '0\xa0minutes')
self.assertEqual(timesince(self.t,
self.t-2*self.oneweek-3*self.onehour-4*self.oneminute),
- '0 minutes')
+ '0\xa0minutes')
self.assertEqual(timesince(self.t,
- self.t-4*self.oneday-5*self.oneminute), '0 minutes')
+ self.t-4*self.oneday-5*self.oneminute), '0\xa0minutes')
def test_different_timezones(self):
""" When using two different timezones. """
@@ -97,28 +98,28 @@ class TimesinceTests(unittest.TestCase):
now_tz = datetime.datetime.now(LocalTimezone(now))
now_tz_i = datetime.datetime.now(FixedOffset((3 * 60) + 15))
- self.assertEqual(timesince(now), '0 minutes')
- self.assertEqual(timesince(now_tz), '0 minutes')
- self.assertEqual(timeuntil(now_tz, now_tz_i), '0 minutes')
+ self.assertEqual(timesince(now), '0\xa0minutes')
+ self.assertEqual(timesince(now_tz), '0\xa0minutes')
+ self.assertEqual(timeuntil(now_tz, now_tz_i), '0\xa0minutes')
def test_date_objects(self):
""" Both timesince and timeuntil should work on date objects (#17937). """
today = datetime.date.today()
- self.assertEqual(timesince(today + self.oneday), '0 minutes')
- self.assertEqual(timeuntil(today - self.oneday), '0 minutes')
+ self.assertEqual(timesince(today + self.oneday), '0\xa0minutes')
+ self.assertEqual(timeuntil(today - self.oneday), '0\xa0minutes')
def test_both_date_objects(self):
""" Timesince should work with both date objects (#9672) """
today = datetime.date.today()
- self.assertEqual(timeuntil(today + self.oneday, today), '1 day')
- self.assertEqual(timeuntil(today - self.oneday, today), '0 minutes')
- self.assertEqual(timeuntil(today + self.oneweek, today), '1 week')
+ self.assertEqual(timeuntil(today + self.oneday, today), '1\xa0day')
+ self.assertEqual(timeuntil(today - self.oneday, today), '0\xa0minutes')
+ self.assertEqual(timeuntil(today + self.oneweek, today), '1\xa0week')
def test_naive_datetime_with_tzinfo_attribute(self):
class naive(datetime.tzinfo):
def utcoffset(self, dt):
return None
future = datetime.datetime(2080, 1, 1, tzinfo=naive())
- self.assertEqual(timesince(future), '0 minutes')
+ self.assertEqual(timesince(future), '0\xa0minutes')
past = datetime.datetime(1980, 1, 1, tzinfo=naive())
- self.assertEqual(timeuntil(past), '0 minutes')
+ self.assertEqual(timeuntil(past), '0\xa0minutes')
diff --git a/tests/view_tests/tests/test_debug.py b/tests/view_tests/tests/test_debug.py
index b44cd88abe..a84b41959c 100644
--- a/tests/view_tests/tests/test_debug.py
+++ b/tests/view_tests/tests/test_debug.py
@@ -6,6 +6,7 @@ from __future__ import absolute_import, unicode_literals
import inspect
import os
import sys
+import tempfile
from django.core import mail
from django.core.files.uploadedfile import SimpleUploadedFile
@@ -13,7 +14,7 @@ from django.core.urlresolvers import reverse
from django.test import TestCase, RequestFactory
from django.test.utils import (override_settings, setup_test_template_loader,
restore_template_loaders)
-from django.utils.encoding import force_text
+from django.utils.encoding import force_text, force_bytes
from django.views.debug import ExceptionReporter
from .. import BrokenException, except_args
@@ -122,6 +123,24 @@ class ExceptionReporterTests(TestCase):
self.assertIn('<h2>Request information</h2>', html)
self.assertIn('<p>Request data not supplied</p>', html)
+ def test_eol_support(self):
+ """Test that the ExceptionReporter supports Unix, Windows and Macintosh EOL markers"""
+ LINES = list('print %d' % i for i in range(1, 6))
+ reporter = ExceptionReporter(None, None, None, None)
+
+ for newline in ['\n','\r\n','\r']:
+ fd,filename = tempfile.mkstemp(text = False)
+ os.write(fd, force_bytes(newline.join(LINES)+newline))
+ os.close(fd)
+
+ try:
+ self.assertEqual(
+ reporter._get_lines_from_file(filename, 3, 2),
+ (1, LINES[1:3], LINES[3], LINES[4:])
+ )
+ finally:
+ os.unlink(filename)
+
def test_no_exception(self):
"An exception report can be generated for just a request"
request = self.rf.get('/test_view/')