diff --git a/django/conf/__init__.py b/django/conf/__init__.py
index 37982e76ad..ac074d5166 100644
--- a/django/conf/__init__.py
+++ b/django/conf/__init__.py
@@ -34,11 +34,8 @@ class LazySettings(LazyObject):
is used the first time we need any settings at all, if the user has not
previously configured the settings manually.
"""
- try:
- settings_module = os.environ[ENVIRONMENT_VARIABLE]
- if not settings_module: # If it's set but is an empty string.
- raise KeyError
- except KeyError:
+ settings_module = os.environ.get(ENVIRONMENT_VARIABLE)
+ if not settings_module:
desc = ("setting %s" % name) if name else "settings"
raise ImproperlyConfigured(
"Requested %s, but settings are not configured. "
diff --git a/django/contrib/admin/filters.py b/django/contrib/admin/filters.py
index 1f8cb5ed61..9e539614a3 100644
--- a/django/contrib/admin/filters.py
+++ b/django/contrib/admin/filters.py
@@ -179,9 +179,9 @@ class RelatedFieldListFilter(FieldListFilter):
self.title = self.lookup_title
def has_output(self):
- if (isinstance(self.field, models.related.RelatedObject)
- and self.field.field.null or hasattr(self.field, 'rel')
- and self.field.null):
+ if (isinstance(self.field, models.related.RelatedObject) and
+ self.field.field.null or hasattr(self.field, 'rel') and
+ self.field.null):
extra = 1
else:
extra = 0
@@ -206,9 +206,9 @@ class RelatedFieldListFilter(FieldListFilter):
}, [self.lookup_kwarg_isnull]),
'display': val,
}
- if (isinstance(self.field, models.related.RelatedObject)
- and self.field.field.null or hasattr(self.field, 'rel')
- and self.field.null):
+ if (isinstance(self.field, models.related.RelatedObject) and
+ self.field.field.null or hasattr(self.field, 'rel') and
+ self.field.null):
yield {
'selected': bool(self.lookup_val_isnull),
'query_string': cl.get_query_string({
diff --git a/django/contrib/admin/helpers.py b/django/contrib/admin/helpers.py
index 012a14ef64..86ab0f263e 100644
--- a/django/contrib/admin/helpers.py
+++ b/django/contrib/admin/helpers.py
@@ -30,7 +30,7 @@ checkbox = forms.CheckboxInput({'class': 'action-select'}, lambda value: False)
class AdminForm(object):
def __init__(self, form, fieldsets, prepopulated_fields, readonly_fields=None, model_admin=None):
- self.form, self.fieldsets = form, normalize_fieldsets(fieldsets)
+ self.form, self.fieldsets = form, fieldsets
self.prepopulated_fields = [{
'field': form[field_name],
'dependencies': [form[f] for f in dependencies]
@@ -42,7 +42,8 @@ class AdminForm(object):
def __iter__(self):
for name, options in self.fieldsets:
- yield Fieldset(self.form, name,
+ yield Fieldset(
+ self.form, name,
readonly_fields=self.readonly_fields,
model_admin=self.model_admin,
**options
@@ -328,32 +329,11 @@ class AdminErrorList(forms.utils.ErrorList):
Stores all errors for the form/formsets in an add/change stage view.
"""
def __init__(self, form, inline_formsets):
+ super(AdminErrorList, self).__init__()
+
if form.is_bound:
self.extend(list(six.itervalues(form.errors)))
for inline_formset in inline_formsets:
self.extend(inline_formset.non_form_errors())
for errors_in_inline_form in inline_formset.errors:
self.extend(list(six.itervalues(errors_in_inline_form)))
-
-
-def normalize_fieldsets(fieldsets):
- """
- Make sure the keys in fieldset dictionaries are strings. Returns the
- normalized data.
- """
- result = []
- for name, options in fieldsets:
- result.append((name, normalize_dictionary(options)))
- return result
-
-
-def normalize_dictionary(data_dict):
- """
- Converts all the keys in "data_dict" to strings. The keys must be
- convertible using str().
- """
- for key, value in data_dict.items():
- if not isinstance(key, str):
- del data_dict[key]
- data_dict[str(key)] = value
- return data_dict
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index 2f39c6a896..4c5adaf0f8 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -613,7 +613,7 @@ class ModelAdmin(BaseModelAdmin):
}
defaults.update(kwargs)
if (defaults.get('fields') is None
- and not modelform_defines_fields(defaults.get('form'))):
+ and not modelform_defines_fields(defaults.get('form'))):
defaults['fields'] = forms.ALL_FIELDS
return modelform_factory(self.model, **defaults)
@@ -1520,7 +1520,8 @@ class ModelAdmin(BaseModelAdmin):
selection_note_all = ungettext('%(total_count)s selected',
'All %(total_count)s selected', cl.result_count)
- context = dict(self.admin_site.each_context(),
+ context = dict(
+ self.admin_site.each_context(),
module_name=force_text(opts.verbose_name_plural),
selection_note=_('0 of %(cnt)s selected') % {'cnt': len(cl.result_list)},
selection_note_all=selection_note_all % {'total_count': cl.result_count},
@@ -1587,7 +1588,8 @@ class ModelAdmin(BaseModelAdmin):
else:
title = _("Are you sure?")
- context = dict(self.admin_site.each_context(),
+ context = dict(
+ self.admin_site.each_context(),
title=title,
object_name=object_name,
object=obj,
diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py
index 2dac947fbc..7b633bef89 100644
--- a/django/contrib/admin/sites.py
+++ b/django/contrib/admin/sites.py
@@ -169,7 +169,7 @@ class AdminSite(object):
raise ImproperlyConfigured("Put 'django.contrib.contenttypes' in "
"your INSTALLED_APPS setting in order to use the admin application.")
if not ('django.contrib.auth.context_processors.auth' in settings.TEMPLATE_CONTEXT_PROCESSORS or
- 'django.core.context_processors.auth' in settings.TEMPLATE_CONTEXT_PROCESSORS):
+ 'django.core.context_processors.auth' in settings.TEMPLATE_CONTEXT_PROCESSORS):
raise ImproperlyConfigured("Put 'django.contrib.auth.context_processors.auth' "
"in your TEMPLATE_CONTEXT_PROCESSORS setting in order to use the admin application.")
@@ -398,7 +398,8 @@ class AdminSite(object):
for app in app_list:
app['models'].sort(key=lambda x: x['name'])
- context = dict(self.each_context(),
+ context = dict(
+ self.each_context(),
title=self.index_title,
app_list=app_list,
)
diff --git a/django/contrib/admin/templates/admin/app_index.html b/django/contrib/admin/templates/admin/app_index.html
index 7aff935126..5eff1248c1 100644
--- a/django/contrib/admin/templates/admin/app_index.html
+++ b/django/contrib/admin/templates/admin/app_index.html
@@ -9,7 +9,7 @@
{% trans 'Home' %}
›
{% for app in app_list %}
-{% blocktrans with app.name as name %}{{ name }}{% endblocktrans %}
+{{ app.name }}
{% endfor %}
{% endblock %}
diff --git a/django/contrib/admin/templates/admin/index.html b/django/contrib/admin/templates/admin/index.html
index 961e4823e0..7b4c1e1221 100644
--- a/django/contrib/admin/templates/admin/index.html
+++ b/django/contrib/admin/templates/admin/index.html
@@ -17,9 +17,7 @@
-
- {% blocktrans with name=app.name %}{{ name }}{% endblocktrans %}
-
+ {{ app.name }}
{% for model in app.models %}
diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py
index 777bfbac81..0181f665ad 100644
--- a/django/contrib/admin/templatetags/admin_list.py
+++ b/django/contrib/admin/templatetags/admin_list.py
@@ -95,7 +95,8 @@ def result_headers(cl):
"""
ordering_field_columns = cl.get_ordering_field_columns()
for i, field_name in enumerate(cl.list_display):
- text, attr = label_for_field(field_name, cl.model,
+ text, attr = label_for_field(
+ field_name, cl.model,
model_admin=cl.model_admin,
return_attr=True
)
diff --git a/django/contrib/admin/templatetags/admin_modify.py b/django/contrib/admin/templatetags/admin_modify.py
index c4ce53fec0..4a6f5c3192 100644
--- a/django/contrib/admin/templatetags/admin_modify.py
+++ b/django/contrib/admin/templatetags/admin_modify.py
@@ -32,8 +32,7 @@ def submit_row(context):
save_as = context['save_as']
ctx = {
'opts': opts,
- 'show_delete_link': (not is_popup and context['has_delete_permission']
- and change and context.get('show_delete', True)),
+ 'show_delete_link': not is_popup and context['has_delete_permission'] and change and context.get('show_delete', True),
'show_save_as_new': not is_popup and change and save_as,
'show_save_and_add_another': context['has_add_permission'] and not is_popup and (not save_as or context['add']),
'show_save_and_continue': not is_popup and context['has_change_permission'],
diff --git a/django/contrib/admin/validation.py b/django/contrib/admin/validation.py
index b2cb0b4181..a15967e024 100644
--- a/django/contrib/admin/validation.py
+++ b/django/contrib/admin/validation.py
@@ -1,3 +1,4 @@
+from django.core.apps import app_cache
from django.core.exceptions import ImproperlyConfigured
from django.db import models
from django.db.models.fields import FieldDoesNotExist
@@ -15,9 +16,9 @@ __all__ = ['BaseValidator', 'InlineValidator']
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()
+ # Before we can introspect models, they need the app cache to be fully
+ # loaded so that inter-relations are set up correctly.
+ app_cache.populate()
def validate(self, cls, model):
for m in dir(self):
@@ -155,7 +156,7 @@ class BaseValidator(object):
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)):
+ models.ManyToManyField)):
raise ImproperlyConfigured("'%s.prepopulated_fields['%s']' "
"is either a DateTimeField, ForeignKey or "
"ManyToManyField. This isn't allowed."
diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py
index 4b0da928a2..dccab55de4 100644
--- a/django/contrib/admin/views/main.py
+++ b/django/contrib/admin/views/main.py
@@ -9,7 +9,7 @@ from django.db import models
from django.db.models.fields import FieldDoesNotExist
from django.utils import six
from django.utils.deprecation import RenameMethodsBase
-from django.utils.encoding import force_str, force_text
+from django.utils.encoding import force_text
from django.utils.translation import ugettext, ugettext_lazy
from django.utils.http import urlencode
@@ -142,14 +142,7 @@ class ChangeList(six.with_metaclass(RenameChangeListMethods)):
lookup_params = self.get_filters_params()
use_distinct = False
- # Normalize the types of keys
for key, value in lookup_params.items():
- if not isinstance(key, str):
- # 'key' will be used as a keyword argument later, so Python
- # requires it to be a string.
- del lookup_params[key]
- lookup_params[force_str(key)] = value
-
if not self.model_admin.lookup_allowed(key, value):
raise DisallowedModelAdminLookup("Filtering by %s not allowed" % key)
@@ -224,7 +217,7 @@ class ChangeList(six.with_metaclass(RenameChangeListMethods)):
# Perform a slight optimization:
# full_result_count is equal to paginator.count if no filters
# were applied
- if self.get_filters_params():
+ if self.get_filters_params() or self.params.get(SEARCH_VAR):
full_result_count = self.root_queryset.count()
else:
full_result_count = result_count
diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py
index a17f875f40..f527d72f3f 100644
--- a/django/contrib/admin/widgets.py
+++ b/django/contrib/admin/widgets.py
@@ -153,10 +153,13 @@ class ForeignKeyRawIdWidget(forms.TextInput):
extra = []
if rel_to in self.admin_site._registry:
# The related object is registered with the same AdminSite
- related_url = reverse('admin:%s_%s_changelist' %
- (rel_to._meta.app_label,
- rel_to._meta.model_name),
- current_app=self.admin_site.name)
+ related_url = reverse(
+ 'admin:%s_%s_changelist' % (
+ rel_to._meta.app_label,
+ rel_to._meta.model_name,
+ ),
+ current_app=self.admin_site.name,
+ )
params = self.url_parameters()
if params:
@@ -167,10 +170,10 @@ class ForeignKeyRawIdWidget(forms.TextInput):
attrs['class'] = 'vForeignKeyRawIdAdminField' # The JavaScript code looks for this hook.
# TODO: "lookup_id_" is hard-coded here. This should instead use
# the correct API to determine the ID dynamically.
- extra.append(' '
- % (related_url, url, name))
- extra.append(''
- % (static('admin/img/selector-search.gif'), _('Lookup')))
+ extra.append(' ' %
+ (related_url, url, name))
+ extra.append('' %
+ (static('admin/img/selector-search.gif'), _('Lookup')))
output = [super(ForeignKeyRawIdWidget, self).render(name, value, attrs)] + extra
if value:
output.append(self.label_for_value(value))
diff --git a/django/contrib/admindocs/models.py b/django/contrib/admindocs/models.py
deleted file mode 100644
index a9f813a4cb..0000000000
--- a/django/contrib/admindocs/models.py
+++ /dev/null
@@ -1 +0,0 @@
-# Empty models.py to allow for specifying admindocs as a test label.
diff --git a/django/contrib/admindocs/templates/admin_doc/template_detail.html b/django/contrib/admindocs/templates/admin_doc/template_detail.html
index 9535724c24..a747af5ac2 100644
--- a/django/contrib/admindocs/templates/admin_doc/template_detail.html
+++ b/django/contrib/admindocs/templates/admin_doc/template_detail.html
@@ -15,15 +15,12 @@
{% block content %}
{% blocktrans %}Template: "{{ name }}"{% endblocktrans %}
-{% regroup templates|dictsort:"site_id" by site as templates_by_site %}
-{% for group in templates_by_site %}
- {% blocktrans with group.grouper as grouper %}Search path for template "{{ name }}" on {{ grouper }}:{% endblocktrans %}
-
- {% for template in group.list|dictsort:"order" %}
- {{ template.file }}
{% if not template.exists %} {% trans '(does not exist)' %}{% endif %}
- {% endfor %}
-
+{% blocktrans %}Search path for template "{{ name }}":{% endblocktrans %}
+
+{% for template in templates|dictsort:"order" %}
+ {{ template.file }}
{% if not template.exists %} {% trans '(does not exist)' %}{% endif %}
{% endfor %}
+
‹ {% trans 'Back to Documentation' %}
{% endblock %}
diff --git a/django/contrib/admindocs/templates/admin_doc/view_index.html b/django/contrib/admindocs/templates/admin_doc/view_index.html
index 891eee7eec..16e48ca8dc 100644
--- a/django/contrib/admindocs/templates/admin_doc/view_index.html
+++ b/django/contrib/admindocs/templates/admin_doc/view_index.html
@@ -15,29 +15,40 @@
{% trans 'View documentation' %}
-{% regroup views|dictsort:"site_id" by site as views_by_site %}
+{% regroup views|dictsort:'namespace' by namespace as views_by_ns %}
-{% for site_views in views_by_site %}
+{% for ns_views in views_by_ns %}
-
{% blocktrans with site_views.grouper.name as name %}Views by URL on {{ name }}{% endblocktrans %}
+
+{% if ns_views.grouper %}
+ {% blocktrans with ns_views.grouper as name %}Views by namespace {{ name }}{% endblocktrans %}
+{% else %}
+ {% blocktrans %}Views by empty namespace{% endblocktrans %}
+{% endif %}
+
-{% for view in site_views.list|dictsort:"url" %}
+{% for view in ns_views.list|dictsort:"url" %}
{% ifchanged %}
-
{% blocktrans with view.full_name as name %}View function: {{ name }}{% endblocktrans %}
+
{% blocktrans with view.full_name as full_name and view.url_name as url_name %}
+ View function: {{ full_name }}
. Name: {{ url_name }}
.
+{% endblocktrans %}
{{ view.title }}
{% endifchanged %}
diff --git a/django/contrib/admindocs/tests/test_fields.py b/django/contrib/admindocs/tests/test_fields.py
index dd465111a1..3f4efedb9c 100644
--- a/django/contrib/admindocs/tests/test_fields.py
+++ b/django/contrib/admindocs/tests/test_fields.py
@@ -21,7 +21,8 @@ class TestFieldType(unittest.TestCase):
pass
def test_field_name(self):
- self.assertRaises(AttributeError,
+ self.assertRaises(
+ AttributeError,
views.get_readable_field_data_type, "NotAField"
)
diff --git a/django/contrib/admindocs/urls.py b/django/contrib/admindocs/urls.py
index 8aa4dcf946..3dc55657df 100644
--- a/django/contrib/admindocs/urls.py
+++ b/django/contrib/admindocs/urls.py
@@ -4,38 +4,29 @@ from django.contrib.admindocs import views
urlpatterns = patterns('',
url('^$',
views.BaseAdminDocsView.as_view(template_name='admin_doc/index.html'),
- name='django-admindocs-docroot'
- ),
+ name='django-admindocs-docroot'),
url('^bookmarklets/$',
views.BookmarkletsView.as_view(),
- name='django-admindocs-bookmarklets'
- ),
+ name='django-admindocs-bookmarklets'),
url('^tags/$',
views.TemplateTagIndexView.as_view(),
- name='django-admindocs-tags'
- ),
+ name='django-admindocs-tags'),
url('^filters/$',
views.TemplateFilterIndexView.as_view(),
- name='django-admindocs-filters'
- ),
+ name='django-admindocs-filters'),
url('^views/$',
views.ViewIndexView.as_view(),
- name='django-admindocs-views-index'
- ),
+ name='django-admindocs-views-index'),
url('^views/(?P
[^/]+)/$',
views.ViewDetailView.as_view(),
- name='django-admindocs-views-detail'
- ),
+ name='django-admindocs-views-detail'),
url('^models/$',
views.ModelIndexView.as_view(),
- name='django-admindocs-models-index'
- ),
+ name='django-admindocs-models-index'),
url('^models/(?P[^\.]+)\.(?P[^/]+)/$',
views.ModelDetailView.as_view(),
- name='django-admindocs-models-detail'
- ),
+ name='django-admindocs-models-detail'),
url('^templates/(?P.*)/$',
views.TemplateDetailView.as_view(),
- name='django-admindocs-templates'
- ),
+ name='django-admindocs-templates'),
)
diff --git a/django/contrib/admindocs/views.py b/django/contrib/admindocs/views.py
index 482d33e76f..9e6dd85595 100644
--- a/django/contrib/admindocs/views.py
+++ b/django/contrib/admindocs/views.py
@@ -2,17 +2,18 @@ from importlib import import_module
import inspect
import os
import re
+import warnings
from django import template
from django.conf import settings
from django.contrib import admin
from django.contrib.admin.views.decorators import staff_member_required
+from django.core.apps import app_cache
from django.db import models
-from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
+from django.core.exceptions import ViewDoesNotExist
from django.http import Http404
from django.core import urlresolvers
from django.contrib.admindocs import utils
-from django.contrib.sites.models import Site
from django.utils.decorators import method_decorator
from django.utils._os import upath
from django.utils import six
@@ -22,10 +23,10 @@ from django.views.generic import TemplateView
# Exclude methods starting with these strings from documentation
MODEL_METHODS_EXCLUDE = ('_', 'add_', 'delete', 'save', 'set_')
-
-class GenericSite(object):
- domain = 'example.com'
- name = 'my site'
+if getattr(settings, 'ADMIN_FOR', None):
+ warnings.warn('The ADMIN_FOR setting has been removed, you can remove '
+ 'this setting from your configuration.', DeprecationWarning,
+ stacklevel=2)
class BaseAdminDocsView(TemplateView):
@@ -128,26 +129,17 @@ class ViewIndexView(BaseAdminDocsView):
template_name = 'admin_doc/view_index.html'
def get_context_data(self, **kwargs):
- if settings.ADMIN_FOR:
- settings_modules = [import_module(m) for m in settings.ADMIN_FOR]
- else:
- settings_modules = [settings]
-
views = []
- for settings_mod in settings_modules:
- urlconf = import_module(settings_mod.ROOT_URLCONF)
- view_functions = extract_views_from_urlpatterns(urlconf.urlpatterns)
- if Site._meta.installed:
- site_obj = Site.objects.get(pk=settings_mod.SITE_ID)
- else:
- site_obj = GenericSite()
- for (func, regex) in view_functions:
- views.append({
- 'full_name': '%s.%s' % (func.__module__, getattr(func, '__name__', func.__class__.__name__)),
- 'site_id': settings_mod.SITE_ID,
- 'site': site_obj,
- 'url': simplify_regex(regex),
- })
+ urlconf = import_module(settings.ROOT_URLCONF)
+ view_functions = extract_views_from_urlpatterns(urlconf.urlpatterns)
+ for (func, regex, namespace, name) in view_functions:
+ views.append({
+ 'full_name': '%s.%s' % (func.__module__, getattr(func, '__name__', func.__class__.__name__)),
+ 'url': simplify_regex(regex),
+ 'url_name': ':'.join((namespace or []) + (name and [name] or [])),
+ 'namespace': ':'.join((namespace or [])),
+ 'name': name,
+ })
kwargs.update({'views': views})
return super(ViewIndexView, self).get_context_data(**kwargs)
@@ -182,7 +174,7 @@ class ModelIndexView(BaseAdminDocsView):
template_name = 'admin_doc/model_index.html'
def get_context_data(self, **kwargs):
- m_list = [m._meta for m in models.get_models()]
+ m_list = [m._meta for m in app_cache.get_models()]
kwargs.update({'models': m_list})
return super(ModelIndexView, self).get_context_data(**kwargs)
@@ -193,17 +185,12 @@ class ModelDetailView(BaseAdminDocsView):
def get_context_data(self, **kwargs):
# Get the model class.
try:
- app_mod = models.get_app(self.kwargs['app_label'])
- except ImproperlyConfigured:
- raise Http404(_("App %r not found") % self.kwargs['app_label'])
- model = None
- for m in models.get_models(app_mod):
- if m._meta.model_name == self.kwargs['model_name']:
- model = m
- break
+ app_cache.get_app_config(self.kwargs['app_label'])
+ except LookupError:
+ raise Http404(_("App %(app_label)r not found") % self.kwargs)
+ model = app_cache.get_model(self.kwargs['app_label'], self.kwargs['model_name'])
if model is None:
- raise Http404(_("Model %(model_name)r not found in app %(app_label)r") % {
- 'model_name': self.kwargs['model_name'], 'app_label': self.kwargs['app_label']})
+ raise Http404(_("Model %(model_name)r not found in app %(app_label)r") % self.kwargs)
opts = model._meta
@@ -296,22 +283,14 @@ class TemplateDetailView(BaseAdminDocsView):
def get_context_data(self, **kwargs):
template = self.kwargs['template']
templates = []
- for site_settings_module in settings.ADMIN_FOR:
- settings_mod = import_module(site_settings_module)
- if Site._meta.installed:
- site_obj = Site.objects.get(pk=settings_mod.SITE_ID)
- else:
- site_obj = GenericSite()
- for dir in settings_mod.TEMPLATE_DIRS:
- template_file = os.path.join(dir, template)
- templates.append({
- 'file': template_file,
- 'exists': os.path.exists(template_file),
- 'contents': lambda: open(template_file).read() if os.path.exists(template_file) else '',
- 'site_id': settings_mod.SITE_ID,
- 'site': site_obj,
- 'order': list(settings_mod.TEMPLATE_DIRS).index(dir),
- })
+ for dir in settings.TEMPLATE_DIRS:
+ template_file = os.path.join(dir, template)
+ templates.append({
+ 'file': template_file,
+ 'exists': os.path.exists(template_file),
+ 'contents': lambda: open(template_file).read() if os.path.exists(template_file) else '',
+ 'order': list(settings.TEMPLATE_DIRS).index(dir),
+ })
kwargs.update({
'name': template,
'templates': templates,
@@ -360,7 +339,7 @@ def get_readable_field_data_type(field):
return field.description % field.__dict__
-def extract_views_from_urlpatterns(urlpatterns, base=''):
+def extract_views_from_urlpatterns(urlpatterns, base='', namespace=None):
"""
Return a list of views from a list of urlpatterns.
@@ -373,10 +352,15 @@ def extract_views_from_urlpatterns(urlpatterns, base=''):
patterns = p.url_patterns
except ImportError:
continue
- views.extend(extract_views_from_urlpatterns(patterns, base + p.regex.pattern))
+ views.extend(extract_views_from_urlpatterns(
+ patterns,
+ base + p.regex.pattern,
+ (namespace or []) + (p.namespace and [p.namespace] or [])
+ ))
elif hasattr(p, 'callback'):
try:
- views.append((p.callback, base + p.regex.pattern))
+ views.append((p.callback, base + p.regex.pattern,
+ namespace, p.name))
except ViewDoesNotExist:
continue
else:
diff --git a/django/contrib/auth/__init__.py b/django/contrib/auth/__init__.py
index 4ef5c0b2cd..d5fc14dd2a 100644
--- a/django/contrib/auth/__init__.py
+++ b/django/contrib/auth/__init__.py
@@ -105,7 +105,15 @@ def logout(request):
user = None
user_logged_out.send(sender=user.__class__, request=request, user=user)
+ # remember language choice saved to session
+ # for backwards compatibility django_language is also checked (remove in 1.8)
+ language = request.session.get('_language', request.session.get('django_language'))
+
request.session.flush()
+
+ if language is not None:
+ request.session['_language'] = language
+
if hasattr(request, 'user'):
from django.contrib.auth.models import AnonymousUser
request.user = AnonymousUser()
@@ -115,13 +123,13 @@ def get_user_model():
"""
Returns the User model that is active in this project.
"""
- from django.db.models import get_model
+ from django.core.apps import app_cache
try:
app_label, model_name = settings.AUTH_USER_MODEL.split('.')
except ValueError:
raise ImproperlyConfigured("AUTH_USER_MODEL must be of the form 'app_label.model_name'")
- user_model = get_model(app_label, model_name)
+ user_model = app_cache.get_model(app_label, model_name)
if user_model is None:
raise ImproperlyConfigured("AUTH_USER_MODEL refers to model '%s' that has not been installed" % settings.AUTH_USER_MODEL)
return user_model
diff --git a/django/contrib/auth/admin.py b/django/contrib/auth/admin.py
index 8514f3bfcf..9bfc070ab2 100644
--- a/django/contrib/auth/admin.py
+++ b/django/contrib/auth/admin.py
@@ -48,8 +48,8 @@ class UserAdmin(admin.ModelAdmin):
add_fieldsets = (
(None, {
'classes': ('wide',),
- 'fields': ('username', 'password1', 'password2')}
- ),
+ 'fields': ('username', 'password1', 'password2'),
+ }),
)
form = UserChangeForm
add_form = UserCreationForm
diff --git a/django/contrib/auth/decorators.py b/django/contrib/auth/decorators.py
index 4935213d0b..6a976b48dc 100644
--- a/django/contrib/auth/decorators.py
+++ b/django/contrib/auth/decorators.py
@@ -29,7 +29,7 @@ def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIE
login_scheme, login_netloc = urlparse(resolved_login_url)[:2]
current_scheme, current_netloc = urlparse(path)[:2]
if ((not login_scheme or login_scheme == current_scheme) and
- (not login_netloc or login_netloc == current_netloc)):
+ (not login_netloc or login_netloc == current_netloc)):
path = request.get_full_path()
from django.contrib.auth.views import redirect_to_login
return redirect_to_login(
diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py
index b13131fc94..e3c139a5fd 100644
--- a/django/contrib/auth/forms.py
+++ b/django/contrib/auth/forms.py
@@ -75,7 +75,7 @@ class UserCreationForm(forms.ModelForm):
username = forms.RegexField(label=_("Username"), max_length=30,
regex=r'^[\w.@+-]+$',
help_text=_("Required. 30 characters or fewer. Letters, digits and "
- "@/./+/-/_ only."),
+ "@/./+/-/_ only."),
error_messages={
'invalid': _("This value may contain only letters, numbers and "
"@/./+/-/_ characters.")})
@@ -124,7 +124,7 @@ class UserChangeForm(forms.ModelForm):
username = forms.RegexField(
label=_("Username"), max_length=30, regex=r"^[\w.@+-]+$",
help_text=_("Required. 30 characters or fewer. Letters, digits and "
- "@/./+/-/_ only."),
+ "@/./+/-/_ only."),
error_messages={
'invalid': _("This value may contain only letters, numbers and "
"@/./+/-/_ characters.")})
diff --git a/django/contrib/auth/hashers.py b/django/contrib/auth/hashers.py
index a4e8133460..713240b502 100644
--- a/django/contrib/auth/hashers.py
+++ b/django/contrib/auth/hashers.py
@@ -57,7 +57,7 @@ def check_password(password, encoded, setter=None, preferred='default'):
must_update = hasher.algorithm != preferred.algorithm
if not must_update:
- must_update = hasher.must_update(encoded)
+ must_update = preferred.must_update(encoded)
is_correct = hasher.verify(password, encoded)
if setter and is_correct and must_update:
setter(password)
diff --git a/django/contrib/auth/management/__init__.py b/django/contrib/auth/management/__init__.py
index 8fd08ac57c..ec963becc4 100644
--- a/django/contrib/auth/management/__init__.py
+++ b/django/contrib/auth/management/__init__.py
@@ -8,10 +8,11 @@ import unicodedata
from django.contrib.auth import (models as auth_app, get_permission_codename,
get_user_model)
+from django.core.apps import app_cache, UnavailableApp
from django.core import exceptions
from django.core.management.base import CommandError
from django.db import DEFAULT_DB_ALIAS, router
-from django.db.models import get_model, get_models, signals, UnavailableApp
+from django.db.models import signals
from django.utils.encoding import DEFAULT_LOCALE_ENCODING
from django.utils import six
from django.utils.six.moves import input
@@ -61,7 +62,7 @@ def _check_permission_clashing(custom, builtin, ctype):
def create_permissions(app, created_models, verbosity, db=DEFAULT_DB_ALIAS, **kwargs):
try:
- get_model('auth', 'Permission')
+ app_cache.get_model('auth', 'Permission')
except UnavailableApp:
return
@@ -70,7 +71,7 @@ def create_permissions(app, created_models, verbosity, db=DEFAULT_DB_ALIAS, **kw
from django.contrib.contenttypes.models import ContentType
- app_models = get_models(app)
+ app_models = app_cache.get_models(app)
# This will hold the permissions we're looking for as
# (content_type, (codename, name))
@@ -119,7 +120,7 @@ def create_permissions(app, created_models, verbosity, db=DEFAULT_DB_ALIAS, **kw
def create_superuser(app, created_models, verbosity, db, **kwargs):
try:
- get_model('auth', 'Permission')
+ app_cache.get_model('auth', 'Permission')
UserModel = get_user_model()
except UnavailableApp:
return
diff --git a/django/contrib/auth/tests/test_basic.py b/django/contrib/auth/tests/test_basic.py
index e5a3e676b8..34b6142632 100644
--- a/django/contrib/auth/tests/test_basic.py
+++ b/django/contrib/auth/tests/test_basic.py
@@ -130,7 +130,8 @@ class BasicTestCase(TestCase):
"Check the operation of the createsuperuser management command"
# We can use the management command to create a superuser
new_io = StringIO()
- call_command("createsuperuser",
+ call_command(
+ "createsuperuser",
interactive=False,
username="joe",
email="joe@somewhere.org",
@@ -146,7 +147,8 @@ class BasicTestCase(TestCase):
# We can supress output on the management command
new_io = StringIO()
- call_command("createsuperuser",
+ call_command(
+ "createsuperuser",
interactive=False,
username="joe2",
email="joe2@somewhere.org",
@@ -159,7 +161,8 @@ class BasicTestCase(TestCase):
self.assertEqual(u.email, 'joe2@somewhere.org')
self.assertFalse(u.has_usable_password())
- call_command("createsuperuser",
+ call_command(
+ "createsuperuser",
interactive=False,
username="joe+admin@somewhere.org",
email="joe@somewhere.org",
@@ -182,7 +185,8 @@ class BasicTestCase(TestCase):
locale.getdefaultlocale = lambda: (None, None)
# Call the command in this new environment
- call_command("createsuperuser",
+ call_command(
+ "createsuperuser",
interactive=True,
username="nolocale@somewhere.org",
email="nolocale@somewhere.org",
@@ -212,7 +216,8 @@ class BasicTestCase(TestCase):
username_field.verbose_name = ulazy('uživatel')
new_io = StringIO()
try:
- call_command("createsuperuser",
+ call_command(
+ "createsuperuser",
interactive=True,
stdout=new_io
)
diff --git a/django/contrib/auth/tests/test_forms.py b/django/contrib/auth/tests/test_forms.py
index bf9a002770..41cb3350b1 100644
--- a/django/contrib/auth/tests/test_forms.py
+++ b/django/contrib/auth/tests/test_forms.py
@@ -133,7 +133,7 @@ class AuthenticationFormTest(TestCase):
[force_text(form.error_messages['inactive'])])
def test_custom_login_allowed_policy(self):
- # The user is inactive, but our custom form policy allows him to log in.
+ # The user is inactive, but our custom form policy allows them to log in.
data = {
'username': 'inactive',
'password': 'password',
diff --git a/django/contrib/auth/tests/test_hashers.py b/django/contrib/auth/tests/test_hashers.py
index d76a81b1a3..58628cd6cd 100644
--- a/django/contrib/auth/tests/test_hashers.py
+++ b/django/contrib/auth/tests/test_hashers.py
@@ -1,13 +1,13 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
-import unittest
from unittest import skipUnless
from django.conf.global_settings import PASSWORD_HASHERS as default_hashers
from django.contrib.auth.hashers import (is_password_usable, BasePasswordHasher,
check_password, make_password, PBKDF2PasswordHasher, load_hashers, PBKDF2SHA1PasswordHasher,
get_hasher, identify_hasher, UNUSABLE_PASSWORD_PREFIX, UNUSABLE_PASSWORD_SUFFIX_LENGTH)
+from django.test import SimpleTestCase
from django.utils import six
@@ -22,7 +22,11 @@ except ImportError:
bcrypt = None
-class TestUtilsHashPass(unittest.TestCase):
+class PBKDF2SingleIterationHasher(PBKDF2PasswordHasher):
+ iterations = 1
+
+
+class TestUtilsHashPass(SimpleTestCase):
def setUp(self):
load_hashers(password_hashers=default_hashers)
@@ -279,6 +283,34 @@ class TestUtilsHashPass(unittest.TestCase):
finally:
hasher.iterations = old_iterations
+ def test_pbkdf2_upgrade_new_hasher(self):
+ self.assertEqual('pbkdf2_sha256', get_hasher('default').algorithm)
+ hasher = get_hasher('default')
+ self.assertNotEqual(hasher.iterations, 1)
+
+ state = {'upgraded': False}
+
+ def setter(password):
+ state['upgraded'] = True
+
+ with self.settings(PASSWORD_HASHERS=[
+ 'django.contrib.auth.tests.test_hashers.PBKDF2SingleIterationHasher']):
+ encoded = make_password('letmein')
+ algo, iterations, salt, hash = encoded.split('$', 3)
+ self.assertEqual(iterations, '1')
+
+ # Check that no upgrade is triggerd
+ self.assertTrue(check_password('letmein', encoded, setter))
+ self.assertFalse(state['upgraded'])
+
+ # Revert to the old iteration count and check if the password would get
+ # updated to the new iteration count.
+ with self.settings(PASSWORD_HASHERS=[
+ 'django.contrib.auth.hashers.PBKDF2PasswordHasher',
+ 'django.contrib.auth.tests.test_hashers.PBKDF2SingleIterationHasher']):
+ self.assertTrue(check_password('letmein', encoded, setter))
+ self.assertTrue(state['upgraded'])
+
def test_load_library_no_algorithm(self):
with self.assertRaises(ValueError) as e:
BasePasswordHasher()._load_library()
diff --git a/django/contrib/auth/tests/test_management.py b/django/contrib/auth/tests/test_management.py
index 795b72b52e..d9de5a2ad0 100644
--- a/django/contrib/auth/tests/test_management.py
+++ b/django/contrib/auth/tests/test_management.py
@@ -8,11 +8,11 @@ from django.contrib.auth.models import User
from django.contrib.auth.tests.custom_user import CustomUser
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.contrib.contenttypes.models import ContentType
+from django.core.apps import app_cache
from django.core import exceptions
from django.core.management import call_command
from django.core.management.base import CommandError
from django.core.management.validation import get_validation_errors
-from django.db.models.loading import get_app
from django.test import TestCase
from django.test.utils import override_settings
from django.utils import six
@@ -91,7 +91,8 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
"Check the operation of the createsuperuser management command"
# We can use the management command to create a superuser
new_io = StringIO()
- call_command("createsuperuser",
+ call_command(
+ "createsuperuser",
interactive=False,
username="joe",
email="joe@somewhere.org",
@@ -108,7 +109,8 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
def test_verbosity_zero(self):
# We can supress output on the management command
new_io = StringIO()
- call_command("createsuperuser",
+ call_command(
+ "createsuperuser",
interactive=False,
username="joe2",
email="joe2@somewhere.org",
@@ -123,7 +125,8 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
def test_email_in_username(self):
new_io = StringIO()
- call_command("createsuperuser",
+ call_command(
+ "createsuperuser",
interactive=False,
username="joe+admin@somewhere.org",
email="joe@somewhere.org",
@@ -140,7 +143,8 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
# We skip validation because the temporary substitution of the
# swappable User model messes with validation.
new_io = StringIO()
- call_command("createsuperuser",
+ call_command(
+ "createsuperuser",
interactive=False,
email="joe@somewhere.org",
date_of_birth="1976-04-01",
@@ -163,7 +167,8 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
# swappable User model messes with validation.
new_io = StringIO()
with self.assertRaises(CommandError):
- call_command("createsuperuser",
+ call_command(
+ "createsuperuser",
interactive=False,
username="joe@somewhere.org",
stdout=new_io,
@@ -179,21 +184,21 @@ class CustomUserModelValidationTestCase(TestCase):
def test_required_fields_is_list(self):
"REQUIRED_FIELDS should be a list."
new_io = StringIO()
- get_validation_errors(new_io, get_app('auth'))
+ get_validation_errors(new_io, app_cache.get_app_config('auth').models_module)
self.assertIn("The REQUIRED_FIELDS must be a list or tuple.", new_io.getvalue())
@override_settings(AUTH_USER_MODEL='auth.CustomUserBadRequiredFields')
def test_username_not_in_required_fields(self):
"USERNAME_FIELD should not appear in REQUIRED_FIELDS."
new_io = StringIO()
- get_validation_errors(new_io, get_app('auth'))
+ get_validation_errors(new_io, app_cache.get_app_config('auth').models_module)
self.assertIn("The field named as the USERNAME_FIELD should not be included in REQUIRED_FIELDS on a swappable User model.", new_io.getvalue())
@override_settings(AUTH_USER_MODEL='auth.CustomUserNonUniqueUsername')
def test_username_non_unique(self):
"A non-unique USERNAME_FIELD should raise a model validation error."
new_io = StringIO()
- get_validation_errors(new_io, get_app('auth'))
+ get_validation_errors(new_io, app_cache.get_app_config('auth').models_module)
self.assertIn("The USERNAME_FIELD must be unique. Add unique=True to the field parameters.", new_io.getvalue())
diff --git a/django/contrib/auth/tests/test_models.py b/django/contrib/auth/tests/test_models.py
index 1373a3c1c1..a858bb2566 100644
--- a/django/contrib/auth/tests/test_models.py
+++ b/django/contrib/auth/tests/test_models.py
@@ -69,9 +69,11 @@ class UserManagerTestCase(TestCase):
self.assertEqual(returned, 'email\ with_whitespace@d.com')
def test_empty_username(self):
- self.assertRaisesMessage(ValueError,
- 'The given username must be set',
- User.objects.create_user, username='')
+ self.assertRaisesMessage(
+ ValueError,
+ 'The given username must be set',
+ User.objects.create_user, username=''
+ )
class AbstractUserTestCase(TestCase):
diff --git a/django/contrib/auth/tests/test_remote_user.py b/django/contrib/auth/tests/test_remote_user.py
index 656a72a61d..5b743ac8c1 100644
--- a/django/contrib/auth/tests/test_remote_user.py
+++ b/django/contrib/auth/tests/test_remote_user.py
@@ -3,6 +3,7 @@ from datetime import datetime
from django.conf import settings
from django.contrib.auth import authenticate
from django.contrib.auth.backends import RemoteUserBackend
+from django.contrib.auth.middleware import RemoteUserMiddleware
from django.contrib.auth.models import User
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.test import TestCase
@@ -15,6 +16,7 @@ class RemoteUserTest(TestCase):
urls = 'django.contrib.auth.tests.urls'
middleware = 'django.contrib.auth.middleware.RemoteUserMiddleware'
backend = 'django.contrib.auth.backends.RemoteUserBackend'
+ header = 'REMOTE_USER'
# Usernames to be passed in REMOTE_USER for the test_known_user test case.
known_user = 'knownuser'
@@ -37,11 +39,11 @@ class RemoteUserTest(TestCase):
self.assertTrue(response.context['user'].is_anonymous())
self.assertEqual(User.objects.count(), num_users)
- response = self.client.get('/remote_user/', REMOTE_USER=None)
+ response = self.client.get('/remote_user/', **{self.header: None})
self.assertTrue(response.context['user'].is_anonymous())
self.assertEqual(User.objects.count(), num_users)
- response = self.client.get('/remote_user/', REMOTE_USER='')
+ response = self.client.get('/remote_user/', **{self.header: ''})
self.assertTrue(response.context['user'].is_anonymous())
self.assertEqual(User.objects.count(), num_users)
@@ -51,13 +53,13 @@ class RemoteUserTest(TestCase):
as a User.
"""
num_users = User.objects.count()
- response = self.client.get('/remote_user/', REMOTE_USER='newuser')
+ response = self.client.get('/remote_user/', **{self.header: 'newuser'})
self.assertEqual(response.context['user'].username, 'newuser')
self.assertEqual(User.objects.count(), num_users + 1)
User.objects.get(username='newuser')
# Another request with same user should not create any new users.
- response = self.client.get('/remote_user/', REMOTE_USER='newuser')
+ response = self.client.get('/remote_user/', **{self.header: 'newuser'})
self.assertEqual(User.objects.count(), num_users + 1)
def test_known_user(self):
@@ -67,12 +69,14 @@ class RemoteUserTest(TestCase):
User.objects.create(username='knownuser')
User.objects.create(username='knownuser2')
num_users = User.objects.count()
- response = self.client.get('/remote_user/', REMOTE_USER=self.known_user)
+ response = self.client.get('/remote_user/',
+ **{self.header: self.known_user})
self.assertEqual(response.context['user'].username, 'knownuser')
self.assertEqual(User.objects.count(), num_users)
# Test that a different user passed in the headers causes the new user
# to be logged in.
- response = self.client.get('/remote_user/', REMOTE_USER=self.known_user2)
+ response = self.client.get('/remote_user/',
+ **{self.header: self.known_user2})
self.assertEqual(response.context['user'].username, 'knownuser2')
self.assertEqual(User.objects.count(), num_users)
@@ -89,13 +93,15 @@ class RemoteUserTest(TestCase):
user.last_login = default_login
user.save()
- response = self.client.get('/remote_user/', REMOTE_USER=self.known_user)
+ response = self.client.get('/remote_user/',
+ **{self.header: self.known_user})
self.assertNotEqual(default_login, response.context['user'].last_login)
user = User.objects.get(username='knownuser')
user.last_login = default_login
user.save()
- response = self.client.get('/remote_user/', REMOTE_USER=self.known_user)
+ response = self.client.get('/remote_user/',
+ **{self.header: self.known_user})
self.assertEqual(default_login, response.context['user'].last_login)
def test_header_disappears(self):
@@ -105,7 +111,8 @@ class RemoteUserTest(TestCase):
"""
User.objects.create(username='knownuser')
# Known user authenticates
- response = self.client.get('/remote_user/', REMOTE_USER=self.known_user)
+ response = self.client.get('/remote_user/',
+ **{self.header: self.known_user})
self.assertEqual(response.context['user'].username, 'knownuser')
# During the session, the REMOTE_USER header disappears. Should trigger logout.
response = self.client.get('/remote_user/')
@@ -140,7 +147,7 @@ class RemoteUserNoCreateTest(RemoteUserTest):
def test_unknown_user(self):
num_users = User.objects.count()
- response = self.client.get('/remote_user/', REMOTE_USER='newuser')
+ response = self.client.get('/remote_user/', **{self.header: 'newuser'})
self.assertTrue(response.context['user'].is_anonymous())
self.assertEqual(User.objects.count(), num_users)
@@ -194,3 +201,22 @@ class RemoteUserCustomTest(RemoteUserTest):
super(RemoteUserCustomTest, self).test_unknown_user()
newuser = User.objects.get(username='newuser')
self.assertEqual(newuser.email, 'user@example.com')
+
+
+class CustomHeaderMiddleware(RemoteUserMiddleware):
+ """
+ Middleware that overrides custom HTTP auth user header.
+ """
+ header = 'HTTP_AUTHUSER'
+
+
+@skipIfCustomUser
+class CustomHeaderRemoteUserTest(RemoteUserTest):
+ """
+ Tests a custom RemoteUserMiddleware subclass with custom HTTP auth user
+ header.
+ """
+ middleware = (
+ 'django.contrib.auth.tests.test_remote_user.CustomHeaderMiddleware'
+ )
+ header = 'HTTP_AUTHUSER'
diff --git a/django/contrib/auth/tests/test_views.py b/django/contrib/auth/tests/test_views.py
index 060f0086ad..4a1783b14d 100644
--- a/django/contrib/auth/tests/test_views.py
+++ b/django/contrib/auth/tests/test_views.py
@@ -1,3 +1,4 @@
+from importlib import import_module
import itertools
import os
import re
@@ -710,6 +711,18 @@ class LogoutTest(AuthViewsTestCase):
"%s should be allowed" % good_url)
self.confirm_logged_out()
+ def test_logout_preserve_language(self):
+ """Check that language stored in session is preserved after logout"""
+ # Create a new session with language
+ engine = import_module(settings.SESSION_ENGINE)
+ session = engine.SessionStore()
+ session['_language'] = 'pl'
+ session.save()
+ self.client.cookies[settings.SESSION_COOKIE_NAME] = session.session_key
+
+ self.client.get('/logout/')
+ self.assertEqual(self.client.session['_language'], 'pl')
+
@skipIfCustomUser
@override_settings(
diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py
index 5e11030f03..013ed9f3e2 100644
--- a/django/contrib/auth/views.py
+++ b/django/contrib/auth/views.py
@@ -98,7 +98,7 @@ def logout(request, next_page=None,
def logout_then_login(request, login_url=None, current_app=None, extra_context=None):
"""
- Logs out the user if he is logged in. Then redirects to the log-in page.
+ Logs out the user if they are logged in. Then redirects to the log-in page.
"""
if not login_url:
login_url = settings.LOGIN_URL
diff --git a/django/contrib/comments/views/comments.py b/django/contrib/comments/views/comments.py
index a2cbe33c0e..294c7c8e42 100644
--- a/django/contrib/comments/views/comments.py
+++ b/django/contrib/comments/views/comments.py
@@ -3,6 +3,7 @@ from django.conf import settings
from django.contrib import comments
from django.contrib.comments import signals
from django.contrib.comments.views.utils import next_redirect, confirmation_view
+from django.core.apps import app_cache
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.db import models
from django.shortcuts import render_to_response
@@ -48,7 +49,7 @@ def post_comment(request, next=None, using=None):
if ctype is None or object_pk is None:
return CommentPostBadRequest("Missing content_type or object_pk field.")
try:
- model = models.get_model(*ctype.split(".", 1))
+ model = app_cache.get_model(*ctype.split(".", 1))
target = model._default_manager.using(using).get(pk=object_pk)
except TypeError:
return CommentPostBadRequest(
diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py
index a310de2fb2..7af1f6a985 100644
--- a/django/contrib/contenttypes/generic.py
+++ b/django/contrib/contenttypes/generic.py
@@ -453,9 +453,10 @@ class BaseGenericInlineFormSet(BaseModelFormSet):
@classmethod
def get_default_prefix(cls):
opts = cls.model._meta
- return '-'.join((opts.app_label, opts.model_name,
- cls.ct_field.name, cls.ct_fk_field.name,
- ))
+ return '-'.join(
+ (opts.app_label, opts.model_name,
+ cls.ct_field.name, cls.ct_fk_field.name)
+ )
def save_new(self, form, commit=True):
setattr(form.instance, self.ct_field.get_attname(),
diff --git a/django/contrib/contenttypes/management.py b/django/contrib/contenttypes/management.py
index 3d2fc4b95e..7ff08b70f8 100644
--- a/django/contrib/contenttypes/management.py
+++ b/django/contrib/contenttypes/management.py
@@ -1,6 +1,7 @@
from django.contrib.contenttypes.models import ContentType
+from django.core.apps import app_cache, UnavailableApp
from django.db import DEFAULT_DB_ALIAS, router
-from django.db.models import get_apps, get_model, get_models, signals, UnavailableApp
+from django.db.models import signals
from django.utils.encoding import smart_text
from django.utils import six
from django.utils.six.moves import input
@@ -12,7 +13,7 @@ def update_contenttypes(app, created_models, verbosity=2, db=DEFAULT_DB_ALIAS, *
entries that no longer have a matching model class.
"""
try:
- get_model('contenttypes', 'ContentType')
+ app_cache.get_model('contenttypes', 'ContentType')
except UnavailableApp:
return
@@ -20,7 +21,7 @@ def update_contenttypes(app, created_models, verbosity=2, db=DEFAULT_DB_ALIAS, *
return
ContentType.objects.clear_cache()
- app_models = get_models(app)
+ app_models = app_cache.get_models(app)
if not app_models:
return
# They all have the same app_label, get the first one.
@@ -85,8 +86,8 @@ If you're unsure, answer 'no'.
def update_all_contenttypes(verbosity=2, **kwargs):
- for app in get_apps():
- update_contenttypes(app, None, verbosity, **kwargs)
+ for app_config in app_cache.get_app_configs(only_with_models_module=True):
+ update_contenttypes(app_config.models_module, None, verbosity, **kwargs)
signals.post_migrate.connect(update_contenttypes)
diff --git a/django/contrib/contenttypes/models.py b/django/contrib/contenttypes/models.py
index aec15d6664..98eae0219c 100644
--- a/django/contrib/contenttypes/models.py
+++ b/django/contrib/contenttypes/models.py
@@ -1,3 +1,4 @@
+from django.core.apps import app_cache
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import smart_text, force_text
@@ -156,7 +157,7 @@ class ContentType(models.Model):
def model_class(self):
"Returns the Python model class for this type of content."
- return models.get_model(self.app_label, self.model,
+ return app_cache.get_model(self.app_label, self.model,
only_installed=False)
def get_object_for_this_type(self, **kwargs):
diff --git a/django/contrib/contenttypes/tests.py b/django/contrib/contenttypes/tests.py
index 8a0010061e..756430f393 100644
--- a/django/contrib/contenttypes/tests.py
+++ b/django/contrib/contenttypes/tests.py
@@ -52,12 +52,13 @@ class FooWithBrokenAbsoluteUrl(FooWithoutUrl):
class ContentTypesTests(TestCase):
+
def setUp(self):
- self.old_Site_meta_installed = Site._meta.installed
+ self._old_installed = Site._meta.app_config.installed
ContentType.objects.clear_cache()
def tearDown(self):
- Site._meta.installed = self.old_Site_meta_installed
+ Site._meta.app_config.installed = self._old_installed
ContentType.objects.clear_cache()
def test_lookup_cache(self):
@@ -222,12 +223,12 @@ class ContentTypesTests(TestCase):
user_ct = ContentType.objects.get_for_model(FooWithUrl)
obj = FooWithUrl.objects.create(name="john")
- if Site._meta.installed:
- response = shortcut(request, user_ct.id, obj.id)
- self.assertEqual("http://%s/users/john/" % get_current_site(request).domain,
- response._headers.get("location")[1])
+ Site._meta.app_config.installed = True
+ response = shortcut(request, user_ct.id, obj.id)
+ self.assertEqual("http://%s/users/john/" % get_current_site(request).domain,
+ response._headers.get("location")[1])
- Site._meta.installed = False
+ Site._meta.app_config.installed = False
response = shortcut(request, user_ct.id, obj.id)
self.assertEqual("http://Example.com/users/john/",
response._headers.get("location")[1])
diff --git a/django/contrib/flatpages/forms.py b/django/contrib/flatpages/forms.py
index 7e3a39e88d..8d85922d9d 100644
--- a/django/contrib/flatpages/forms.py
+++ b/django/contrib/flatpages/forms.py
@@ -23,8 +23,8 @@ class FlatpageForm(forms.ModelForm):
code='missing_leading_slash',
)
if (settings.APPEND_SLASH and
- 'django.middleware.common.CommonMiddleware' in settings.MIDDLEWARE_CLASSES and
- not url.endswith('/')):
+ 'django.middleware.common.CommonMiddleware' in settings.MIDDLEWARE_CLASSES and
+ not url.endswith('/')):
raise forms.ValidationError(
ugettext("URL is missing a trailing slash."),
code='missing_trailing_slash',
diff --git a/django/contrib/flatpages/templatetags/flatpages.py b/django/contrib/flatpages/templatetags/flatpages.py
index a32ac7f490..5e7e9bdfe5 100644
--- a/django/contrib/flatpages/templatetags/flatpages.py
+++ b/django/contrib/flatpages/templatetags/flatpages.py
@@ -73,8 +73,8 @@ def get_flatpages(parser, token):
"""
bits = token.split_contents()
syntax_message = ("%(tag_name)s expects a syntax of %(tag_name)s "
- "['url_starts_with'] [for user] as context_name" %
- dict(tag_name=bits[0]))
+ "['url_starts_with'] [for user] as context_name" %
+ dict(tag_name=bits[0]))
# Must have at 3-6 bits in the tag
if len(bits) >= 3 and len(bits) <= 6:
diff --git a/django/contrib/formtools/models.py b/django/contrib/formtools/models.py
index 13990e24af..8a90059b9d 100644
--- a/django/contrib/formtools/models.py
+++ b/django/contrib/formtools/models.py
@@ -1 +1,2 @@
-""" models.py (even empty) currently required by the runtests.py to enable unit tests """
+# This file is required to pretend formtools has models.
+# Otherwise test models cannot be registered.
diff --git a/django/contrib/formtools/tests/wizard/test_forms.py b/django/contrib/formtools/tests/wizard/test_forms.py
index 386e448485..fedb799669 100644
--- a/django/contrib/formtools/tests/wizard/test_forms.py
+++ b/django/contrib/formtools/tests/wizard/test_forms.py
@@ -48,7 +48,7 @@ class CustomKwargsStep1(Step1):
def __init__(self, test=None, *args, **kwargs):
self.test = test
- return super(CustomKwargsStep1, self).__init__(*args, **kwargs)
+ super(CustomKwargsStep1, self).__init__(*args, **kwargs)
class TestModel(models.Model):
diff --git a/django/contrib/formtools/wizard/views.py b/django/contrib/formtools/wizard/views.py
index f19cfc76f3..2b4a86d115 100644
--- a/django/contrib/formtools/wizard/views.py
+++ b/django/contrib/formtools/wizard/views.py
@@ -123,7 +123,7 @@ class WizardView(TemplateView):
@classmethod
def get_initkwargs(cls, form_list=None, initial_dict=None,
- instance_dict=None, condition_dict=None, *args, **kwargs):
+ instance_dict=None, condition_dict=None, *args, **kwargs):
"""
Creates a dict with all needed parameters for the form wizard instances.
diff --git a/django/contrib/gis/db/backends/postgis/operations.py b/django/contrib/gis/db/backends/postgis/operations.py
index fabf2b7871..e8da6a52a6 100644
--- a/django/contrib/gis/db/backends/postgis/operations.py
+++ b/django/contrib/gis/db/backends/postgis/operations.py
@@ -369,7 +369,7 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
dist_param = value
if (not geography and geodetic and lookup_type != 'dwithin'
- and option == 'spheroid'):
+ and option == 'spheroid'):
# using distance_spheroid requires the spheroid of the field as
# a parameter.
return [f._spheroid, dist_param]
@@ -467,7 +467,7 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
def two_to_three(np):
return np >= 2 and np <= 3
if (lookup_type in self.distance_functions and
- lookup_type != 'dwithin'):
+ lookup_type != 'dwithin'):
return two_to_three(num_param)
else:
return exactly_two(num_param)
diff --git a/django/contrib/gis/db/backends/postgis/schema.py b/django/contrib/gis/db/backends/postgis/schema.py
index 46e438acaa..181826789b 100644
--- a/django/contrib/gis/db/backends/postgis/schema.py
+++ b/django/contrib/gis/db/backends/postgis/schema.py
@@ -1,6 +1,4 @@
-from django.conf import settings
from django.db.backends.postgresql_psycopg2.schema import DatabaseSchemaEditor
-from django.utils.functional import cached_property
class PostGISSchemaEditor(DatabaseSchemaEditor):
diff --git a/django/contrib/gis/db/backends/spatialite/creation.py b/django/contrib/gis/db/backends/spatialite/creation.py
index fb3606f7e9..521985259e 100644
--- a/django/contrib/gis/db/backends/spatialite/creation.py
+++ b/django/contrib/gis/db/backends/spatialite/creation.py
@@ -13,7 +13,7 @@ class SpatiaLiteCreation(DatabaseCreation):
database already exists. Returns the name of the test database created.
This method is overloaded to load up the SpatiaLite initialization
- SQL prior to calling the `syncdb` command.
+ SQL prior to calling the `migrate` command.
"""
# Don't import django.core.management if it isn't needed.
from django.core.management import call_command
@@ -31,13 +31,13 @@ class SpatiaLiteCreation(DatabaseCreation):
self.connection.close()
self.connection.settings_dict["NAME"] = test_database_name
- # Need to load the SpatiaLite initialization SQL before running `syncdb`.
+ # Need to load the SpatiaLite initialization SQL before running `migrate`.
self.load_spatialite_sql()
- # Report syncdb messages at one level lower than that requested.
+ # Report migrate messages at one level lower than that requested.
# This ensures we don't get flooded with messages during testing
# (unless you really ask to be flooded)
- call_command('syncdb',
+ call_command('migrate',
verbosity=max(verbosity - 1, 0),
interactive=False,
database=self.connection.alias,
@@ -47,7 +47,7 @@ class SpatiaLiteCreation(DatabaseCreation):
# custom SQL has been removed. The only test data should come from
# test fixtures, or autogenerated from post_migrate triggers.
# This has the side effect of loading initial data (which was
- # intentionally skipped in the syncdb).
+ # intentionally skipped in the migrate).
call_command('flush',
verbosity=max(verbosity - 1, 0),
interactive=False,
diff --git a/django/contrib/gis/db/backends/spatialite/models.py b/django/contrib/gis/db/backends/spatialite/models.py
index 9860779647..551bc70ccd 100644
--- a/django/contrib/gis/db/backends/spatialite/models.py
+++ b/django/contrib/gis/db/backends/spatialite/models.py
@@ -1,7 +1,7 @@
"""
The GeometryColumns and SpatialRefSys models for the SpatiaLite backend.
"""
-from django.db import models
+from django.db import connection, models
from django.contrib.gis.db.backends.base import SpatialRefSysMixin
from django.utils.encoding import python_2_unicode_compatible
@@ -53,9 +53,13 @@ class SpatialRefSys(models.Model, SpatialRefSysMixin):
auth_srid = models.IntegerField()
ref_sys_name = models.CharField(max_length=256)
proj4text = models.CharField(max_length=2048)
+ if connection.ops.spatial_version[0] >= 4:
+ srtext = models.CharField(max_length=2048)
@property
def wkt(self):
+ if hasattr(self, 'srtext'):
+ return self.srtext
from django.contrib.gis.gdal import SpatialReference
return SpatialReference(self.proj4text).wkt
diff --git a/django/contrib/gis/db/models/fields.py b/django/contrib/gis/db/models/fields.py
index ee82d4f263..2daf08147b 100644
--- a/django/contrib/gis/db/models/fields.py
+++ b/django/contrib/gis/db/models/fields.py
@@ -114,9 +114,9 @@ class GeometryField(Field):
kwargs['srid'] = self.srid
if self.dim != 2:
kwargs['dim'] = self.dim
- if self.spatial_index != True:
+ if self.spatial_index is not True:
kwargs['spatial_index'] = self.spatial_index
- if self.geography != False:
+ if self.geography is not False:
kwargs['geography'] = self.geography
return name, path, args, kwargs
diff --git a/django/contrib/gis/db/models/query.py b/django/contrib/gis/db/models/query.py
index cdff5396cc..66b96a0873 100644
--- a/django/contrib/gis/db/models/query.py
+++ b/django/contrib/gis/db/models/query.py
@@ -362,12 +362,14 @@ class GeoQuerySet(QuerySet):
relative = int(bool(relative))
if not isinstance(precision, six.integer_types):
raise TypeError('SVG precision keyword argument must be an integer.')
- s = {'desc': 'SVG',
- 'procedure_fmt': '%(geo_col)s,%(rel)s,%(precision)s',
- 'procedure_args': {'rel': relative,
- 'precision': precision,
- }
- }
+ s = {
+ 'desc': 'SVG',
+ 'procedure_fmt': '%(geo_col)s,%(rel)s,%(precision)s',
+ 'procedure_args': {
+ 'rel': relative,
+ 'precision': precision,
+ }
+ }
return self._spatial_attribute('svg', s, **kwargs)
def sym_difference(self, geom, **kwargs):
@@ -746,11 +748,12 @@ class GeoQuerySet(QuerySet):
for geometry set-like operations (e.g., intersection, difference,
union, sym_difference).
"""
- s = {'geom_args': ('geom',),
- 'select_field': GeomField(),
- 'procedure_fmt': '%(geo_col)s,%(geom)s',
- 'procedure_args': {'geom': geom},
- }
+ s = {
+ 'geom_args': ('geom',),
+ 'select_field': GeomField(),
+ 'procedure_fmt': '%(geo_col)s,%(geom)s',
+ 'procedure_args': {'geom': geom},
+ }
if connections[self.db].ops.oracle:
s['procedure_fmt'] += ',%(tolerance)s'
s['procedure_args']['tolerance'] = tolerance
diff --git a/django/contrib/gis/db/models/sql/where.py b/django/contrib/gis/db/models/sql/where.py
index 3a504274b6..1e750ebd89 100644
--- a/django/contrib/gis/db/models/sql/where.py
+++ b/django/contrib/gis/db/models/sql/where.py
@@ -39,7 +39,7 @@ class GeoWhereNode(WhereNode):
if isinstance(data, (list, tuple)):
obj, lookup_type, value = data
if (isinstance(obj, Constraint) and
- isinstance(obj.field, GeometryField)):
+ isinstance(obj.field, GeometryField)):
data = (GeoConstraint(obj), lookup_type, value)
return super(GeoWhereNode, self)._prepare_data(data)
diff --git a/django/contrib/gis/forms/fields.py b/django/contrib/gis/forms/fields.py
index 151f66c39c..29e423df28 100644
--- a/django/contrib/gis/forms/fields.py
+++ b/django/contrib/gis/forms/fields.py
@@ -23,7 +23,7 @@ class GeometryField(forms.Field):
'invalid_geom': _('Invalid geometry value.'),
'invalid_geom_type': _('Invalid geometry type.'),
'transform_error': _('An error occurred when transforming the geometry '
- 'to the SRID of the geometry form field.'),
+ 'to the SRID of the geometry form field.'),
}
def __init__(self, **kwargs):
@@ -44,10 +44,16 @@ class GeometryField(forms.Field):
if not isinstance(value, GEOSGeometry):
try:
value = GEOSGeometry(value)
- if not value.srid:
- value.srid = self.widget.map_srid
except (GEOSException, ValueError, TypeError):
raise forms.ValidationError(self.error_messages['invalid_geom'], code='invalid_geom')
+
+ # Try to set the srid
+ if not value.srid:
+ try:
+ value.srid = self.widget.map_srid
+ except AttributeError:
+ if self.srid:
+ value.srid = self.srid
return value
def clean(self, value):
@@ -66,15 +72,12 @@ class GeometryField(forms.Field):
raise forms.ValidationError(self.error_messages['invalid_geom_type'], code='invalid_geom_type')
# Transforming the geometry if the SRID was set.
- if self.srid:
- if not geom.srid:
- # Should match that of the field if not given.
- geom.srid = self.srid
- elif self.srid != -1 and self.srid != geom.srid:
- try:
- geom.transform(self.srid)
- except GEOSException:
- raise forms.ValidationError(self.error_messages['transform_error'], code='transform_error')
+ if self.srid and self.srid != -1 and self.srid != geom.srid:
+ try:
+ geom.transform(self.srid)
+ except GEOSException:
+ raise forms.ValidationError(
+ self.error_messages['transform_error'], code='transform_error')
return geom
diff --git a/django/contrib/gis/forms/widgets.py b/django/contrib/gis/forms/widgets.py
index 2609ac16fd..eca2c67a8d 100644
--- a/django/contrib/gis/forms/widgets.py
+++ b/django/contrib/gis/forms/widgets.py
@@ -66,7 +66,8 @@ class BaseGeometryWidget(Widget):
value.srid, self.map_srid, err)
)
- context = self.build_attrs(attrs,
+ context = self.build_attrs(
+ attrs,
name=name,
module='geodjango_%s' % name.replace('-', '_'), # JS-safe
serialized=self.serialize(value),
@@ -102,6 +103,13 @@ class OSMWidget(BaseGeometryWidget):
'gis/js/OLMapWidget.js',
)
+ def __init__(self, attrs=None):
+ super(OSMWidget, self).__init__()
+ for key in ('default_lon', 'default_lat'):
+ self.attrs[key] = getattr(self, key)
+ if attrs:
+ self.attrs.update(attrs)
+
@property
def map_srid(self):
# Use the official spherical mercator projection SRID on versions
@@ -110,12 +118,3 @@ class OSMWidget(BaseGeometryWidget):
return 3857
else:
return 900913
-
- def render(self, name, value, attrs=None):
- default_attrs = {
- 'default_lon': self.default_lon,
- 'default_lat': self.default_lat,
- }
- if attrs:
- default_attrs.update(attrs)
- return super(OSMWidget, self).render(name, value, default_attrs)
diff --git a/django/contrib/gis/gdal/prototypes/generation.py b/django/contrib/gis/gdal/prototypes/generation.py
index 6b38dc593f..3d2e3046c8 100644
--- a/django/contrib/gis/gdal/prototypes/generation.py
+++ b/django/contrib/gis/gdal/prototypes/generation.py
@@ -104,7 +104,7 @@ def string_output(func, argtypes, offset=-1, str_result=False, decoding=None):
# given offset.
def _check_str(result, func, cargs):
res = check_string(result, func, cargs,
- offset=offset, str_result=str_result)
+ offset=offset, str_result=str_result)
if res and decoding:
res = res.decode(decoding)
return res
diff --git a/django/contrib/gis/geoip/tests.py b/django/contrib/gis/geoip/tests.py
index f771608e1d..8d7bdff291 100644
--- a/django/contrib/gis/geoip/tests.py
+++ b/django/contrib/gis/geoip/tests.py
@@ -25,7 +25,7 @@ if HAS_GEOS:
@skipUnless(HAS_GEOIP and getattr(settings, "GEOIP_PATH", None),
- "GeoIP is required along with the GEOIP_DATA setting.")
+ "GeoIP is required along with the GEOIP_PATH setting.")
class GeoIPTest(unittest.TestCase):
def test01_init(self):
diff --git a/django/contrib/gis/geos/libgeos.py b/django/contrib/gis/geos/libgeos.py
index 059eb5b602..239067a433 100644
--- a/django/contrib/gis/geos/libgeos.py
+++ b/django/contrib/gis/geos/libgeos.py
@@ -48,9 +48,11 @@ if lib_names:
# No GEOS library could be found.
if lib_path is None:
- raise ImportError('Could not find the GEOS library (tried "%s"). '
- 'Try setting GEOS_LIBRARY_PATH in your settings.' %
- '", "'.join(lib_names))
+ raise ImportError(
+ 'Could not find the GEOS library (tried "%s"). '
+ 'Try setting GEOS_LIBRARY_PATH in your settings.' %
+ '", "'.join(lib_names)
+ )
# Getting the GEOS C library. The C interface (CDLL) is used for
# both *NIX and Windows.
diff --git a/django/contrib/gis/geos/polygon.py b/django/contrib/gis/geos/polygon.py
index f617f1fe76..171bd971cd 100644
--- a/django/contrib/gis/geos/polygon.py
+++ b/django/contrib/gis/geos/polygon.py
@@ -171,5 +171,5 @@ class Polygon(GEOSGeometry):
def kml(self):
"Returns the KML representation of this Polygon."
inner_kml = ''.join("%s" % self[i + 1].kml
- for i in xrange(self.num_interior_rings))
+ for i in xrange(self.num_interior_rings))
return "%s%s" % (self[0].kml, inner_kml)
diff --git a/django/contrib/gis/geos/tests/test_geos_mutation.py b/django/contrib/gis/geos/tests/test_geos_mutation.py
index ba95ff7582..db9f12d3b6 100644
--- a/django/contrib/gis/geos/tests/test_geos_mutation.py
+++ b/django/contrib/gis/geos/tests/test_geos_mutation.py
@@ -117,7 +117,7 @@ class GEOSMutationTest(unittest.TestCase):
def test04_LineStringMutations(self):
'Testing LineString mutations'
for ls in (LineString((1, 0), (4, 1), (6, -1)),
- fromstr('LINESTRING (1 0,4 1,6 -1)')):
+ fromstr('LINESTRING (1 0,4 1,6 -1)')):
self.assertEqual(ls._get_single_external(1), (4.0, 1.0), 'LineString _get_single_external')
# _set_single
@@ -135,14 +135,14 @@ class GEOSMutationTest(unittest.TestCase):
def test05_Polygon(self):
'Testing Polygon mutations'
for pg in (Polygon(((1, 0), (4, 1), (6, -1), (8, 10), (1, 0)),
- ((5, 4), (6, 4), (6, 3), (5, 4))),
- fromstr('POLYGON ((1 0,4 1,6 -1,8 10,1 0),(5 4,6 4,6 3,5 4))')):
+ ((5, 4), (6, 4), (6, 3), (5, 4))),
+ fromstr('POLYGON ((1 0,4 1,6 -1,8 10,1 0),(5 4,6 4,6 3,5 4))')):
self.assertEqual(pg._get_single_external(0),
- LinearRing((1, 0), (4, 1), (6, -1), (8, 10), (1, 0)),
- 'Polygon _get_single_external(0)')
+ LinearRing((1, 0), (4, 1), (6, -1), (8, 10), (1, 0)),
+ 'Polygon _get_single_external(0)')
self.assertEqual(pg._get_single_external(1),
- LinearRing((5, 4), (6, 4), (6, 3), (5, 4)),
- 'Polygon _get_single_external(1)')
+ LinearRing((5, 4), (6, 4), (6, 3), (5, 4)),
+ 'Polygon _get_single_external(1)')
# _set_list
pg._set_list(2, (((1, 2), (10, 0), (12, 9), (-1, 15), (1, 2)),
@@ -160,7 +160,7 @@ class GEOSMutationTest(unittest.TestCase):
def test06_Collection(self):
'Testing Collection mutations'
for mp in (MultiPoint(*map(Point, ((3, 4), (-1, 2), (5, -4), (2, 8)))),
- fromstr('MULTIPOINT (3 4,-1 2,5 -4,2 8)')):
+ fromstr('MULTIPOINT (3 4,-1 2,5 -4,2 8)')):
self.assertEqual(mp._get_single_external(2), Point(5, -4), 'Collection _get_single_external')
mp._set_list(3, map(Point, ((5, 5), (3, -2), (8, 1))))
diff --git a/django/contrib/gis/models.py b/django/contrib/gis/models.py
index e379e82a7b..466efcee9a 100644
--- a/django/contrib/gis/models.py
+++ b/django/contrib/gis/models.py
@@ -1,7 +1,7 @@
from django.db import connection
if (hasattr(connection.ops, 'spatial_version') and
- not connection.ops.mysql):
+ not connection.ops.mysql):
# Getting the `SpatialRefSys` and `GeometryColumns`
# models for the default spatial backend. These
# aliases are provided for backwards-compatibility.
diff --git a/django/contrib/gis/sitemaps/kml.py b/django/contrib/gis/sitemaps/kml.py
index 5e74c6c47d..aa9ae69f5d 100644
--- a/django/contrib/gis/sitemaps/kml.py
+++ b/django/contrib/gis/sitemaps/kml.py
@@ -1,3 +1,4 @@
+from django.core.apps import app_cache
from django.core import urlresolvers
from django.contrib.sitemaps import Sitemap
from django.contrib.gis.db.models.fields import GeometryField
@@ -25,7 +26,7 @@ class KMLSitemap(Sitemap):
"""
kml_sources = []
if sources is None:
- sources = models.get_models()
+ sources = app_cache.get_models()
for source in sources:
if isinstance(source, models.base.ModelBase):
for field in source._meta.fields:
diff --git a/django/contrib/gis/sitemaps/views.py b/django/contrib/gis/sitemaps/views.py
index c4c08de8f5..e55a371672 100644
--- a/django/contrib/gis/sitemaps/views.py
+++ b/django/contrib/gis/sitemaps/views.py
@@ -1,5 +1,8 @@
from __future__ import unicode_literals
+import warnings
+
+from django.core.apps import app_cache
from django.http import HttpResponse, Http404
from django.template import loader
from django.contrib.sites.models import get_current_site
@@ -7,7 +10,6 @@ from django.core import urlresolvers
from django.core.paginator import EmptyPage, PageNotAnInteger
from django.contrib.gis.db.models.fields import GeometryField
from django.db import connections, DEFAULT_DB_ALIAS
-from django.db.models import get_model
from django.db.models.fields import FieldDoesNotExist
from django.utils import six
from django.utils.translation import ugettext as _
@@ -20,6 +22,8 @@ def index(request, sitemaps):
This view generates a sitemap index that uses the proper view
for resolving geographic section sitemap URLs.
"""
+ warnings.warn("Geo Sitemaps are deprecated. Use plain sitemaps from "
+ "django.contrib.sitemaps instead", DeprecationWarning, stacklevel=2)
current_site = get_current_site(request)
sites = []
protocol = request.scheme
@@ -43,6 +47,8 @@ def sitemap(request, sitemaps, section=None):
This view generates a sitemap with additional geographic
elements defined by Google.
"""
+ warnings.warn("Geo Sitemaps are deprecated. Use plain sitemaps from "
+ "django.contrib.sitemaps instead", DeprecationWarning, stacklevel=2)
maps, urls = [], []
if section is not None:
if section not in sitemaps:
@@ -75,7 +81,7 @@ def kml(request, label, model, field_name=None, compress=False, using=DEFAULT_DB
must be that of a geographic field.
"""
placemarks = []
- klass = get_model(label, model)
+ klass = app_cache.get_model(label, model)
if not klass:
raise Http404('You must supply a valid app label and module name. Got "%s.%s"' % (label, model))
diff --git a/django/contrib/gis/tests/geo3d/tests.py b/django/contrib/gis/tests/geo3d/tests.py
index c294ca1790..917085013d 100644
--- a/django/contrib/gis/tests/geo3d/tests.py
+++ b/django/contrib/gis/tests/geo3d/tests.py
@@ -47,9 +47,9 @@ interstate_data = (
('I-45',
'LINESTRING(-95.3708481 29.7765870 11.339,-95.3694580 29.7787980 4.536,-95.3690305 29.7797359 9.762,-95.3691886 29.7812450 12.448,-95.3696447 29.7850144 10.457,-95.3702511 29.7868518 9.418,-95.3706724 29.7881286 14.858,-95.3711632 29.7896157 15.386,-95.3714525 29.7936267 13.168,-95.3717848 29.7955007 15.104,-95.3717719 29.7969804 16.516,-95.3717305 29.7982117 13.923,-95.3717254 29.8000778 14.385,-95.3719875 29.8013539 15.160,-95.3720575 29.8026785 15.544,-95.3721321 29.8040912 14.975,-95.3722074 29.8050998 15.688,-95.3722779 29.8060430 16.099,-95.3733818 29.8076750 15.197,-95.3741563 29.8103686 17.268,-95.3749458 29.8129927 19.857,-95.3763564 29.8144557 15.435)',
(11.339, 4.536, 9.762, 12.448, 10.457, 9.418, 14.858,
- 15.386, 13.168, 15.104, 16.516, 13.923, 14.385, 15.16,
- 15.544, 14.975, 15.688, 16.099, 15.197, 17.268, 19.857,
- 15.435),
+ 15.386, 13.168, 15.104, 16.516, 13.923, 14.385, 15.16,
+ 15.544, 14.975, 15.688, 16.099, 15.197, 17.268, 19.857,
+ 15.435),
),
)
diff --git a/django/contrib/gis/tests/geoapp/test_feeds.py b/django/contrib/gis/tests/geoapp/test_feeds.py
index 7b8fc159b4..0817e65cb4 100644
--- a/django/contrib/gis/tests/geoapp/test_feeds.py
+++ b/django/contrib/gis/tests/geoapp/test_feeds.py
@@ -20,11 +20,11 @@ class GeoFeedTest(TestCase):
def setUp(self):
Site(id=settings.SITE_ID, domain="example.com", name="example.com").save()
- self.old_Site_meta_installed = Site._meta.installed
- Site._meta.installed = True
+ self._old_installed = Site._meta.app_config.installed
+ Site._meta.app_config.installed = True
def tearDown(self):
- Site._meta.installed = self.old_Site_meta_installed
+ Site._meta.app_config.installed = self._old_installed
def assertChildNodes(self, elem, expected):
"Taken from syndication/tests.py."
diff --git a/django/contrib/gis/tests/geoapp/test_regress.py b/django/contrib/gis/tests/geoapp/test_regress.py
index 3bd74b00d1..5fd74ea997 100644
--- a/django/contrib/gis/tests/geoapp/test_regress.py
+++ b/django/contrib/gis/tests/geoapp/test_regress.py
@@ -33,10 +33,11 @@ class GeoRegressionTests(TestCase):
def test_kmz(self):
"Testing `render_to_kmz` with non-ASCII data. See #11624."
name = "Åland Islands"
- places = [{'name': name,
- 'description': name,
- 'kml': '5.0,23.0'
- }]
+ places = [{
+ 'name': name,
+ 'description': name,
+ 'kml': '5.0,23.0'
+ }]
render_to_kmz('gis/kml/placemarks.kml', {'places': places})
@no_spatialite
diff --git a/django/contrib/gis/tests/geoapp/test_sitemaps.py b/django/contrib/gis/tests/geoapp/test_sitemaps.py
index d9ede80c57..ee9974ceaa 100644
--- a/django/contrib/gis/tests/geoapp/test_sitemaps.py
+++ b/django/contrib/gis/tests/geoapp/test_sitemaps.py
@@ -11,6 +11,7 @@ from django.contrib.gis.geos import HAS_GEOS
from django.contrib.gis.tests.utils import HAS_SPATIAL_DB
from django.contrib.sites.models import Site
from django.test import TestCase
+from django.test.utils import IgnoreDeprecationWarningsMixin
from django.utils._os import upath
if HAS_GEOS:
@@ -18,17 +19,19 @@ if HAS_GEOS:
@skipUnless(HAS_GEOS and HAS_SPATIAL_DB, "Geos and spatial db are required.")
-class GeoSitemapTest(TestCase):
+class GeoSitemapTest(IgnoreDeprecationWarningsMixin, TestCase):
urls = 'django.contrib.gis.tests.geoapp.urls'
def setUp(self):
+ super(GeoSitemapTest, self).setUp()
Site(id=settings.SITE_ID, domain="example.com", name="example.com").save()
- self.old_Site_meta_installed = Site._meta.installed
- Site._meta.installed = True
+ self._old_installed = Site._meta.app_config.installed
+ Site._meta.app_config.installed = True
def tearDown(self):
- Site._meta.installed = self.old_Site_meta_installed
+ Site._meta.app_config.installed = self._old_installed
+ super(GeoSitemapTest, self).tearDown()
def assertChildNodes(self, elem, expected):
"Taken from syndication/tests.py."
diff --git a/django/contrib/gis/tests/test_geoforms.py b/django/contrib/gis/tests/test_geoforms.py
index ca28fb503c..2eb91ec527 100644
--- a/django/contrib/gis/tests/test_geoforms.py
+++ b/django/contrib/gis/tests/test_geoforms.py
@@ -76,6 +76,19 @@ class GeometryFieldTest(SimpleTestCase):
for wkt in ('POINT(5)', 'MULTI POLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)))', 'BLAH(0 0, 1 1)'):
self.assertRaises(forms.ValidationError, fld.to_python, wkt)
+ def test_field_with_text_widget(self):
+ class PointForm(forms.Form):
+ pt = forms.PointField(srid=4326, widget=forms.TextInput)
+
+ form = PointForm()
+ cleaned_pt = form.fields['pt'].clean('POINT(5 23)')
+ self.assertEqual(cleaned_pt, GEOSGeometry('POINT(5 23)'))
+ self.assertEqual(4326, cleaned_pt.srid)
+
+ point = GEOSGeometry('SRID=4326;POINT(5 23)')
+ form = PointForm(data={'pt': 'POINT(5 23)'}, initial={'pt': point})
+ self.assertFalse(form.has_changed())
+
@skipUnless(HAS_GDAL and HAS_SPATIALREFSYS,
"SpecializedFieldTest needs gdal support and a spatial database")
@@ -244,6 +257,15 @@ class SpecializedFieldTest(SimpleTestCase):
for invalid in [geo for key, geo in self.geometries.items() if key != 'geometrycollection']:
self.assertFalse(GeometryForm(data={'g': invalid.wkt}).is_valid())
+
+@skipUnless(HAS_GDAL and HAS_SPATIALREFSYS,
+ "OSMWidgetTest needs gdal support and a spatial database")
+class OSMWidgetTest(SimpleTestCase):
+ def setUp(self):
+ self.geometries = {
+ 'point': GEOSGeometry("SRID=4326;POINT(9.052734375 42.451171875)"),
+ }
+
def test_osm_widget(self):
class PointForm(forms.Form):
p = forms.PointField(widget=forms.OSMWidget)
@@ -251,9 +273,32 @@ class SpecializedFieldTest(SimpleTestCase):
geom = self.geometries['point']
form = PointForm(data={'p': geom})
rendered = form.as_p()
+
self.assertIn("OpenStreetMap (Mapnik)", rendered)
self.assertIn("id: 'id_p',", rendered)
+ def test_default_lat_lon(self):
+ class PointForm(forms.Form):
+ p = forms.PointField(
+ widget=forms.OSMWidget(attrs={
+ 'default_lon': 20, 'default_lat': 30
+ }),
+ )
+
+ form = PointForm()
+ rendered = form.as_p()
+
+ self.assertIn("options['default_lon'] = 20;", rendered)
+ self.assertIn("options['default_lat'] = 30;", rendered)
+ if forms.OSMWidget.default_lon != 20:
+ self.assertNotIn(
+ "options['default_lon'] = %d;" % forms.OSMWidget.default_lon,
+ rendered)
+ if forms.OSMWidget.default_lat != 30:
+ self.assertNotIn(
+ "options['default_lat'] = %d;" % forms.OSMWidget.default_lat,
+ rendered)
+
@skipUnless(HAS_GDAL and HAS_SPATIALREFSYS,
"CustomGeometryWidgetTest needs gdal support and a spatial database")
diff --git a/django/contrib/gis/tests/test_spatialrefsys.py b/django/contrib/gis/tests/test_spatialrefsys.py
index 194578d1e0..2bbee70a47 100644
--- a/django/contrib/gis/tests/test_spatialrefsys.py
+++ b/django/contrib/gis/tests/test_spatialrefsys.py
@@ -12,7 +12,7 @@ test_srs = ({'srid': 4326,
# Only the beginning, because there are differences depending on installed libs
'srtext': 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84"',
# +ellps=WGS84 has been removed in the 4326 proj string in proj-4.8
- 'proj4_re': r'\+proj=longlat (\+ellps=WGS84 )?\+datum=WGS84 \+no_defs ',
+ 'proj4_re': r'\+proj=longlat (\+ellps=WGS84 )?(\+datum=WGS84 |\+towgs84=0,0,0,0,0,0,0 )\+no_defs ',
'spheroid': 'WGS 84', 'name': 'WGS 84',
'geographic': True, 'projected': False, 'spatialite': True,
'ellipsoid': (6378137.0, 6356752.3, 298.257223563), # From proj's "cs2cs -le" and Wikipedia (semi-minor only)
@@ -23,8 +23,8 @@ test_srs = ({'srid': 4326,
'auth_srid': 32140,
'srtext': 'PROJCS["NAD83 / Texas South Central",GEOGCS["NAD83",DATUM["North_American_Datum_1983",SPHEROID["GRS 1980"',
'proj4_re': r'\+proj=lcc \+lat_1=30.28333333333333 \+lat_2=28.38333333333333 \+lat_0=27.83333333333333 '
- r'\+lon_0=-99 \+x_0=600000 \+y_0=4000000 (\+ellps=GRS80 )?'
- r'(\+datum=NAD83 |\+towgs84=0,0,0,0,0,0,0 )?\+units=m \+no_defs ',
+ r'\+lon_0=-99 \+x_0=600000 \+y_0=4000000 (\+ellps=GRS80 )?'
+ r'(\+datum=NAD83 |\+towgs84=0,0,0,0,0,0,0 )?\+units=m \+no_defs ',
'spheroid': 'GRS 1980', 'name': 'NAD83 / Texas South Central',
'geographic': False, 'projected': True, 'spatialite': False,
'ellipsoid': (6378137.0, 6356752.31414, 298.257222101), # From proj's "cs2cs -le" and Wikipedia (semi-minor only)
diff --git a/django/contrib/gis/utils/layermapping.py b/django/contrib/gis/utils/layermapping.py
index 3ba087b967..c834cb03b0 100644
--- a/django/contrib/gis/utils/layermapping.py
+++ b/django/contrib/gis/utils/layermapping.py
@@ -339,7 +339,7 @@ class LayerMapping(object):
otherwise the proper exception is raised.
"""
if (isinstance(ogr_field, OFTString) and
- isinstance(model_field, (models.CharField, models.TextField))):
+ isinstance(model_field, (models.CharField, models.TextField))):
if self.encoding:
# The encoding for OGR data sources may be specified here
# (e.g., 'cp437' for Census Bureau boundary files).
diff --git a/django/contrib/humanize/tests.py b/django/contrib/humanize/tests.py
index d2bb4c034e..0f31beb81f 100644
--- a/django/contrib/humanize/tests.py
+++ b/django/contrib/humanize/tests.py
@@ -28,7 +28,7 @@ now = datetime.datetime(2012, 3, 9, 22, 30)
class MockDateTime(datetime.datetime):
@classmethod
- def now(self, tz=None):
+ def now(cls, tz=None):
if tz is None or tz.utcoffset(now) is None:
return now
else:
diff --git a/django/contrib/messages/models.py b/django/contrib/messages/models.py
deleted file mode 100644
index 4f656f5082..0000000000
--- a/django/contrib/messages/models.py
+++ /dev/null
@@ -1 +0,0 @@
-# Models module required so tests are discovered.
diff --git a/django/contrib/messages/storage/cookie.py b/django/contrib/messages/storage/cookie.py
index d0d09dee48..a318002127 100644
--- a/django/contrib/messages/storage/cookie.py
+++ b/django/contrib/messages/storage/cookie.py
@@ -39,7 +39,7 @@ class MessageDecoder(json.JSONDecoder):
return [self.process_messages(item) for item in obj]
if isinstance(obj, dict):
return dict((key, self.process_messages(value))
- for key, value in six.iteritems(obj))
+ for key, value in six.iteritems(obj))
return obj
def decode(self, s, **kwargs):
diff --git a/django/contrib/messages/tests/base.py b/django/contrib/messages/tests/base.py
index 1b1a9c0048..f3e676fac5 100644
--- a/django/contrib/messages/tests/base.py
+++ b/django/contrib/messages/tests/base.py
@@ -62,7 +62,7 @@ class BaseTests(object):
TEMPLATE_CONTEXT_PROCESSORS=global_settings.TEMPLATE_CONTEXT_PROCESSORS,
MESSAGE_TAGS='',
MESSAGE_STORAGE='%s.%s' % (self.storage_class.__module__,
- self.storage_class.__name__),
+ self.storage_class.__name__),
SESSION_SERIALIZER='django.contrib.sessions.serializers.JSONSerializer',
)
self.settings_override.enable()
@@ -164,8 +164,7 @@ class BaseTests(object):
response = self.client.post(add_url, data, follow=True)
self.assertRedirects(response, show_url)
self.assertTrue('messages' in response.context)
- messages = [Message(self.levels[level], msg) for msg in
- data['messages']]
+ messages = [Message(self.levels[level], msg) for msg in data['messages']]
self.assertEqual(list(response.context['messages']), messages)
for msg in data['messages']:
self.assertContains(response, msg)
@@ -209,8 +208,7 @@ class BaseTests(object):
show_url = reverse('django.contrib.messages.tests.urls.show')
messages = []
for level in ('debug', 'info', 'success', 'warning', 'error'):
- messages.extend([Message(self.levels[level], msg) for msg in
- data['messages']])
+ messages.extend([Message(self.levels[level], msg) for msg in data['messages']])
add_url = reverse('django.contrib.messages.tests.urls.add',
args=(level,))
self.client.post(add_url, data)
@@ -285,7 +283,7 @@ class BaseTests(object):
def get_existing_storage(self):
return self.get_storage([Message(constants.INFO, 'Test message 1'),
Message(constants.INFO, 'Test message 2',
- extra_tags='tag')])
+ extra_tags='tag')])
def test_existing_read(self):
"""
diff --git a/django/contrib/sessions/tests.py b/django/contrib/sessions/tests.py
index af89f0048e..51a65a1fef 100644
--- a/django/contrib/sessions/tests.py
+++ b/django/contrib/sessions/tests.py
@@ -141,7 +141,7 @@ class SessionTestsMixin(object):
def test_save(self):
if (hasattr(self.session, '_cache') and 'DummyCache' in
- settings.CACHES[settings.SESSION_CACHE_ALIAS]['BACKEND']):
+ settings.CACHES[settings.SESSION_CACHE_ALIAS]['BACKEND']):
raise unittest.SkipTest("Session saving tests require a real cache backend")
self.session.save()
self.assertTrue(self.session.exists(self.session.session_key))
@@ -446,7 +446,7 @@ class FileSessionTests(SessionTestsMixin, unittest.TestCase):
def count_sessions():
return len([session_file for session_file in os.listdir(storage_path)
- if session_file.startswith(file_prefix)])
+ if session_file.startswith(file_prefix)])
self.assertEqual(0, count_sessions())
diff --git a/django/contrib/sitemaps/__init__.py b/django/contrib/sitemaps/__init__.py
index db31043bfa..4396708098 100644
--- a/django/contrib/sitemaps/__init__.py
+++ b/django/contrib/sitemaps/__init__.py
@@ -94,7 +94,7 @@ class Sitemap(object):
if all_items_lastmod:
all_items_lastmod = lastmod is not None
if (all_items_lastmod and
- (latest_lastmod is None or lastmod > latest_lastmod)):
+ (latest_lastmod is None or lastmod > latest_lastmod)):
latest_lastmod = lastmod
url_info = {
'item': item,
diff --git a/django/contrib/sitemaps/models.py b/django/contrib/sitemaps/models.py
index 6e7633bfe0..9ade3254cc 100644
--- a/django/contrib/sitemaps/models.py
+++ b/django/contrib/sitemaps/models.py
@@ -1 +1,2 @@
-# This file intentionally left blank
+# This file is required to pretend sitemaps has models.
+# Otherwise test models cannot be registered.
diff --git a/django/contrib/sitemaps/tests/base.py b/django/contrib/sitemaps/tests/base.py
index 8e027d491e..98abb3dca4 100644
--- a/django/contrib/sitemaps/tests/base.py
+++ b/django/contrib/sitemaps/tests/base.py
@@ -25,10 +25,10 @@ class SitemapTestsBase(TestCase):
def setUp(self):
self.base_url = '%s://%s' % (self.protocol, self.domain)
- self.old_Site_meta_installed = Site._meta.installed
+ self._old_installed = Site._meta.app_config.installed
cache.clear()
# Create an object for sitemap content.
TestModel.objects.create(name='Test Object')
def tearDown(self):
- Site._meta.installed = self.old_Site_meta_installed
+ Site._meta.app_config.installed = self._old_installed
diff --git a/django/contrib/sitemaps/tests/test_http.py b/django/contrib/sitemaps/tests/test_http.py
index fc6660af0a..61be8c842a 100644
--- a/django/contrib/sitemaps/tests/test_http.py
+++ b/django/contrib/sitemaps/tests/test_http.py
@@ -107,8 +107,9 @@ class HTTPSitemapTests(SitemapTestsBase):
def test_requestsite_sitemap(self):
# Make sure hitting the flatpages sitemap without the sites framework
- # installed doesn't raise an exception
- Site._meta.installed = False
+ # installed doesn't raise an exception.
+ # Reset by SitemapTestsBase.tearDown().
+ Site._meta.app_config.installed = False
response = self.client.get('/simple/sitemap.xml')
expected_content = """
@@ -133,7 +134,8 @@ class HTTPSitemapTests(SitemapTestsBase):
Sitemap.get_urls if Site objects exists, but the sites framework is not
actually installed.
"""
- Site._meta.installed = False
+ # Reset by SitemapTestsBase.tearDown().
+ Site._meta.app_config.installed = False
self.assertRaises(ImproperlyConfigured, Sitemap().get_urls)
def test_sitemap_item(self):
diff --git a/django/contrib/sites/tests.py b/django/contrib/sites/tests.py
index 6bfbfd7cf2..2be28a1429 100644
--- a/django/contrib/sites/tests.py
+++ b/django/contrib/sites/tests.py
@@ -12,11 +12,11 @@ class SitesFrameworkTests(TestCase):
def setUp(self):
Site(id=settings.SITE_ID, domain="example.com", name="example.com").save()
- self.old_Site_meta_installed = Site._meta.installed
- Site._meta.installed = True
+ self._old_installed = Site._meta.app_config.installed
+ Site._meta.app_config.installed = True
def tearDown(self):
- Site._meta.installed = self.old_Site_meta_installed
+ Site._meta.app_config.installed = self._old_installed
def test_save_another(self):
# Regression for #17415
@@ -67,7 +67,7 @@ class SitesFrameworkTests(TestCase):
self.assertRaises(ObjectDoesNotExist, get_current_site, request)
# A RequestSite is returned if the sites framework is not installed
- Site._meta.installed = False
+ Site._meta.app_config.installed = False
site = get_current_site(request)
self.assertTrue(isinstance(site, RequestSite))
self.assertEqual(site.name, "example.com")
diff --git a/django/contrib/staticfiles/handlers.py b/django/contrib/staticfiles/handlers.py
index 3fe0c9da26..bb54db6b7d 100644
--- a/django/contrib/staticfiles/handlers.py
+++ b/django/contrib/staticfiles/handlers.py
@@ -12,18 +12,11 @@ class StaticFilesHandler(WSGIHandler):
WSGI middleware that intercepts calls to the static files directory, as
defined by the STATIC_URL setting, and serves those files.
"""
- def __init__(self, application, base_dir=None):
+ def __init__(self, application):
self.application = application
- if base_dir:
- self.base_dir = base_dir
- else:
- self.base_dir = self.get_base_dir()
self.base_url = urlparse(self.get_base_url())
super(StaticFilesHandler, self).__init__()
- def get_base_dir(self):
- return settings.STATIC_ROOT
-
def get_base_url(self):
utils.check_settings()
return settings.STATIC_URL
diff --git a/django/contrib/staticfiles/management/commands/collectstatic.py b/django/contrib/staticfiles/management/commands/collectstatic.py
index c1e9fa811b..67fa6229c4 100644
--- a/django/contrib/staticfiles/management/commands/collectstatic.py
+++ b/django/contrib/staticfiles/management/commands/collectstatic.py
@@ -294,12 +294,6 @@ Type 'yes' to continue, or 'no' to cancel: """
self.log("Pretending to copy '%s'" % source_path, level=1)
else:
self.log("Copying '%s'" % source_path, level=1)
- if self.local:
- full_path = self.storage.path(prefixed_path)
- try:
- os.makedirs(os.path.dirname(full_path))
- except OSError:
- pass
with source_storage.open(path) as source_file:
self.storage.save(prefixed_path, source_file)
if not prefixed_path in self.copied_files:
diff --git a/django/core/apps/__init__.py b/django/core/apps/__init__.py
new file mode 100644
index 0000000000..0384b1257d
--- /dev/null
+++ b/django/core/apps/__init__.py
@@ -0,0 +1 @@
+from .cache import app_cache, UnavailableApp # NOQA
diff --git a/django/core/apps/base.py b/django/core/apps/base.py
new file mode 100644
index 0000000000..860777bb03
--- /dev/null
+++ b/django/core/apps/base.py
@@ -0,0 +1,47 @@
+from collections import OrderedDict
+
+from django.utils._os import upath
+
+
+class AppConfig(object):
+ """
+ Class representing a Django application and its configuration.
+ """
+
+ def __init__(self, name, app_module, models_module):
+ # Full Python path to the application eg. 'django.contrib.admin'.
+ # This is the value that appears in INSTALLED_APPS.
+ self.name = name
+
+ # Last component of the Python path to the application eg. 'admin'.
+ # This value must be unique across a Django project.
+ self.label = name.rpartition(".")[2]
+
+ # Root module eg. .
+ self.app_module = app_module
+
+ # Module containing models eg. . None if the application
+ # doesn't have a models module.
+ self.models_module = models_module
+
+ # Mapping of lower case model names to model classes.
+ # Populated by calls to AppCache.register_model().
+ self.models = OrderedDict()
+
+ # Whether the app is in INSTALLED_APPS or was automatically created
+ # when one of its models was imported.
+ self.installed = app_module is not None
+
+ # Filesystem path to the application directory eg.
+ # u'/usr/lib/python2.7/dist-packages/django/contrib/admin'.
+ # This is a unicode object on Python 2 and a str on Python 3.
+ self.path = upath(app_module.__path__[0]) if app_module is not None else None
+
+ @classmethod
+ def _stub(cls, label):
+ return cls(label, None, None)
+
+ def __repr__(self):
+ return '' % self.label
diff --git a/django/core/apps/cache.py b/django/core/apps/cache.py
new file mode 100644
index 0000000000..b076fc1968
--- /dev/null
+++ b/django/core/apps/cache.py
@@ -0,0 +1,395 @@
+"Utilities for loading models and the modules that contain them."
+
+from collections import OrderedDict
+import imp
+from importlib import import_module
+import os
+import sys
+import warnings
+
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+from django.utils.module_loading import module_has_submodule
+from django.utils._os import upath
+from django.utils import six
+
+from .base import AppConfig
+
+
+MODELS_MODULE_NAME = 'models'
+
+
+class UnavailableApp(Exception):
+ pass
+
+
+class AppCache(object):
+ """
+ A cache that stores installed applications and their models. Used to
+ provide reverse-relations and for app introspection.
+ """
+
+ def __init__(self, master=False):
+ # Only one master of the app-cache may exist at a given time, and it
+ # shall be the app_cache variable defined at the end of this module.
+ if master and hasattr(sys.modules[__name__], 'app_cache'):
+ raise RuntimeError("You may create only one master app cache.")
+
+ # When master is set to False, the app cache isn't populated from
+ # INSTALLED_APPS and ignores the only_installed arguments to
+ # get_model[s].
+ self.master = master
+
+ # Mapping of labels to AppConfig instances for installed apps.
+ self.app_configs = OrderedDict()
+
+ # Pending lookups for lazy relations
+ self.pending_lookups = {}
+
+ # Set of app names. Allows restricting the set of installed apps.
+ # Used by TransactionTestCase.available_apps for performance reasons.
+ self.available_apps = None
+
+ # -- Everything below here is only used when populating the cache --
+ self.loaded = False
+ self.handled = set()
+ self.postponed = []
+ self.nesting_level = 0
+ self._get_models_cache = {}
+
+ def populate(self):
+ """
+ Fill in all the cache information. This method is threadsafe, in the
+ sense that every caller will see the same state upon return, and if the
+ cache is already initialised, it does no work.
+ """
+ if self.loaded:
+ return
+ if not self.master:
+ self.loaded = True
+ return
+ # Note that we want to use the import lock here - the app loading is
+ # in many cases initiated implicitly by importing, and thus it is
+ # possible to end up in deadlock when one thread initiates loading
+ # without holding the importer lock and another thread then tries to
+ # import something which also launches the app loading. For details of
+ # this situation see #18251.
+ imp.acquire_lock()
+ try:
+ if self.loaded:
+ return
+ for app_name in settings.INSTALLED_APPS:
+ if app_name in self.handled:
+ continue
+ self.load_app(app_name, can_postpone=True)
+ if not self.nesting_level:
+ for app_name in self.postponed:
+ self.load_app(app_name)
+ self.loaded = True
+ finally:
+ imp.release_lock()
+
+ def load_app(self, app_name, can_postpone=False):
+ """
+ Loads the app with the provided fully qualified name, and returns the
+ model module.
+ """
+ app_module = import_module(app_name)
+ self.handled.add(app_name)
+ self.nesting_level += 1
+ try:
+ models_module = import_module('%s.%s' % (app_name, MODELS_MODULE_NAME))
+ except ImportError:
+ # If the app doesn't have a models module, we can just swallow the
+ # ImportError and return no models for this app.
+ if not module_has_submodule(app_module, MODELS_MODULE_NAME):
+ models_module = None
+ # But if the app does have a models module, we need to figure out
+ # whether to suppress or propagate the error. If can_postpone is
+ # True then it may be that the package is still being imported by
+ # Python and the models module isn't available yet. So we add the
+ # app to the postponed list and we'll try it again after all the
+ # recursion has finished (in populate). If can_postpone is False
+ # then it's time to raise the ImportError.
+ else:
+ if can_postpone:
+ self.postponed.append(app_name)
+ return
+ else:
+ raise
+ finally:
+ self.nesting_level -= 1
+
+ app_config = AppConfig(
+ name=app_name, app_module=app_module, models_module=models_module)
+ # If a stub config existed for this app, preserve models registry.
+ old_app_config = self.app_configs.get(app_config.label)
+ if old_app_config is not None:
+ app_config.models = old_app_config.models
+ self.app_configs[app_config.label] = app_config
+
+ return models_module
+
+ def app_cache_ready(self):
+ """
+ Returns true if the model cache is fully populated.
+
+ Useful for code that wants to cache the results of get_models() for
+ themselves once it is safe to do so.
+ """
+ return self.loaded
+
+ def get_app_configs(self, only_installed=True, only_with_models_module=False):
+ """
+ Return an iterable of application configurations.
+
+ If only_installed is True (default), only applications explicitly
+ listed in INSTALLED_APPS are considered.
+
+ If only_with_models_module in True (non-default), only applications
+ containing a models module are considered.
+ """
+ self.populate()
+ for app_config in self.app_configs.values():
+ if only_installed and not app_config.installed:
+ continue
+ if only_with_models_module and app_config.models_module is None:
+ continue
+ if self.available_apps is not None and app_config.name not in self.available_apps:
+ continue
+ yield app_config
+
+ def get_app_config(self, app_label, only_installed=True, only_with_models_module=False):
+ """
+ Returns the application configuration for the given app_label.
+
+ Raises LookupError if no application exists with this app_label.
+
+ Raises UnavailableApp when set_available_apps() disables the
+ application with this app_label.
+
+ If only_installed is True (default), only applications explicitly
+ listed in INSTALLED_APPS are considered.
+
+ If only_with_models_module in True (non-default), only applications
+ containing a models module are considered.
+ """
+ self.populate()
+ app_config = self.app_configs.get(app_label)
+ if app_config is None:
+ raise LookupError("No app with label %r." % app_label)
+ if only_installed and not app_config.installed:
+ raise LookupError("App with label %r isn't in INSTALLED_APPS." % app_label)
+ if only_with_models_module and app_config.models_module is None:
+ raise LookupError("App with label %r doesn't have a models module." % app_label)
+ if self.available_apps is not None and app_config.name not in self.available_apps:
+ raise UnavailableApp("App with label %r isn't available." % app_label)
+ return app_config
+
+ def get_models(self, app_mod=None,
+ include_auto_created=False, include_deferred=False,
+ only_installed=True, include_swapped=False):
+ """
+ Given a module containing models, returns a list of the models.
+ Otherwise returns a list of all installed models.
+
+ By default, auto-created models (i.e., m2m models without an
+ explicit intermediate table) are not included. However, if you
+ specify include_auto_created=True, they will be.
+
+ By default, models created to satisfy deferred attribute
+ queries are *not* included in the list of models. However, if
+ you specify include_deferred, they will be.
+
+ By default, models that aren't part of installed apps will *not*
+ be included in the list of models. However, if you specify
+ only_installed=False, they will be. If you're using a non-default
+ AppCache, this argument does nothing - all models will be included.
+
+ By default, models that have been swapped out will *not* be
+ included in the list of models. However, if you specify
+ include_swapped, they will be.
+ """
+ if not self.master:
+ only_installed = False
+ cache_key = (app_mod, include_auto_created, include_deferred, only_installed, include_swapped)
+ model_list = None
+ try:
+ model_list = self._get_models_cache[cache_key]
+ if self.available_apps is not None and only_installed:
+ model_list = [
+ m for m in model_list
+ if self.app_configs[m._meta.app_label].name in self.available_apps
+ ]
+ return model_list
+ except KeyError:
+ pass
+ self.populate()
+ if app_mod:
+ app_label = app_mod.__name__.split('.')[-2]
+ try:
+ app_config = self.app_configs[app_label]
+ except KeyError:
+ app_list = []
+ else:
+ app_list = [app_config] if app_config.installed else []
+ else:
+ app_list = six.itervalues(self.app_configs)
+ if only_installed:
+ app_list = (app for app in app_list if app.installed)
+ model_list = []
+ for app in app_list:
+ model_list.extend(
+ model for model in app.models.values()
+ if ((not model._deferred or include_deferred) and
+ (not model._meta.auto_created or include_auto_created) and
+ (not model._meta.swapped or include_swapped))
+ )
+ self._get_models_cache[cache_key] = model_list
+ if self.available_apps is not None and only_installed:
+ model_list = [
+ m for m in model_list
+ if self.app_configs[m._meta.app_label].name in self.available_apps
+ ]
+ return model_list
+
+ def get_model(self, app_label, model_name,
+ seed_cache=True, only_installed=True):
+ """
+ Returns the model matching the given app_label and case-insensitive
+ model_name.
+
+ Returns None if no model is found.
+
+ Raises UnavailableApp when set_available_apps() in in effect and
+ doesn't include app_label.
+ """
+ if not self.master:
+ only_installed = False
+ if seed_cache:
+ self.populate()
+ if only_installed:
+ app_config = self.app_configs.get(app_label)
+ if app_config is not None and not app_config.installed:
+ return None
+ if (self.available_apps is not None
+ and app_config.name not in self.available_apps):
+ raise UnavailableApp("App with label %s isn't available." % app_label)
+ try:
+ return self.app_configs[app_label].models[model_name.lower()]
+ except KeyError:
+ return None
+
+ def register_model(self, app_label, model):
+ try:
+ app_config = self.app_configs[app_label]
+ except KeyError:
+ app_config = AppConfig._stub(app_label)
+ self.app_configs[app_label] = app_config
+ # Add the model to the app_config's models dictionary.
+ model_name = model._meta.model_name
+ model_dict = app_config.models
+ if model_name in model_dict:
+ # The same model may be imported via different paths (e.g.
+ # appname.models and project.appname.models). We use the source
+ # filename as a means to detect identity.
+ fname1 = os.path.abspath(upath(sys.modules[model.__module__].__file__))
+ fname2 = os.path.abspath(upath(sys.modules[model_dict[model_name].__module__].__file__))
+ # Since the filename extension could be .py the first time and
+ # .pyc or .pyo the second time, ignore the extension when
+ # comparing.
+ if os.path.splitext(fname1)[0] == os.path.splitext(fname2)[0]:
+ return
+ model_dict[model_name] = model
+ self._get_models_cache.clear()
+
+ def set_available_apps(self, available):
+ available = set(available)
+ installed = set(settings.INSTALLED_APPS)
+ if not available.issubset(installed):
+ raise ValueError("Available apps isn't a subset of installed "
+ "apps, extra apps: %s" % ", ".join(available - installed))
+ self.available_apps = available
+
+ def unset_available_apps(self):
+ self.available_apps = None
+
+ ### DEPRECATED METHODS GO BELOW THIS LINE ###
+
+ def get_app(self, app_label):
+ """
+ Returns the module containing the models for the given app_label.
+
+ Raises UnavailableApp when set_available_apps() in in effect and
+ doesn't include app_label.
+ """
+ warnings.warn(
+ "get_app_config(app_label).models_module supersedes get_app(app_label).",
+ PendingDeprecationWarning, stacklevel=2)
+ try:
+ return self.get_app_config(app_label).models_module
+ except LookupError as exc:
+ # Change the exception type for backwards compatibility.
+ raise ImproperlyConfigured(*exc.args)
+
+ def get_apps(self):
+ """
+ Returns a list of all installed modules that contain models.
+ """
+ warnings.warn(
+ "[a.models_module for a in get_app_configs()] supersedes get_apps().",
+ PendingDeprecationWarning, stacklevel=2)
+ return [app_config.models_module for app_config in self.get_app_configs()]
+
+ def _get_app_package(self, app):
+ return '.'.join(app.__name__.split('.')[:-1])
+
+ def get_app_package(self, app_label):
+ warnings.warn(
+ "get_app_config(label).name supersedes get_app_package(label).",
+ PendingDeprecationWarning, stacklevel=2)
+ return self._get_app_package(self.get_app(app_label))
+
+ def _get_app_path(self, app):
+ if hasattr(app, '__path__'): # models/__init__.py package
+ app_path = app.__path__[0]
+ else: # models.py module
+ app_path = app.__file__
+ return os.path.dirname(upath(app_path))
+
+ def get_app_path(self, app_label):
+ warnings.warn(
+ "get_app_config(label).path supersedes get_app_path(label).",
+ PendingDeprecationWarning, stacklevel=2)
+ return self._get_app_path(self.get_app(app_label))
+
+ def get_app_paths(self):
+ """
+ Returns a list of paths to all installed apps.
+
+ Useful for discovering files at conventional locations inside apps
+ (static files, templates, etc.)
+ """
+ warnings.warn(
+ "[a.path for a in get_app_configs()] supersedes get_app_paths().",
+ PendingDeprecationWarning, stacklevel=2)
+
+ self.populate()
+
+ app_paths = []
+ for app in self.get_apps():
+ app_paths.append(self._get_app_path(app))
+ return app_paths
+
+ def register_models(self, app_label, *models):
+ """
+ Register a set of models as belonging to an app.
+ """
+ warnings.warn(
+ "register_models(app_label, models) is deprecated.",
+ PendingDeprecationWarning, stacklevel=2)
+ for model in models:
+ self.register_model(app_label, model)
+
+
+app_cache = AppCache(master=True)
diff --git a/django/core/checks/compatibility/django_1_6_0.py b/django/core/checks/compatibility/django_1_6_0.py
index a00196f2c0..fef8bc3a51 100644
--- a/django/core/checks/compatibility/django_1_6_0.py
+++ b/django/core/checks/compatibility/django_1_6_0.py
@@ -1,5 +1,6 @@
from __future__ import unicode_literals
+from django.core.apps import app_cache
from django.db import models
@@ -31,7 +32,7 @@ def check_boolean_field_default_value():
warns the user that the default has changed from False to Null.
"""
fields = []
- for cls in models.get_models():
+ for cls in app_cache.get_models():
opts = cls._meta
for f in opts.local_fields:
if isinstance(f, models.BooleanField) and not f.has_default():
diff --git a/django/core/exceptions.py b/django/core/exceptions.py
index efec22850b..cf4aea0f36 100644
--- a/django/core/exceptions.py
+++ b/django/core/exceptions.py
@@ -4,6 +4,7 @@ Global Django exception and warning classes.
from functools import reduce
import operator
+from django.utils import six
from django.utils.encoding import force_text
@@ -77,64 +78,89 @@ class ValidationError(Exception):
"""An error while validating data."""
def __init__(self, message, code=None, params=None):
"""
- ValidationError can be passed any object that can be printed (usually
- a string), a list of objects or a dictionary.
+ The `message` argument can be a single error, a list of errors, or a
+ dictionary that maps field names to lists of errors. What we define as
+ an "error" can be either a simple string or an instance of
+ ValidationError with its message attribute set, and what we define as
+ list or dictionary can be an actual `list` or `dict` or an instance
+ of ValidationError with its `error_list` or `error_dict` attribute set.
"""
+
+ # PY2 can't pickle naive exception: http://bugs.python.org/issue1692335.
+ super(ValidationError, self).__init__(message, code, params)
+
+ if isinstance(message, ValidationError):
+ if hasattr(message, 'error_dict'):
+ message = message.error_dict
+ # PY2 has a `message` property which is always there so we can't
+ # duck-type on it. It was introduced in Python 2.5 and already
+ # deprecated in Python 2.6.
+ elif not hasattr(message, 'message' if six.PY3 else 'code'):
+ message = message.error_list
+ else:
+ message, code, params = message.message, message.code, message.params
+
if isinstance(message, dict):
- self.error_dict = message
+ self.error_dict = {}
+ for field, messages in message.items():
+ if not isinstance(messages, ValidationError):
+ messages = ValidationError(messages)
+ self.error_dict[field] = messages.error_list
+
elif isinstance(message, list):
- self.error_list = message
+ self.error_list = []
+ for message in message:
+ # Normalize plain strings to instances of ValidationError.
+ if not isinstance(message, ValidationError):
+ message = ValidationError(message)
+ self.error_list.extend(message.error_list)
+
else:
+ self.message = message
self.code = code
self.params = params
- self.message = message
self.error_list = [self]
@property
def message_dict(self):
- message_dict = {}
- for field, messages in self.error_dict.items():
- message_dict[field] = []
- for message in messages:
- if isinstance(message, ValidationError):
- message_dict[field].extend(message.messages)
- else:
- message_dict[field].append(force_text(message))
- return message_dict
+ # Trigger an AttributeError if this ValidationError
+ # doesn't have an error_dict.
+ getattr(self, 'error_dict')
+
+ return dict(self)
@property
def messages(self):
if hasattr(self, 'error_dict'):
- message_list = reduce(operator.add, self.error_dict.values())
- else:
- message_list = self.error_list
-
- messages = []
- for message in message_list:
- if isinstance(message, ValidationError):
- params = message.params
- message = message.message
- if params:
- message %= params
- message = force_text(message)
- messages.append(message)
- return messages
-
- def __str__(self):
- if hasattr(self, 'error_dict'):
- return repr(self.message_dict)
- return repr(self.messages)
-
- def __repr__(self):
- return 'ValidationError(%s)' % self
+ return reduce(operator.add, dict(self).values())
+ return list(self)
def update_error_dict(self, error_dict):
if hasattr(self, 'error_dict'):
if error_dict:
- for k, v in self.error_dict.items():
- error_dict.setdefault(k, []).extend(v)
+ for field, errors in self.error_dict.items():
+ error_dict.setdefault(field, []).extend(errors)
else:
error_dict = self.error_dict
else:
error_dict[NON_FIELD_ERRORS] = self.error_list
return error_dict
+
+ def __iter__(self):
+ if hasattr(self, 'error_dict'):
+ for field, errors in self.error_dict.items():
+ yield field, list(ValidationError(errors))
+ else:
+ for error in self.error_list:
+ message = error.message
+ if error.params:
+ message %= error.params
+ yield force_text(message)
+
+ def __str__(self):
+ if hasattr(self, 'error_dict'):
+ return repr(dict(self))
+ return repr(list(self))
+
+ def __repr__(self):
+ return 'ValidationError(%s)' % self
diff --git a/django/core/files/move.py b/django/core/files/move.py
index 8aee5afaa6..902cd1fada 100644
--- a/django/core/files/move.py
+++ b/django/core/files/move.py
@@ -68,8 +68,8 @@ def file_move_safe(old_file_name, new_file_name, chunk_size=1024 * 64, allow_ove
# first open the old file, so that it won't go away
with open(old_file_name, 'rb') as old_file:
# now open the new file, not forgetting allow_overwrite
- fd = os.open(new_file_name, os.O_WRONLY | os.O_CREAT | getattr(os, 'O_BINARY', 0) |
- (os.O_EXCL if not allow_overwrite else 0))
+ fd = os.open(new_file_name, (os.O_WRONLY | os.O_CREAT | getattr(os, 'O_BINARY', 0) |
+ (os.O_EXCL if not allow_overwrite else 0)))
try:
locks.lock(fd, locks.LOCK_EX)
current_chunk = None
diff --git a/django/core/files/storage.py b/django/core/files/storage.py
index aae3d7e984..e8db352d3e 100644
--- a/django/core/files/storage.py
+++ b/django/core/files/storage.py
@@ -149,7 +149,8 @@ class FileSystemStorage(Storage):
Standard filesystem storage
"""
- def __init__(self, location=None, base_url=None, file_permissions_mode=None):
+ def __init__(self, location=None, base_url=None, file_permissions_mode=None,
+ directory_permissions_mode=None):
if location is None:
location = settings.MEDIA_ROOT
self.base_location = location
@@ -161,6 +162,10 @@ class FileSystemStorage(Storage):
file_permissions_mode if file_permissions_mode is not None
else settings.FILE_UPLOAD_PERMISSIONS
)
+ self.directory_permissions_mode = (
+ directory_permissions_mode if directory_permissions_mode is not None
+ else settings.FILE_UPLOAD_DIRECTORY_PERMISSIONS
+ )
def _open(self, name, mode='rb'):
return File(open(self.path(name), mode))
@@ -175,12 +180,12 @@ class FileSystemStorage(Storage):
directory = os.path.dirname(full_path)
if not os.path.exists(directory):
try:
- if settings.FILE_UPLOAD_DIRECTORY_PERMISSIONS is not None:
+ if self.directory_permissions_mode is not None:
# os.makedirs applies the global umask, so we reset it,
- # for consistency with FILE_UPLOAD_PERMISSIONS behavior.
+ # for consistency with file_permissions_mode behavior.
old_umask = os.umask(0)
try:
- os.makedirs(directory, settings.FILE_UPLOAD_DIRECTORY_PERMISSIONS)
+ os.makedirs(directory, self.directory_permissions_mode)
finally:
os.umask(old_umask)
else:
diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py
index e24193c52b..0b47e179db 100644
--- a/django/core/management/__init__.py
+++ b/django/core/management/__init__.py
@@ -118,8 +118,7 @@ def get_commands():
for app_name in apps:
try:
path = find_management_module(app_name)
- _commands.update(dict((name, app_name)
- for name in find_commands(path)))
+ _commands.update(dict((name, app_name) for name in find_commands(path)))
except ImportError:
pass # No management module - ignore this app
diff --git a/django/core/management/base.py b/django/core/management/base.py
index ef967d021f..aeb27e1f0b 100644
--- a/django/core/management/base.py
+++ b/django/core/management/base.py
@@ -11,7 +11,6 @@ import sys
from optparse import make_option, OptionParser
import django
-from django.core.exceptions import ImproperlyConfigured
from django.core.management.color import color_style, no_style
from django.utils.encoding import force_str
from django.utils.six import StringIO
@@ -342,16 +341,20 @@ class AppCommand(BaseCommand):
args = ''
def handle(self, *app_labels, **options):
- from django.db import models
+ from django.core.apps import app_cache
if not app_labels:
raise CommandError('Enter at least one appname.')
try:
- app_list = [models.get_app(app_label) for app_label in app_labels]
- except (ImproperlyConfigured, ImportError) as e:
+ app_configs = [app_cache.get_app_config(app_label) for app_label in app_labels]
+ except (LookupError, ImportError) as e:
raise CommandError("%s. Are you sure your INSTALLED_APPS setting is correct?" % e)
output = []
- for app in app_list:
- app_output = self.handle_app(app, **options)
+ for app_config in app_configs:
+ if app_config.models_module is None:
+ raise CommandError(
+ "AppCommand cannot handle app %r because it doesn't have "
+ "a models module." % app_config.label)
+ app_output = self.handle_app(app_config.models_module, **options)
if app_output:
output.append(app_output)
return '\n'.join(output)
diff --git a/django/core/management/color.py b/django/core/management/color.py
index 20e31fff93..3890a4546f 100644
--- a/django/core/management/color.py
+++ b/django/core/management/color.py
@@ -13,10 +13,12 @@ def supports_color():
Returns True if the running system's terminal supports color, and False
otherwise.
"""
- unsupported_platform = (sys.platform in ('win32', 'Pocket PC'))
+ plat = sys.platform
+ supported_platform = plat != 'Pocket PC' and (plat != 'win32' or
+ 'ANSICON' in os.environ)
# isatty is not always implemented, #6223.
is_a_tty = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()
- if unsupported_platform or not is_a_tty:
+ if not supported_platform or not is_a_tty:
return False
return True
diff --git a/django/core/management/commands/compilemessages.py b/django/core/management/commands/compilemessages.py
index d1ec277b63..915176e6a7 100644
--- a/django/core/management/commands/compilemessages.py
+++ b/django/core/management/commands/compilemessages.py
@@ -13,8 +13,8 @@ def has_bom(fn):
with open(fn, 'rb') as f:
sample = f.read(4)
return sample[:3] == b'\xef\xbb\xbf' or \
- sample.startswith(codecs.BOM_UTF16_LE) or \
- sample.startswith(codecs.BOM_UTF16_BE)
+ sample.startswith(codecs.BOM_UTF16_LE) or \
+ sample.startswith(codecs.BOM_UTF16_BE)
def compile_messages(stdout, locale=None):
diff --git a/django/core/management/commands/dumpdata.py b/django/core/management/commands/dumpdata.py
index 85817e9194..fa657bcd72 100644
--- a/django/core/management/commands/dumpdata.py
+++ b/django/core/management/commands/dumpdata.py
@@ -3,7 +3,6 @@ import warnings
from collections import OrderedDict
from optparse import make_option
-from django.core.exceptions import ImproperlyConfigured
from django.core.management.base import BaseCommand, CommandError
from django.core import serializers
from django.db import router, DEFAULT_DB_ALIAS
@@ -38,7 +37,7 @@ class Command(BaseCommand):
args = '[appname appname.ModelName ...]'
def handle(self, *app_labels, **options):
- from django.db.models import get_app, get_apps, get_model
+ from django.core.apps import app_cache
format = options.get('format')
indent = options.get('indent')
@@ -64,21 +63,24 @@ class Command(BaseCommand):
for exclude in excludes:
if '.' in exclude:
app_label, model_name = exclude.split('.', 1)
- model_obj = get_model(app_label, model_name)
+ model_obj = app_cache.get_model(app_label, model_name)
if not model_obj:
raise CommandError('Unknown model in excludes: %s' % exclude)
excluded_models.add(model_obj)
else:
try:
- app_obj = get_app(exclude)
- excluded_apps.add(app_obj)
- except ImproperlyConfigured:
+ app_obj = app_cache.get_app_config(exclude).models_module
+ if app_obj is not None:
+ excluded_apps.add(app_obj)
+ except LookupError:
raise CommandError('Unknown app in excludes: %s' % exclude)
if len(app_labels) == 0:
if primary_keys:
raise CommandError("You can only use --pks option with one model")
- app_list = OrderedDict((app, None) for app in get_apps() if app not in excluded_apps)
+ app_list = OrderedDict((app_config.models_module, None)
+ for app_config in app_cache.get_app_configs(only_with_models_module=True)
+ if app_config.models_module not in excluded_apps)
else:
if len(app_labels) > 1 and primary_keys:
raise CommandError("You can only use --pks option with one model")
@@ -87,12 +89,12 @@ class Command(BaseCommand):
try:
app_label, model_label = label.split('.')
try:
- app = get_app(app_label)
- except ImproperlyConfigured:
+ app = app_cache.get_app_config(app_label).models_module
+ except LookupError:
raise CommandError("Unknown application: %s" % app_label)
- if app in excluded_apps:
+ if app is None or app in excluded_apps:
continue
- model = get_model(app_label, model_label)
+ model = app_cache.get_model(app_label, model_label)
if model is None:
raise CommandError("Unknown model: %s.%s" % (app_label, model_label))
@@ -107,10 +109,10 @@ class Command(BaseCommand):
# This is just an app - no model qualifier
app_label = label
try:
- app = get_app(app_label)
- except ImproperlyConfigured:
+ app = app_cache.get_app_config(app_label).models_module
+ except LookupError:
raise CommandError("Unknown application: %s" % app_label)
- if app in excluded_apps:
+ if app is None or app in excluded_apps:
continue
app_list[app] = None
@@ -160,13 +162,13 @@ def sort_dependencies(app_list):
is serialized before a normal model, and any model with a natural key
dependency has it's dependencies serialized first.
"""
- from django.db.models import get_model, get_models
+ from django.core.apps import app_cache
# Process the list of models, and get the list of dependencies
model_dependencies = []
models = set()
for app, model_list in app_list:
if model_list is None:
- model_list = get_models(app)
+ model_list = app_cache.get_models(app)
for model in model_list:
models.add(model)
@@ -174,7 +176,7 @@ def sort_dependencies(app_list):
if hasattr(model, 'natural_key'):
deps = getattr(model.natural_key, 'dependencies', [])
if deps:
- deps = [get_model(*d.split('.')) for d in deps]
+ deps = [app_cache.get_model(*d.split('.')) for d in deps]
else:
deps = []
diff --git a/django/core/management/commands/flush.py b/django/core/management/commands/flush.py
index 130338a55a..562147e403 100644
--- a/django/core/management/commands/flush.py
+++ b/django/core/management/commands/flush.py
@@ -3,7 +3,8 @@ from importlib import import_module
from optparse import make_option
from django.conf import settings
-from django.db import connections, router, transaction, models, DEFAULT_DB_ALIAS
+from django.core.apps import app_cache
+from django.db import connections, router, transaction, DEFAULT_DB_ALIAS
from django.core.management import call_command
from django.core.management.base import NoArgsCommand, CommandError
from django.core.management.color import no_style
@@ -93,6 +94,6 @@ Are you sure you want to do this?
# Emit the post migrate signal. This allows individual applications to
# respond as if the database had been migrated from scratch.
all_models = []
- for app in models.get_apps():
- all_models.extend(router.get_migratable_models(app, database, include_auto_created=True))
+ for app_config in app_cache.get_app_configs(only_with_models_module=True):
+ all_models.extend(router.get_migratable_models(app_config.models_module, database, include_auto_created=True))
emit_post_migrate_signal(set(all_models), verbosity, interactive, database)
diff --git a/django/core/management/commands/loaddata.py b/django/core/management/commands/loaddata.py
index 667fbbf493..ee88232d9f 100644
--- a/django/core/management/commands/loaddata.py
+++ b/django/core/management/commands/loaddata.py
@@ -8,12 +8,12 @@ import zipfile
from optparse import make_option
from django.conf import settings
+from django.core.apps import app_cache
from django.core import serializers
from django.core.management.base import BaseCommand, CommandError
from django.core.management.color import no_style
from django.db import (connections, router, transaction, DEFAULT_DB_ALIAS,
IntegrityError, DatabaseError)
-from django.db.models import get_app_paths
from django.utils import lru_cache
from django.utils.encoding import force_text
from django.utils.functional import cached_property
@@ -178,11 +178,15 @@ class Command(BaseCommand):
if self.verbosity >= 2:
self.stdout.write("Loading '%s' fixtures..." % fixture_name)
- if os.path.sep in fixture_name:
+ if os.path.isabs(fixture_name):
fixture_dirs = [os.path.dirname(fixture_name)]
fixture_name = os.path.basename(fixture_name)
else:
fixture_dirs = self.fixture_dirs
+ if os.path.sep in fixture_name:
+ fixture_dirs = [os.path.join(dir_, os.path.dirname(fixture_name))
+ for dir_ in fixture_dirs]
+ fixture_name = os.path.basename(fixture_name)
suffixes = ('.'.join(ext for ext in combo if ext)
for combo in product(databases, ser_fmts, cmp_fmts))
@@ -226,8 +230,8 @@ class Command(BaseCommand):
current directory.
"""
dirs = []
- for path in get_app_paths():
- d = os.path.join(path, 'fixtures')
+ for app_config in app_cache.get_app_configs():
+ d = os.path.join(app_config.path, 'fixtures')
if os.path.isdir(d):
dirs.append(d)
dirs.extend(list(settings.FIXTURE_DIRS))
diff --git a/django/core/management/commands/makemessages.py b/django/core/management/commands/makemessages.py
index 25ca250c4a..a42ba96444 100644
--- a/django/core/management/commands/makemessages.py
+++ b/django/core/management/commands/makemessages.py
@@ -29,25 +29,28 @@ def check_programs(*programs):
@total_ordering
class TranslatableFile(object):
- def __init__(self, dirpath, file_name):
+ def __init__(self, dirpath, file_name, locale_dir):
self.file = file_name
self.dirpath = dirpath
+ self.locale_dir = locale_dir
def __repr__(self):
return "" % os.sep.join([self.dirpath, self.file])
def __eq__(self, other):
- return self.dirpath == other.dirpath and self.file == other.file
+ return self.path == other.path
def __lt__(self, other):
- if self.dirpath == other.dirpath:
- return self.file < other.file
- return self.dirpath < other.dirpath
+ return self.path < other.path
- def process(self, command, potfile, domain, keep_pot=False):
+ @property
+ def path(self):
+ return os.path.join(self.dirpath, self.file)
+
+ def process(self, command, domain):
"""
- Extract translatable literals from self.file for :param domain:
- creating or updating the :param potfile: POT file.
+ Extract translatable literals from self.file for :param domain:,
+ creating or updating the POT file.
Uses the xgettext GNU gettext utility.
"""
@@ -127,8 +130,6 @@ class TranslatableFile(object):
if status != STATUS_OK:
if is_templatized:
os.unlink(work_file)
- if not keep_pot and os.path.exists(potfile):
- os.unlink(potfile)
raise CommandError(
"errors happened while running xgettext on %s\n%s" %
(self.file, errors))
@@ -136,6 +137,8 @@ class TranslatableFile(object):
# Print warnings
command.stdout.write(errors)
if msgs:
+ # Write/append messages to pot file
+ potfile = os.path.join(self.locale_dir, '%s.pot' % str(domain))
if is_templatized:
# Remove '.py' suffix
if os.name == 'nt':
@@ -147,6 +150,7 @@ class TranslatableFile(object):
new = '#: ' + orig_file[2:]
msgs = msgs.replace(old, new)
write_pot_file(potfile, msgs)
+
if is_templatized:
os.unlink(work_file)
@@ -242,64 +246,94 @@ class Command(NoArgsCommand):
% get_text_list(list(self.extensions), 'and'))
self.invoked_for_django = False
+ self.locale_paths = []
+ self.default_locale_path = None
if os.path.isdir(os.path.join('conf', 'locale')):
- localedir = os.path.abspath(os.path.join('conf', 'locale'))
+ self.locale_paths = [os.path.abspath(os.path.join('conf', 'locale'))]
+ self.default_locale_path = self.locale_paths[0]
self.invoked_for_django = True
# Ignoring all contrib apps
self.ignore_patterns += ['contrib/*']
- elif os.path.isdir('locale'):
- localedir = os.path.abspath('locale')
else:
- raise CommandError("This script should be run from the Django Git "
- "tree or your project or app tree. If you did indeed run it "
- "from the Git checkout or your project or application, "
- "maybe you are just missing the conf/locale (in the django "
- "tree) or locale (for project and application) directory? It "
- "is not created automatically, you have to create it by hand "
- "if you want to enable i18n for your project or application.")
+ self.locale_paths.extend(list(settings.LOCALE_PATHS))
+ # Allow to run makemessages inside an app dir
+ if os.path.isdir('locale'):
+ self.locale_paths.append(os.path.abspath('locale'))
+ if self.locale_paths:
+ self.default_locale_path = self.locale_paths[0]
+ if not os.path.exists(self.default_locale_path):
+ os.makedirs(self.default_locale_path)
- check_programs('xgettext')
-
- potfile = self.build_pot_file(localedir)
-
- # Build po files for each selected locale
+ # Build locale list
locales = []
if locale is not None:
locales = locale
elif process_all:
- locale_dirs = filter(os.path.isdir, glob.glob('%s/*' % localedir))
+ locale_dirs = filter(os.path.isdir, glob.glob('%s/*' % self.default_locale_path))
locales = [os.path.basename(l) for l in locale_dirs]
-
if locales:
check_programs('msguniq', 'msgmerge', 'msgattrib')
+ check_programs('xgettext')
+
try:
+ potfiles = self.build_potfiles()
+
+ # Build po files for each selected locale
for locale in locales:
if self.verbosity > 0:
self.stdout.write("processing locale %s\n" % locale)
- self.write_po_file(potfile, locale)
+ for potfile in potfiles:
+ self.write_po_file(potfile, locale)
finally:
- if not self.keep_pot and os.path.exists(potfile):
- os.unlink(potfile)
+ if not self.keep_pot:
+ self.remove_potfiles()
- def build_pot_file(self, localedir):
+ def build_potfiles(self):
+ """
+ Build pot files and apply msguniq to them.
+ """
file_list = self.find_files(".")
-
- potfile = os.path.join(localedir, '%s.pot' % str(self.domain))
- if os.path.exists(potfile):
- # Remove a previous undeleted potfile, if any
- os.unlink(potfile)
-
+ self.remove_potfiles()
for f in file_list:
try:
- f.process(self, potfile, self.domain, self.keep_pot)
+ f.process(self, self.domain)
except UnicodeDecodeError:
self.stdout.write("UnicodeDecodeError: skipped file %s in %s" % (f.file, f.dirpath))
- return potfile
+
+ potfiles = []
+ for path in self.locale_paths:
+ potfile = os.path.join(path, '%s.pot' % str(self.domain))
+ if not os.path.exists(potfile):
+ continue
+ args = ['msguniq', '--to-code=utf-8']
+ if self.wrap:
+ args.append(self.wrap)
+ if self.location:
+ args.append(self.location)
+ args.append(potfile)
+ msgs, errors, status = popen_wrapper(args)
+ if errors:
+ if status != STATUS_OK:
+ raise CommandError(
+ "errors happened while running msguniq\n%s" % errors)
+ elif self.verbosity > 0:
+ self.stdout.write(errors)
+ with open(potfile, 'w') as fp:
+ fp.write(msgs)
+ potfiles.append(potfile)
+ return potfiles
+
+ def remove_potfiles(self):
+ for path in self.locale_paths:
+ pot_path = os.path.join(path, '%s.pot' % str(self.domain))
+ if os.path.exists(pot_path):
+ os.unlink(pot_path)
def find_files(self, root):
"""
- Helper method to get all files in the given root.
+ Helper method to get all files in the given root. Also check that there
+ is a matching locale dir for each file.
"""
def is_ignored(path, ignore_patterns):
@@ -319,12 +353,26 @@ class Command(NoArgsCommand):
dirnames.remove(dirname)
if self.verbosity > 1:
self.stdout.write('ignoring directory %s\n' % dirname)
+ elif dirname == 'locale':
+ dirnames.remove(dirname)
+ self.locale_paths.insert(0, os.path.join(os.path.abspath(dirpath), dirname))
for filename in filenames:
- if is_ignored(os.path.normpath(os.path.join(dirpath, filename)), self.ignore_patterns):
+ file_path = os.path.normpath(os.path.join(dirpath, filename))
+ if is_ignored(file_path, self.ignore_patterns):
if self.verbosity > 1:
self.stdout.write('ignoring file %s in %s\n' % (filename, dirpath))
else:
- all_files.append(TranslatableFile(dirpath, filename))
+ locale_dir = None
+ for path in self.locale_paths:
+ if os.path.abspath(dirpath).startswith(os.path.dirname(path)):
+ locale_dir = path
+ break
+ if not locale_dir:
+ locale_dir = self.default_locale_path
+ if not locale_dir:
+ raise CommandError(
+ "Unable to find a locale path to store translations for file %s" % file_path)
+ all_files.append(TranslatableFile(dirpath, filename, locale_dir))
return sorted(all_files)
def write_po_file(self, potfile, locale):
@@ -332,30 +380,14 @@ class Command(NoArgsCommand):
Creates or updates the PO file for self.domain and :param locale:.
Uses contents of the existing :param potfile:.
- Uses mguniq, msgmerge, and msgattrib GNU gettext utilities.
+ Uses msgmerge, and msgattrib GNU gettext utilities.
"""
- args = ['msguniq', '--to-code=utf-8']
- if self.wrap:
- args.append(self.wrap)
- if self.location:
- args.append(self.location)
- args.append(potfile)
- msgs, errors, status = popen_wrapper(args)
- if errors:
- if status != STATUS_OK:
- raise CommandError(
- "errors happened while running msguniq\n%s" % errors)
- elif self.verbosity > 0:
- self.stdout.write(errors)
-
basedir = os.path.join(os.path.dirname(potfile), locale, 'LC_MESSAGES')
if not os.path.isdir(basedir):
os.makedirs(basedir)
pofile = os.path.join(basedir, '%s.po' % str(self.domain))
if os.path.exists(pofile):
- with open(potfile, 'w') as fp:
- fp.write(msgs)
args = ['msgmerge', '-q']
if self.wrap:
args.append(self.wrap)
@@ -369,8 +401,11 @@ class Command(NoArgsCommand):
"errors happened while running msgmerge\n%s" % errors)
elif self.verbosity > 0:
self.stdout.write(errors)
- elif not self.invoked_for_django:
- msgs = self.copy_plural_forms(msgs, locale)
+ else:
+ with open(potfile, 'r') as fp:
+ msgs = fp.read()
+ if not self.invoked_for_django:
+ msgs = self.copy_plural_forms(msgs, locale)
msgs = msgs.replace(
"#. #-#-#-#-# %s.pot (PACKAGE VERSION) #-#-#-#-#\n" % self.domain, "")
with open(pofile, 'w') as fp:
diff --git a/django/core/management/commands/makemigrations.py b/django/core/management/commands/makemigrations.py
index 1079b4269c..3bd7ad42be 100644
--- a/django/core/management/commands/makemigrations.py
+++ b/django/core/management/commands/makemigrations.py
@@ -1,38 +1,44 @@
import sys
import os
+import operator
from optparse import make_option
-from django.core.management.base import BaseCommand
-from django.core.exceptions import ImproperlyConfigured
-from django.db import connections, DEFAULT_DB_ALIAS
+from django.core.apps import app_cache
+from django.core.management.base import BaseCommand, CommandError
+from django.db import connections, DEFAULT_DB_ALIAS, migrations
from django.db.migrations.loader import MigrationLoader
-from django.db.migrations.autodetector import MigrationAutodetector, InteractiveMigrationQuestioner
+from django.db.migrations.autodetector import MigrationAutodetector
+from django.db.migrations.questioner import MigrationQuestioner, InteractiveMigrationQuestioner
from django.db.migrations.state import ProjectState
from django.db.migrations.writer import MigrationWriter
-from django.db.models.loading import cache
+from django.utils.six.moves import reduce
class Command(BaseCommand):
option_list = BaseCommand.option_list + (
- make_option('--empty', action='store_true', dest='empty', default=False,
- help='Make a blank migration.'),
+ make_option('--dry-run', action='store_true', dest='dry_run', default=False,
+ help="Just show what migrations would be made; don't actually write them."),
+ make_option('--merge', action='store_true', dest='merge', default=False,
+ help="Enable fixing of migration conflicts."),
)
help = "Creates new migration(s) for apps."
- usage_str = "Usage: ./manage.py makemigrations [--empty] [app [app ...]]"
+ usage_str = "Usage: ./manage.py makemigrations [--dry-run] [app [app ...]]"
def handle(self, *app_labels, **options):
self.verbosity = int(options.get('verbosity'))
self.interactive = options.get('interactive')
+ self.dry_run = options.get('dry_run', False)
+ self.merge = options.get('merge', False)
# Make sure the app they asked for exists
app_labels = set(app_labels)
bad_app_labels = set()
for app_label in app_labels:
try:
- cache.get_app(app_label)
- except ImproperlyConfigured:
+ app_cache.get_app_config(app_label)
+ except LookupError:
bad_app_labels.add(app_label)
if bad_app_labels:
for app_label in bad_app_labels:
@@ -43,10 +49,30 @@ class Command(BaseCommand):
# (makemigrations doesn't look at the database state).
loader = MigrationLoader(connections[DEFAULT_DB_ALIAS])
+ # Before anything else, see if there's conflicting apps and drop out
+ # hard if there are any and they don't want to merge
+ conflicts = loader.detect_conflicts()
+ if conflicts and not self.merge:
+ name_str = "; ".join(
+ "%s in %s" % (", ".join(names), app)
+ for app, names in conflicts.items()
+ )
+ raise CommandError("Conflicting migrations detected (%s).\nTo fix them run 'python manage.py makemigrations --merge'" % name_str)
+
+ # If they want to merge and there's nothing to merge, then politely exit
+ if self.merge and not conflicts:
+ self.stdout.write("No conflicts detected to merge.")
+ return
+
+ # If they want to merge and there is something to merge, then
+ # divert into the merge code
+ if self.merge and conflicts:
+ return self.handle_merge(loader, conflicts)
+
# Detect changes
autodetector = MigrationAutodetector(
loader.graph.project_state(),
- ProjectState.from_app_cache(cache),
+ ProjectState.from_app_cache(app_cache),
InteractiveMigrationQuestioner(specified_apps=app_labels),
)
changes = autodetector.changes(graph=loader.graph, trim_to_apps=app_labels or None)
@@ -62,10 +88,10 @@ class Command(BaseCommand):
return
directory_created = {}
- for app_label, migrations in changes.items():
+ for app_label, app_migrations in changes.items():
if self.verbosity >= 1:
self.stdout.write(self.style.MIGRATE_HEADING("Migrations for '%s':" % app_label) + "\n")
- for migration in migrations:
+ for migration in app_migrations:
# Describe the migration
writer = MigrationWriter(migration)
if self.verbosity >= 1:
@@ -73,15 +99,81 @@ class Command(BaseCommand):
for operation in migration.operations:
self.stdout.write(" - %s\n" % operation.describe())
# Write it
- migrations_directory = os.path.dirname(writer.path)
- if not directory_created.get(app_label, False):
- if not os.path.isdir(migrations_directory):
- os.mkdir(migrations_directory)
- init_path = os.path.join(migrations_directory, "__init__.py")
- if not os.path.isfile(init_path):
- open(init_path, "w").close()
- # We just do this once per app
- directory_created[app_label] = True
- migration_string = writer.as_string()
+ if not self.dry_run:
+ migrations_directory = os.path.dirname(writer.path)
+ if not directory_created.get(app_label, False):
+ if not os.path.isdir(migrations_directory):
+ os.mkdir(migrations_directory)
+ init_path = os.path.join(migrations_directory, "__init__.py")
+ if not os.path.isfile(init_path):
+ open(init_path, "w").close()
+ # We just do this once per app
+ directory_created[app_label] = True
+ migration_string = writer.as_string()
+ with open(writer.path, "wb") as fh:
+ fh.write(migration_string)
+
+ def handle_merge(self, loader, conflicts):
+ """
+ Handles merging together conflicted migrations interactively,
+ if it's safe; otherwise, advises on how to fix it.
+ """
+ if self.interactive:
+ questioner = InteractiveMigrationQuestioner()
+ else:
+ questioner = MigrationQuestioner()
+ for app_label, migration_names in conflicts.items():
+ # Grab out the migrations in question, and work out their
+ # common ancestor.
+ merge_migrations = []
+ for migration_name in migration_names:
+ migration = loader.get_migration(app_label, migration_name)
+ migration.ancestry = loader.graph.forwards_plan((app_label, migration_name))
+ merge_migrations.append(migration)
+ common_ancestor = None
+ for level in zip(*[m.ancestry for m in merge_migrations]):
+ if reduce(operator.eq, level):
+ common_ancestor = level[0]
+ else:
+ break
+ if common_ancestor is None:
+ raise ValueError("Could not find common ancestor of %s" % migration_names)
+ # Now work out the operations along each divergent branch
+ for migration in merge_migrations:
+ migration.branch = migration.ancestry[
+ (migration.ancestry.index(common_ancestor) + 1):
+ ]
+ migration.merged_operations = []
+ for node_app, node_name in migration.branch:
+ migration.merged_operations.extend(
+ loader.get_migration(node_app, node_name).operations
+ )
+ # In future, this could use some of the Optimizer code
+ # (can_optimize_through) to automatically see if they're
+ # mergeable. For now, we always just prompt the user.
+ if self.verbosity > 0:
+ self.stdout.write(self.style.MIGRATE_HEADING("Merging %s" % app_label))
+ for migration in merge_migrations:
+ self.stdout.write(self.style.MIGRATE_LABEL(" Branch %s" % migration.name))
+ for operation in migration.merged_operations:
+ self.stdout.write(" - %s\n" % operation.describe())
+ if questioner.ask_merge(app_label):
+ # If they still want to merge it, then write out an empty
+ # file depending on the migrations needing merging.
+ numbers = [
+ MigrationAutodetector.parse_number(migration.name)
+ for migration in merge_migrations
+ ]
+ try:
+ biggest_number = max([x for x in numbers if x is not None])
+ except ValueError:
+ biggest_number = 1
+ subclass = type("Migration", (migrations.Migration, ), {
+ "dependencies": [(app_label, migration.name) for migration in merge_migrations],
+ })
+ new_migration = subclass("%04i_merge" % (biggest_number + 1), app_label)
+ writer = MigrationWriter(new_migration)
with open(writer.path, "wb") as fh:
- fh.write(migration_string)
+ fh.write(writer.as_string())
+ if self.verbosity > 0:
+ self.stdout.write("\nCreated new merge migration %s" % writer.path)
diff --git a/django/core/management/commands/migrate.py b/django/core/management/commands/migrate.py
index 191bc2c39d..b607e8ee43 100644
--- a/django/core/management/commands/migrate.py
+++ b/django/core/management/commands/migrate.py
@@ -7,13 +7,16 @@ import itertools
import traceback
from django.conf import settings
+from django.core.apps import app_cache
from django.core.management import call_command
from django.core.management.base import BaseCommand, CommandError
from django.core.management.color import no_style
from django.core.management.sql import custom_sql_for_model, emit_post_migrate_signal, emit_pre_migrate_signal
-from django.db import connections, router, transaction, models, DEFAULT_DB_ALIAS
+from django.db import connections, router, transaction, DEFAULT_DB_ALIAS
from django.db.migrations.executor import MigrationExecutor
from django.db.migrations.loader import MigrationLoader, AmbiguityError
+from django.db.migrations.state import ProjectState
+from django.db.migrations.autodetector import MigrationAutodetector
from django.utils.module_loading import module_has_submodule
@@ -59,6 +62,16 @@ class Command(BaseCommand):
# Work out which apps have migrations and which do not
executor = MigrationExecutor(connection, self.migration_progress_callback)
+ # Before anything else, see if there's conflicting apps and drop out
+ # hard if there are any
+ conflicts = executor.loader.detect_conflicts()
+ if conflicts:
+ name_str = "; ".join(
+ "%s in %s" % (", ".join(names), app)
+ for app, names in conflicts.items()
+ )
+ raise CommandError("Conflicting migrations detected (%s).\nTo fix them run 'python manage.py makemigrations --merge'" % name_str)
+
# If they supplied command line arguments, work out what they mean.
run_syncdb = False
target_app_labels_only = True
@@ -120,6 +133,15 @@ class Command(BaseCommand):
if not plan:
if self.verbosity >= 1:
self.stdout.write(" No migrations needed.")
+ # If there's changes that aren't in migrations yet, tell them how to fix it.
+ autodetector = MigrationAutodetector(
+ executor.loader.graph.project_state(),
+ ProjectState.from_app_cache(app_cache),
+ )
+ changes = autodetector.changes(graph=executor.loader.graph)
+ if changes:
+ self.stdout.write(self.style.NOTICE(" Your models have changes that are not yet reflected in a migration, and so won't be applied."))
+ self.stdout.write(self.style.NOTICE(" Run 'manage.py makemigrations' to make new migrations, and then re-run 'manage.py migrate' to apply them."))
else:
executor.migrate(targets, plan, fake=options.get("fake", False))
@@ -158,9 +180,10 @@ class Command(BaseCommand):
# Build the manifest of apps and models that are to be synchronized
all_models = [
- (app.__name__.split('.')[-2],
- router.get_migratable_models(app, connection.alias, include_auto_created=True))
- for app in models.get_apps() if app.__name__.split('.')[-2] in apps
+ (app_config.label,
+ router.get_migratable_models(app_config.models_module, connection.alias, include_auto_created=True))
+ for app_config in app_cache.get_app_configs(only_with_models_module=True)
+ if app_config.label in apps
]
def model_installed(model):
diff --git a/django/core/management/commands/runserver.py b/django/core/management/commands/runserver.py
index 402b3d342c..327832ba43 100644
--- a/django/core/management/commands/runserver.py
+++ b/django/core/management/commands/runserver.py
@@ -1,3 +1,5 @@
+from __future__ import unicode_literals
+
from optparse import make_option
from datetime import datetime
import errno
@@ -8,7 +10,10 @@ import socket
from django.core.management.base import BaseCommand, CommandError
from django.core.servers.basehttp import run, get_internal_wsgi_application
+from django.db import connections, DEFAULT_DB_ALIAS
+from django.db.migrations.executor import MigrationExecutor
from django.utils import autoreload
+from django.utils import six
naiveip_re = re.compile(r"""^(?:
(?P
@@ -96,13 +101,17 @@ class Command(BaseCommand):
self.stdout.write("Validating models...\n\n")
self.validate(display_num_errors=True)
+ self.check_migrations()
+ now = datetime.now().strftime('%B %d, %Y - %X')
+ if six.PY2:
+ now = now.decode('utf-8')
self.stdout.write((
"%(started_at)s\n"
"Django version %(version)s, using settings %(settings)r\n"
"Starting development server at http://%(addr)s:%(port)s/\n"
"Quit the server with %(quit_command)s.\n"
) % {
- "started_at": datetime.now().strftime('%B %d, %Y - %X'),
+ "started_at": now,
"version": self.get_version(),
"settings": settings.SETTINGS_MODULE,
"addr": '[%s]' % self.addr if self._raw_ipv6 else self.addr,
@@ -137,6 +146,16 @@ class Command(BaseCommand):
self.stdout.write(shutdown_message)
sys.exit(0)
+ def check_migrations(self):
+ """
+ Checks to see if the set of migrations on disk matches the
+ migrations in the database. Prints a warning if they don't match.
+ """
+ executor = MigrationExecutor(connections[DEFAULT_DB_ALIAS])
+ plan = executor.migration_plan(executor.loader.graph.leaf_nodes())
+ if plan:
+ self.stdout.write(self.style.NOTICE("\nYou have unapplied migrations; your app may not work properly until they are applied."))
+ self.stdout.write(self.style.NOTICE("Run 'python manage.py migrate' to apply them.\n"))
# Kept for backward compatibility
BaseRunserverCommand = Command
diff --git a/django/core/management/commands/shell.py b/django/core/management/commands/shell.py
index 00a6602c0b..5c189ac980 100644
--- a/django/core/management/commands/shell.py
+++ b/django/core/management/commands/shell.py
@@ -66,8 +66,8 @@ class Command(NoArgsCommand):
def handle_noargs(self, **options):
# XXX: (Temporary) workaround for ticket #1796: force early loading of all
# models from installed apps.
- from django.db.models.loading import get_models
- get_models()
+ from django.core.apps import app_cache
+ app_cache.get_models()
use_plain = options.get('plain', False)
no_startup = options.get('no_startup', False)
diff --git a/django/core/management/commands/sqlsequencereset.py b/django/core/management/commands/sqlsequencereset.py
index 8f4ea823c7..cc35030a91 100644
--- a/django/core/management/commands/sqlsequencereset.py
+++ b/django/core/management/commands/sqlsequencereset.py
@@ -2,8 +2,9 @@ from __future__ import unicode_literals
from optparse import make_option
+from django.core.apps import app_cache
from django.core.management.base import AppCommand
-from django.db import connections, models, DEFAULT_DB_ALIAS
+from django.db import connections, DEFAULT_DB_ALIAS
class Command(AppCommand):
@@ -20,4 +21,4 @@ class Command(AppCommand):
def handle_app(self, app, **options):
connection = connections[options.get('database')]
- return '\n'.join(connection.ops.sequence_reset_sql(self.style, models.get_models(app, include_auto_created=True)))
+ return '\n'.join(connection.ops.sequence_reset_sql(self.style, app_cache.get_models(app, include_auto_created=True)))
diff --git a/django/core/management/commands/testserver.py b/django/core/management/commands/testserver.py
index 182587b0f5..79f0ff5501 100644
--- a/django/core/management/commands/testserver.py
+++ b/django/core/management/commands/testserver.py
@@ -37,7 +37,8 @@ class Command(BaseCommand):
# multiple times.
shutdown_message = '\nServer stopped.\nNote that the test database, %r, has not been deleted. You can explore it on your own.' % db_name
use_threading = connection.features.test_db_allows_multiple_connections
- call_command('runserver',
+ call_command(
+ 'runserver',
addrport=addrport,
shutdown_message=shutdown_message,
use_reloader=False,
diff --git a/django/core/management/sql.py b/django/core/management/sql.py
index 6f60d73ae9..0b6e38124e 100644
--- a/django/core/management/sql.py
+++ b/django/core/management/sql.py
@@ -6,6 +6,7 @@ import re
import warnings
from django.conf import settings
+from django.core.apps import app_cache
from django.core.management.base import CommandError
from django.db import models, router
@@ -24,7 +25,7 @@ def sql_create(app, style, connection):
# We trim models from the current app so that the sqlreset command does not
# generate invalid SQL (leaving models out of known_models is harmless, so
# we can be conservative).
- app_models = models.get_models(app, include_auto_created=True)
+ app_models = app_cache.get_models(app, include_auto_created=True)
final_output = []
tables = connection.introspection.table_names()
known_models = set(model for model in connection.introspection.installed_models(tables) if model not in app_models)
@@ -168,7 +169,7 @@ def _split_statements(content):
def custom_sql_for_model(model, style, connection):
opts = model._meta
app_dirs = []
- app_dir = models.get_app_path(model._meta.app_label)
+ app_dir = app_cache.get_app_config(model._meta.app_label).path
app_dirs.append(os.path.normpath(os.path.join(app_dir, 'sql')))
# Deprecated location -- remove in Django 1.9
@@ -206,23 +207,27 @@ def custom_sql_for_model(model, style, connection):
def emit_pre_migrate_signal(create_models, verbosity, interactive, db):
# Emit the pre_migrate signal for every application.
- for app in models.get_apps():
- app_name = app.__name__.split('.')[-2]
+ for app_config in app_cache.get_app_configs(only_with_models_module=True):
if verbosity >= 2:
- print("Running pre-migrate handlers for application %s" % app_name)
- models.signals.pre_migrate.send(sender=app, app=app,
- create_models=create_models,
- verbosity=verbosity,
- interactive=interactive,
- db=db)
+ print("Running pre-migrate handlers for application %s" % app_config.label)
+ models.signals.pre_migrate.send(
+ sender=app_config.models_module,
+ app=app_config.models_module,
+ create_models=create_models,
+ verbosity=verbosity,
+ interactive=interactive,
+ db=db)
def emit_post_migrate_signal(created_models, verbosity, interactive, db):
# Emit the post_migrate signal for every application.
- for app in models.get_apps():
- app_name = app.__name__.split('.')[-2]
+ for app_config in app_cache.get_app_configs(only_with_models_module=True):
if verbosity >= 2:
- print("Running post-migrate handlers for application %s" % app_name)
- models.signals.post_migrate.send(sender=app, app=app,
- created_models=created_models, verbosity=verbosity,
- interactive=interactive, db=db)
+ print("Running post-migrate handlers for application %s" % app_config.label)
+ models.signals.post_migrate.send(
+ sender=app_config.models_module,
+ app=app_config.models_module,
+ created_models=created_models,
+ verbosity=verbosity,
+ interactive=interactive,
+ db=db)
diff --git a/django/core/management/validation.py b/django/core/management/validation.py
index 5bf9413c20..1cedcb9925 100644
--- a/django/core/management/validation.py
+++ b/django/core/management/validation.py
@@ -26,16 +26,13 @@ def get_validation_errors(outfile, app=None):
validates all models of all installed apps. Writes errors, if any, to outfile.
Returns number of errors.
"""
+ from django.core.apps import app_cache
from django.db import connection, models
- from django.db.models.loading import get_app_errors
from django.db.models.deletion import SET_NULL, SET_DEFAULT
e = ModelErrorCollection(outfile)
- for (app_name, error) in get_app_errors().items():
- e.add(app_name, error)
-
- for cls in models.get_models(app, include_swapped=True):
+ for cls in app_cache.get_models(app, include_swapped=True):
opts = cls._meta
# Check swappable attribute.
@@ -45,7 +42,7 @@ def get_validation_errors(outfile, app=None):
except ValueError:
e.add(opts, "%s is not of the form 'app_label.app_name'." % opts.swappable)
continue
- if not models.get_model(app_label, model_name):
+ if not app_cache.get_model(app_label, model_name):
e.add(opts, "Model has been swapped out for '%s' which has not been installed or is abstract." % opts.swapped)
# No need to perform any other validation checks on a swapped model.
continue
@@ -155,7 +152,7 @@ def get_validation_errors(outfile, app=None):
# Check to see if the related field will clash with any existing
# fields, m2m fields, m2m related objects or related objects
if f.rel:
- if f.rel.to not in models.get_models():
+ if f.rel.to not in app_cache.get_models():
# If the related model is swapped, provide a hint;
# otherwise, the model just hasn't been installed.
if not isinstance(f.rel.to, six.string_types) and f.rel.to._meta.swapped:
@@ -210,7 +207,7 @@ def get_validation_errors(outfile, app=None):
# Check to see if the related m2m field will clash with any
# existing fields, m2m fields, m2m related objects or related
# objects
- if f.rel.to not in models.get_models():
+ if f.rel.to not in app_cache.get_models():
# If the related model is swapped, provide a hint;
# otherwise, the model just hasn't been installed.
if not isinstance(f.rel.to, six.string_types) and f.rel.to._meta.swapped:
@@ -268,10 +265,9 @@ def get_validation_errors(outfile, app=None):
)
else:
seen_to = True
- if f.rel.through not in models.get_models(include_auto_created=True):
+ if f.rel.through not in app_cache.get_models(include_auto_created=True):
e.add(opts, "'%s' specifies an m2m relation through model "
- "%s, which has not been installed." % (f.name, f.rel.through)
- )
+ "%s, which has not been installed." % (f.name, f.rel.through))
signature = (f.rel.to, cls, f.rel.through)
if signature in seen_intermediary_signatures:
e.add(opts, "The model %s has two manually-defined m2m "
@@ -295,13 +291,14 @@ def get_validation_errors(outfile, app=None):
if not seen_related_fk or not seen_this_fk:
e.add(opts, "'%s' is a manually-defined m2m relation "
"through model %s, which does not have foreign keys "
- "to %s and %s" % (f.name, f.rel.through._meta.object_name,
- f.rel.to._meta.object_name, cls._meta.object_name)
+ "to %s and %s" % (
+ f.name, f.rel.through._meta.object_name,
+ f.rel.to._meta.object_name, cls._meta.object_name
+ )
)
elif isinstance(f.rel.through, six.string_types):
e.add(opts, "'%s' specifies an m2m relation through model %s, "
- "which has not been installed" % (f.name, f.rel.through)
- )
+ "which has not been installed" % (f.name, f.rel.through))
rel_opts = f.rel.to._meta
rel_name = f.related.get_accessor_name()
diff --git a/django/core/serializers/base.py b/django/core/serializers/base.py
index b7d3e28a0d..db4b79a020 100644
--- a/django/core/serializers/base.py
+++ b/django/core/serializers/base.py
@@ -3,6 +3,7 @@ Module for abstract serializer/unserializer base classes.
"""
import warnings
+from django.core.apps import app_cache
from django.db import models
from django.utils import six
@@ -136,10 +137,9 @@ class Deserializer(six.Iterator):
self.stream = six.StringIO(stream_or_string)
else:
self.stream = stream_or_string
- # hack to make sure that the models have all been loaded before
- # deserialization starts (otherwise subclass calls to get_model()
- # and friends might fail...)
- models.get_apps()
+ # Make sure the app cache is loaded before deserialization starts
+ # (otherwise subclass calls to get_model() and friends might fail...)
+ app_cache.populate()
def __iter__(self):
return self
diff --git a/django/core/serializers/python.py b/django/core/serializers/python.py
index 4ac7cc4cf1..13d6f01a4e 100644
--- a/django/core/serializers/python.py
+++ b/django/core/serializers/python.py
@@ -6,6 +6,7 @@ other serializers.
from __future__ import unicode_literals
from django.conf import settings
+from django.core.apps import app_cache
from django.core.serializers import base
from django.db import models, DEFAULT_DB_ALIAS
from django.utils.encoding import smart_text, is_protected_type
@@ -87,7 +88,8 @@ def Deserializer(object_list, **options):
db = options.pop('using', DEFAULT_DB_ALIAS)
ignore = options.pop('ignorenonexistent', False)
- models.get_apps()
+ app_cache.populate()
+
for d in object_list:
# Look up the model and starting build a dict of data for it.
Model = _get_model(d["model"])
@@ -153,7 +155,7 @@ def _get_model(model_identifier):
Helper to look up a model from an "app_label.model_name" string.
"""
try:
- Model = models.get_model(*model_identifier.split("."))
+ Model = app_cache.get_model(*model_identifier.split("."))
except TypeError:
Model = None
if Model is None:
diff --git a/django/core/serializers/xml_serializer.py b/django/core/serializers/xml_serializer.py
index 6870ac7d44..90ad2cf398 100644
--- a/django/core/serializers/xml_serializer.py
+++ b/django/core/serializers/xml_serializer.py
@@ -5,6 +5,7 @@ XML serializer.
from __future__ import unicode_literals
from django.conf import settings
+from django.core.apps import app_cache
from django.core.serializers import base
from django.db import models, DEFAULT_DB_ALIAS
from django.utils.xmlutils import SimplerXMLGenerator
@@ -276,7 +277,7 @@ class Deserializer(base.Deserializer):
"<%s> node is missing the required '%s' attribute"
% (node.nodeName, attr))
try:
- Model = models.get_model(*model_identifier.split("."))
+ Model = app_cache.get_model(*model_identifier.split("."))
except TypeError:
Model = None
if Model is None:
diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py
index 634eb7519a..1dca15b4aa 100644
--- a/django/core/urlresolvers.py
+++ b/django/core/urlresolvers.py
@@ -131,8 +131,7 @@ def get_ns_resolver(ns_pattern, resolver):
# Build a namespaced resolver for the given parent urlconf pattern.
# This makes it possible to have captured parameters in the parent
# urlconf pattern.
- ns_resolver = RegexURLResolver(ns_pattern,
- resolver.url_patterns)
+ ns_resolver = RegexURLResolver(ns_pattern, resolver.url_patterns)
return RegexURLResolver(r'^/', [ns_resolver])
diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py
index 39a774132e..89131a4fc4 100644
--- a/django/db/backends/__init__.py
+++ b/django/db/backends/__init__.py
@@ -156,7 +156,7 @@ class BaseDatabaseWrapper(object):
"""
self.validate_thread_sharing()
if (self.use_debug_cursor or
- (self.use_debug_cursor is None and settings.DEBUG)):
+ (self.use_debug_cursor is None and settings.DEBUG)):
cursor = self.make_debug_cursor(self._cursor())
else:
cursor = utils.CursorWrapper(self._cursor(), self)
@@ -1218,8 +1218,7 @@ class BaseDatabaseOperations(object):
# Structure returned by the DB-API cursor.description interface (PEP 249)
FieldInfo = namedtuple('FieldInfo',
- 'name type_code display_size internal_size precision scale null_ok'
-)
+ 'name type_code display_size internal_size precision scale null_ok')
class BaseDatabaseIntrospection(object):
@@ -1272,10 +1271,11 @@ class BaseDatabaseIntrospection(object):
If only_existing is True, the resulting list will only include the tables
that actually exist in the database.
"""
- from django.db import models, router
+ from django.core.apps import app_cache
+ from django.db import router
tables = set()
- for app in models.get_apps():
- for model in router.get_migratable_models(app, self.connection.alias):
+ for app_config in app_cache.get_app_configs(only_with_models_module=True):
+ for model in router.get_migratable_models(app_config.models_module, self.connection.alias):
if not model._meta.managed:
continue
tables.add(model._meta.db_table)
@@ -1292,10 +1292,11 @@ class BaseDatabaseIntrospection(object):
def installed_models(self, tables):
"Returns a set of all models represented by the provided list of table names."
- from django.db import models, router
+ from django.core.apps import app_cache
+ from django.db import router
all_models = []
- for app in models.get_apps():
- all_models.extend(router.get_migratable_models(app, self.connection.alias))
+ for app_config in app_cache.get_app_configs(only_with_models_module=True):
+ all_models.extend(router.get_migratable_models(app_config.models_module, self.connection.alias))
tables = list(map(self.table_name_converter, tables))
return set([
m for m in all_models
@@ -1304,13 +1305,13 @@ class BaseDatabaseIntrospection(object):
def sequence_list(self):
"Returns a list of information about all DB sequences for all models in all apps."
+ from django.core.apps import app_cache
from django.db import models, router
- apps = models.get_apps()
sequence_list = []
- for app in apps:
- for model in router.get_migratable_models(app, self.connection.alias):
+ for app_config in app_cache.get_app_configs(only_with_models_module=True):
+ for model in router.get_migratable_models(app_config.models_module, self.connection.alias):
if not model._meta.managed:
continue
if model._meta.swapped:
diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py
index b38d00c84a..e7932dd800 100644
--- a/django/db/backends/mysql/base.py
+++ b/django/db/backends/mysql/base.py
@@ -332,13 +332,15 @@ class DatabaseOperations(BaseDatabaseOperations):
# Truncate already resets the AUTO_INCREMENT field from
# MySQL version 5.0.13 onwards. Refs #16961.
if self.connection.mysql_version < (5, 0, 13):
- return ["%s %s %s %s %s;" %
- (style.SQL_KEYWORD('ALTER'),
+ return [
+ "%s %s %s %s %s;" % (
+ style.SQL_KEYWORD('ALTER'),
style.SQL_KEYWORD('TABLE'),
style.SQL_TABLE(self.quote_name(sequence['table'])),
style.SQL_KEYWORD('AUTO_INCREMENT'),
style.SQL_FIELD('= 1'),
- ) for sequence in sequences]
+ ) for sequence in sequences
+ ]
else:
return []
diff --git a/django/db/backends/mysql/compiler.py b/django/db/backends/mysql/compiler.py
index 609573442c..85c045fff2 100644
--- a/django/db/backends/mysql/compiler.py
+++ b/django/db/backends/mysql/compiler.py
@@ -8,7 +8,7 @@ class SQLCompiler(compiler.SQLCompiler):
index_extra_select = len(self.query.extra_select)
for value, field in zip_longest(row[index_extra_select:], fields):
if (field and field.get_internal_type() in ("BooleanField", "NullBooleanField") and
- value in (0, 1)):
+ value in (0, 1)):
value = bool(value)
values.append(value)
return row[:index_extra_select] + tuple(values)
diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py
index 6a6a877b46..6078eecb20 100644
--- a/django/db/backends/oracle/base.py
+++ b/django/db/backends/oracle/base.py
@@ -390,9 +390,11 @@ WHEN (new.%(col_name)s IS NULL)
sequence_name = self._get_sequence_name(sequence_info['table'])
table_name = self.quote_name(sequence_info['table'])
column_name = self.quote_name(sequence_info['column'] or 'id')
- query = _get_sequence_reset_sql() % {'sequence': sequence_name,
- 'table': table_name,
- 'column': column_name}
+ query = _get_sequence_reset_sql() % {
+ 'sequence': sequence_name,
+ 'table': table_name,
+ 'column': column_name,
+ }
sql.append(query)
return sql
@@ -880,12 +882,10 @@ class FormatStylePlaceholderCursor(object):
def fetchmany(self, size=None):
if size is None:
size = self.arraysize
- return tuple(_rowfactory(r, self.cursor)
- for r in self.cursor.fetchmany(size))
+ return tuple(_rowfactory(r, self.cursor) for r in self.cursor.fetchmany(size))
def fetchall(self):
- return tuple(_rowfactory(r, self.cursor)
- for r in self.cursor.fetchall())
+ return tuple(_rowfactory(r, self.cursor) for r in self.cursor.fetchall())
def var(self, *args):
return VariableWrapper(self.cursor.var(*args))
diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py
index cb23c33a5c..91e16a1b76 100644
--- a/django/db/backends/postgresql_psycopg2/base.py
+++ b/django/db/backends/postgresql_psycopg2/base.py
@@ -168,7 +168,8 @@ class DatabaseWrapper(BaseDatabaseWrapper):
# notification. If we don't set self.connection to None, the error
# will occur a every request.
self.connection = None
- logger.warning('psycopg2 error while closing the connection.',
+ logger.warning(
+ 'psycopg2 error while closing the connection.',
exc_info=sys.exc_info()
)
raise
diff --git a/django/db/backends/postgresql_psycopg2/schema.py b/django/db/backends/postgresql_psycopg2/schema.py
index b86e0857bb..946f39d586 100644
--- a/django/db/backends/postgresql_psycopg2/schema.py
+++ b/django/db/backends/postgresql_psycopg2/schema.py
@@ -2,4 +2,57 @@ from django.db.backends.schema import BaseDatabaseSchemaEditor
class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
- pass
+
+ sql_create_sequence = "CREATE SEQUENCE %(sequence)s"
+ sql_delete_sequence = "DROP SEQUENCE IF EXISTS %(sequence)s CASCADE"
+ sql_set_sequence_max = "SELECT setval('%(sequence)s', MAX(%(column)s)) FROM %(table)s"
+
+ def _alter_column_type_sql(self, table, column, type):
+ """
+ Makes ALTER TYPE with SERIAL make sense.
+ """
+ if type.lower() == "serial":
+ sequence_name = "%s_%s_seq" % (table, column)
+ return (
+ (
+ self.sql_alter_column_type % {
+ "column": self.quote_name(column),
+ "type": "integer",
+ },
+ [],
+ ),
+ [
+ (
+ self.sql_delete_sequence % {
+ "sequence": sequence_name,
+ },
+ [],
+ ),
+ (
+ self.sql_create_sequence % {
+ "sequence": sequence_name,
+ },
+ [],
+ ),
+ (
+ self.sql_alter_column % {
+ "table": table,
+ "changes": self.sql_alter_column_default % {
+ "column": column,
+ "default": "nextval('%s')" % sequence_name,
+ }
+ },
+ [],
+ ),
+ (
+ self.sql_set_sequence_max % {
+ "table": table,
+ "column": column,
+ "sequence": sequence_name,
+ },
+ [],
+ ),
+ ],
+ )
+ else:
+ return super(DatabaseSchemaEditor, self)._alter_column_type_sql(table, column, type)
diff --git a/django/db/backends/schema.py b/django/db/backends/schema.py
index 8040252f2c..e33956763c 100644
--- a/django/db/backends/schema.py
+++ b/django/db/backends/schema.py
@@ -90,7 +90,7 @@ class BaseDatabaseSchemaEditor(object):
# Log the command we're running, then run it
logger.debug("%s; (params %r)" % (sql, params))
if self.collect_sql:
- self.collected_sql.append((sql % list(map(self.connection.ops.quote_parameter, params))) + ";")
+ self.collected_sql.append((sql % tuple(map(self.connection.ops.quote_parameter, params))) + ";")
else:
cursor.execute(sql, params)
@@ -498,6 +498,18 @@ class BaseDatabaseSchemaEditor(object):
"name": fk_name,
}
)
+ # Drop incoming FK constraints if we're a primary key and things are going
+ # to change.
+ if old_field.primary_key and new_field.primary_key and old_type != new_type:
+ for rel in new_field.model._meta.get_all_related_objects():
+ rel_fk_names = self._constraint_names(rel.model, [rel.field.column], foreign_key=True)
+ for fk_name in rel_fk_names:
+ self.execute(
+ self.sql_delete_fk % {
+ "table": self.quote_name(rel.model._meta.db_table),
+ "name": fk_name,
+ }
+ )
# Change check constraints?
if old_db_params['check'] != new_db_params['check'] and old_db_params['check']:
constraint_names = self._constraint_names(model, [old_field.column], check=True)
@@ -524,15 +536,12 @@ class BaseDatabaseSchemaEditor(object):
})
# Next, start accumulating actions to do
actions = []
+ post_actions = []
# Type change?
if old_type != new_type:
- actions.append((
- self.sql_alter_column_type % {
- "column": self.quote_name(new_field.column),
- "type": new_type,
- },
- [],
- ))
+ fragment, other_actions = self._alter_column_type_sql(model._meta.db_table, new_field.column, new_type)
+ actions.append(fragment)
+ post_actions.extend(other_actions)
# Default change?
old_default = self.effective_default(old_field)
new_default = self.effective_default(new_field)
@@ -596,6 +605,9 @@ class BaseDatabaseSchemaEditor(object):
},
params,
)
+ if post_actions:
+ for sql, params in post_actions:
+ self.execute(sql, params)
# Added a unique?
if not old_field.unique and new_field.unique:
self.execute(
@@ -615,6 +627,11 @@ class BaseDatabaseSchemaEditor(object):
"extra": "",
}
)
+ # Type alteration on primary key? Then we need to alter the column
+ # referring to us.
+ rels_to_update = []
+ if old_field.primary_key and new_field.primary_key and old_type != new_type:
+ rels_to_update.extend(new_field.model._meta.get_all_related_objects())
# Changed to become primary key?
# Note that we don't detect unsetting of a PK, as we assume another field
# will always come along and replace it.
@@ -641,6 +658,21 @@ class BaseDatabaseSchemaEditor(object):
"columns": self.quote_name(new_field.column),
}
)
+ # Update all referencing columns
+ rels_to_update.extend(new_field.model._meta.get_all_related_objects())
+ # Handle our type alters on the other end of rels from the PK stuff above
+ for rel in rels_to_update:
+ rel_db_params = rel.field.db_parameters(connection=self.connection)
+ rel_type = rel_db_params['type']
+ self.execute(
+ self.sql_alter_column % {
+ "table": self.quote_name(rel.model._meta.db_table),
+ "changes": self.sql_alter_column_type % {
+ "column": self.quote_name(rel.field.column),
+ "type": rel_type,
+ }
+ }
+ )
# Does it have a foreign key?
if new_field.rel:
self.execute(
@@ -652,6 +684,18 @@ class BaseDatabaseSchemaEditor(object):
"to_column": self.quote_name(new_field.rel.get_related_field().column),
}
)
+ # Rebuild FKs that pointed to us if we previously had to drop them
+ if old_field.primary_key and new_field.primary_key and old_type != new_type:
+ for rel in new_field.model._meta.get_all_related_objects():
+ self.execute(
+ self.sql_create_fk % {
+ "table": self.quote_name(rel.model._meta.db_table),
+ "name": self._create_index_name(rel.model, [rel.field.column], suffix="_fk"),
+ "column": self.quote_name(rel.field.column),
+ "to_table": self.quote_name(model._meta.db_table),
+ "to_column": self.quote_name(new_field.column),
+ }
+ )
# Does it have check constraints we need to add?
if old_db_params['check'] != new_db_params['check'] and new_db_params['check']:
self.execute(
@@ -666,6 +710,27 @@ class BaseDatabaseSchemaEditor(object):
if self.connection.features.connection_persists_old_columns:
self.connection.close()
+ def _alter_column_type_sql(self, table, column, type):
+ """
+ Hook to specialise column type alteration for different backends,
+ for cases when a creation type is different to an alteration type
+ (e.g. SERIAL in PostgreSQL, PostGIS fields).
+
+ Should return two things; an SQL fragment of (sql, params) to insert
+ into an ALTER TABLE statement, and a list of extra (sql, params) tuples
+ to run once the field is altered.
+ """
+ return (
+ (
+ self.sql_alter_column_type % {
+ "column": self.quote_name(column),
+ "type": type,
+ },
+ [],
+ ),
+ [],
+ )
+
def _alter_many_to_many(self, model, old_field, new_field, strict):
"""
Alters M2Ms to repoint their to= endpoints.
diff --git a/django/db/backends/sqlite3/schema.py b/django/db/backends/sqlite3/schema.py
index f04095507f..e2b9603819 100644
--- a/django/db/backends/sqlite3/schema.py
+++ b/django/db/backends/sqlite3/schema.py
@@ -1,6 +1,6 @@
+from django.core.apps.cache import AppCache
from django.db.backends.schema import BaseDatabaseSchemaEditor
from django.db.models.fields.related import ManyToManyField
-from django.db.models.loading import BaseAppCache
class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
@@ -39,7 +39,7 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
del body[field.name]
del mapping[field.column]
# Work inside a new AppCache
- app_cache = BaseAppCache()
+ app_cache = AppCache()
# Construct a new model for the new state
meta_contents = {
'app_label': model._meta.app_label,
diff --git a/django/db/migrations/autodetector.py b/django/db/migrations/autodetector.py
index 656ce3e939..4074366188 100644
--- a/django/db/migrations/autodetector.py
+++ b/django/db/migrations/autodetector.py
@@ -1,11 +1,9 @@
import re
-import os
-import sys
-from django.utils import datetime_safe, importlib
-from django.utils.six.moves import input
+import datetime
+
from django.db.migrations import operations
from django.db.migrations.migration import Migration
-from django.db.models.loading import cache
+from django.db.migrations.questioner import MigrationQuestioner
class MigrationAutodetector(object):
@@ -70,7 +68,7 @@ class MigrationAutodetector(object):
model_state = self.to_state.models[app_label, model_name]
# Are there any relationships out from this model? if so, punt it to the next phase.
related_fields = []
- for field in new_app_cache.get_model(app_label, model_name)._meta.fields:
+ for field in new_app_cache.get_model(app_label, model_name)._meta.local_fields:
if field.rel:
if field.rel.to:
related_fields.append((field.name, field.rel.to._meta.app_label.lower(), field.rel.to._meta.object_name.lower()))
@@ -156,37 +154,64 @@ class MigrationAutodetector(object):
)
# Changes within models
kept_models = set(old_model_keys).intersection(new_model_keys)
+ old_fields = set()
+ new_fields = set()
for app_label, model_name in kept_models:
old_model_state = self.from_state.models[app_label, model_name]
new_model_state = self.to_state.models[app_label, model_name]
- # New fields
- old_field_names = set(x for x, y in old_model_state.fields)
- new_field_names = set(x for x, y in new_model_state.fields)
- for field_name in new_field_names - old_field_names:
- field = new_model_state.get_field_by_name(field_name)
- # Scan to see if this is actually a rename!
- field_dec = field.deconstruct()[1:]
- found_rename = False
- for removed_field_name in (old_field_names - new_field_names):
- if old_model_state.get_field_by_name(removed_field_name).deconstruct()[1:] == field_dec:
- if self.questioner.ask_rename(model_name, removed_field_name, field_name, field):
+ # Collect field changes for later global dealing with (so AddFields
+ # always come before AlterFields even on separate models)
+ old_fields.update((app_label, model_name, x) for x, y in old_model_state.fields)
+ new_fields.update((app_label, model_name, x) for x, y in new_model_state.fields)
+ # Unique_together changes
+ if old_model_state.options.get("unique_together", set()) != new_model_state.options.get("unique_together", set()):
+ self.add_to_migration(
+ app_label,
+ operations.AlterUniqueTogether(
+ name=model_name,
+ unique_together=new_model_state.options.get("unique_together", set()),
+ )
+ )
+ # New fields
+ for app_label, model_name, field_name in new_fields - old_fields:
+ old_model_state = self.from_state.models[app_label, model_name]
+ new_model_state = self.to_state.models[app_label, model_name]
+ field = new_model_state.get_field_by_name(field_name)
+ # Scan to see if this is actually a rename!
+ field_dec = field.deconstruct()[1:]
+ found_rename = False
+ for rem_app_label, rem_model_name, rem_field_name in (old_fields - new_fields):
+ if rem_app_label == app_label and rem_model_name == model_name:
+ if old_model_state.get_field_by_name(rem_field_name).deconstruct()[1:] == field_dec:
+ if self.questioner.ask_rename(model_name, rem_field_name, field_name, field):
self.add_to_migration(
app_label,
operations.RenameField(
model_name=model_name,
- old_name=removed_field_name,
+ old_name=rem_field_name,
new_name=field_name,
)
)
- old_field_names.remove(removed_field_name)
- new_field_names.remove(field_name)
+ old_fields.remove((rem_app_label, rem_model_name, rem_field_name))
+ new_fields.remove((app_label, model_name, field_name))
found_rename = True
break
- if found_rename:
- continue
- # You can't just add NOT NULL fields with no default
- if not field.null and not field.has_default():
- field.default = self.questioner.ask_not_null_addition(field_name, model_name)
+ if found_rename:
+ continue
+ # You can't just add NOT NULL fields with no default
+ if not field.null and not field.has_default():
+ field = field.clone()
+ field.default = self.questioner.ask_not_null_addition(field_name, model_name)
+ self.add_to_migration(
+ app_label,
+ operations.AddField(
+ model_name=model_name,
+ name=field_name,
+ field=field,
+ preserve_default=False,
+ )
+ )
+ else:
self.add_to_migration(
app_label,
operations.AddField(
@@ -195,36 +220,31 @@ class MigrationAutodetector(object):
field=field,
)
)
- # Old fields
- for field_name in old_field_names - new_field_names:
+ # Old fields
+ for app_label, model_name, field_name in old_fields - new_fields:
+ old_model_state = self.from_state.models[app_label, model_name]
+ new_model_state = self.to_state.models[app_label, model_name]
+ self.add_to_migration(
+ app_label,
+ operations.RemoveField(
+ model_name=model_name,
+ name=field_name,
+ )
+ )
+ # The same fields
+ for app_label, model_name, field_name in old_fields.intersection(new_fields):
+ # Did the field change?
+ old_model_state = self.from_state.models[app_label, model_name]
+ new_model_state = self.to_state.models[app_label, model_name]
+ old_field_dec = old_model_state.get_field_by_name(field_name).deconstruct()
+ new_field_dec = new_model_state.get_field_by_name(field_name).deconstruct()
+ if old_field_dec != new_field_dec:
self.add_to_migration(
app_label,
- operations.RemoveField(
+ operations.AlterField(
model_name=model_name,
name=field_name,
- )
- )
- # The same fields
- for field_name in old_field_names.intersection(new_field_names):
- # Did the field change?
- old_field_dec = old_model_state.get_field_by_name(field_name).deconstruct()
- new_field_dec = new_model_state.get_field_by_name(field_name).deconstruct()
- if old_field_dec != new_field_dec:
- self.add_to_migration(
- app_label,
- operations.AlterField(
- model_name=model_name,
- name=field_name,
- field=new_model_state.get_field_by_name(field_name),
- )
- )
- # unique_together changes
- if old_model_state.options.get("unique_together", set()) != new_model_state.options.get("unique_together", set()):
- self.add_to_migration(
- app_label,
- operations.AlterUniqueTogether(
- name=model_name,
- unique_together=new_model_state.options.get("unique_together", set()),
+ field=new_model_state.get_field_by_name(field_name),
)
)
# Alright, now add internal dependencies
@@ -331,8 +351,9 @@ class MigrationAutodetector(object):
def suggest_name(cls, ops):
"""
Given a set of operations, suggests a name for the migration
- they might represent. Names not guaranteed to be unique; they
- must be prefixed by a number or date.
+ they might represent. Names are not guaranteed to be unique,
+ but we put some effort in to the fallback name to avoid VCS conflicts
+ if we can.
"""
if len(ops) == 1:
if isinstance(ops[0], operations.CreateModel):
@@ -345,7 +366,7 @@ class MigrationAutodetector(object):
return "remove_%s_%s" % (ops[0].model_name.lower(), ops[0].name.lower())
elif all(isinstance(o, operations.CreateModel) for o in ops):
return "_".join(sorted(o.name.lower() for o in ops))
- return "auto"
+ return "auto_%s" % datetime.datetime.now().strftime("%Y%m%d_%H%M")
@classmethod
def parse_number(cls, name):
@@ -356,107 +377,3 @@ class MigrationAutodetector(object):
if re.match(r"^\d+_", name):
return int(name.split("_")[0])
return None
-
-
-class MigrationQuestioner(object):
- """
- Gives the autodetector responses to questions it might have.
- This base class has a built-in noninteractive mode, but the
- interactive subclass is what the command-line arguments will use.
- """
-
- def __init__(self, defaults=None):
- self.defaults = defaults or {}
-
- def ask_initial(self, app_label):
- "Should we create an initial migration for the app?"
- return self.defaults.get("ask_initial", False)
-
- def ask_not_null_addition(self, field_name, model_name):
- "Adding a NOT NULL field to a model"
- # None means quit
- return None
-
- def ask_rename(self, model_name, old_name, new_name, field_instance):
- "Was this field really renamed?"
- return self.defaults.get("ask_rename", False)
-
-
-class InteractiveMigrationQuestioner(MigrationQuestioner):
-
- def __init__(self, specified_apps=set()):
- self.specified_apps = specified_apps
-
- def _boolean_input(self, question, default=None):
- result = input("%s " % question)
- if not result and default is not None:
- return default
- while len(result) < 1 or result[0].lower() not in "yn":
- result = input("Please answer yes or no: ")
- return result[0].lower() == "y"
-
- def _choice_input(self, question, choices):
- print(question)
- for i, choice in enumerate(choices):
- print(" %s) %s" % (i + 1, choice))
- result = input("Select an option: ")
- while True:
- try:
- value = int(result)
- if 0 < value <= len(choices):
- return value
- except ValueError:
- pass
- result = input("Please select a valid option: ")
-
- def ask_initial(self, app_label):
- "Should we create an initial migration for the app?"
- # If it was specified on the command line, definitely true
- if app_label in self.specified_apps:
- return True
- # Otherwise, we look to see if it has a migrations module
- # without any Python files in it, apart from __init__.py.
- # Apps from the new app template will have these; the python
- # file check will ensure we skip South ones.
- models_module = cache.get_app(app_label)
- migrations_import_path = "%s.migrations" % models_module.__package__
- try:
- migrations_module = importlib.import_module(migrations_import_path)
- except ImportError:
- return False
- else:
- filenames = os.listdir(os.path.dirname(migrations_module.__file__))
- return not any(x.endswith(".py") for x in filenames if x != "__init__.py")
-
- def ask_not_null_addition(self, field_name, model_name):
- "Adding a NOT NULL field to a model"
- choice = self._choice_input(
- "You are trying to add a non-nullable field '%s' to %s without a default;\n" % (field_name, model_name) +
- "this is not possible. Please select a fix:",
- [
- "Provide a one-off default now (will be set on all existing rows)",
- "Quit, and let me add a default in models.py",
- ]
- )
- if choice == 2:
- sys.exit(3)
- else:
- print("Please enter the default value now, as valid Python")
- print("The datetime module is available, so you can do e.g. datetime.date.today()")
- while True:
- code = input(">>> ")
- if not code:
- print("Please enter some code, or 'exit' (with no quotes) to exit.")
- elif code == "exit":
- sys.exit(1)
- else:
- try:
- return eval(code, {}, {"datetime": datetime_safe})
- except (SyntaxError, NameError) as e:
- print("Invalid input: %s" % e)
- else:
- break
-
- def ask_rename(self, model_name, old_name, new_name, field_instance):
- "Was this field really renamed?"
- return self._boolean_input("Did you rename %s.%s to %s.%s (a %s)? [y/N]" % (model_name, old_name, model_name, new_name, field_instance.__class__.__name__), False)
diff --git a/django/db/migrations/loader.py b/django/db/migrations/loader.py
index 598c582fa0..4c12e05add 100644
--- a/django/db/migrations/loader.py
+++ b/django/db/migrations/loader.py
@@ -1,7 +1,8 @@
+from importlib import import_module
import os
import sys
-from importlib import import_module
-from django.db.models.loading import cache
+
+from django.core.apps import app_cache
from django.db.migrations.recorder import MigrationRecorder
from django.db.migrations.graph import MigrationGraph
from django.utils import six
@@ -45,7 +46,7 @@ class MigrationLoader(object):
if app_label in settings.MIGRATION_MODULES:
return settings.MIGRATION_MODULES[app_label]
else:
- return '%s.migrations' % cache.get_app_package(app_label)
+ return '%s.migrations' % app_cache.get_app_config(app_label).name
def load_disk(self):
"""
@@ -54,10 +55,9 @@ class MigrationLoader(object):
self.disk_migrations = {}
self.unmigrated_apps = set()
self.migrated_apps = set()
- for app in cache.get_apps():
+ for app_config in app_cache.get_app_configs(only_with_models_module=True):
# Get the migrations module directory
- app_label = app.__name__.split(".")[-2]
- module_name = self.migrations_module(app_label)
+ module_name = self.migrations_module(app_config.label)
was_loaded = module_name in sys.modules
try:
module = import_module(module_name)
@@ -65,7 +65,7 @@ class MigrationLoader(object):
# I hate doing this, but I don't want to squash other import errors.
# Might be better to try a directory check directly.
if "No module named" in str(e) and "migrations" in str(e):
- self.unmigrated_apps.add(app_label)
+ self.unmigrated_apps.add(app_config.label)
continue
raise
else:
@@ -78,7 +78,7 @@ class MigrationLoader(object):
# Force a reload if it's already loaded (tests need this)
if was_loaded:
six.moves.reload_module(module)
- self.migrated_apps.add(app_label)
+ self.migrated_apps.add(app_config.label)
directory = os.path.dirname(module.__file__)
# Scan for .py[c|o] files
migration_names = set()
@@ -99,14 +99,14 @@ class MigrationLoader(object):
break
raise
if not hasattr(migration_module, "Migration"):
- raise BadMigrationError("Migration %s in app %s has no Migration class" % (migration_name, app_label))
+ raise BadMigrationError("Migration %s in app %s has no Migration class" % (migration_name, app_config.label))
# Ignore South-style migrations
if hasattr(migration_module.Migration, "forwards"):
south_style_migrations = True
break
- self.disk_migrations[app_label, migration_name] = migration_module.Migration(migration_name, app_label)
+ self.disk_migrations[app_config.label, migration_name] = migration_module.Migration(migration_name, app_config.label)
if south_style_migrations:
- self.unmigrated_apps.add(app_label)
+ self.unmigrated_apps.add(app_config.label)
def get_migration(self, app_label, name_prefix):
"Gets the migration exactly named, or raises KeyError"
@@ -187,6 +187,20 @@ class MigrationLoader(object):
for parent in migration.dependencies:
self.graph.add_dependency(key, parent)
+ def detect_conflicts(self):
+ """
+ Looks through the loaded graph and detects any conflicts - apps
+ with more than one leaf migration. Returns a dict of the app labels
+ that conflict with the migration names that conflict.
+ """
+ seen_apps = {}
+ conflicting_apps = set()
+ for app_label, migration_name in self.graph.leaf_nodes():
+ if app_label in seen_apps:
+ conflicting_apps.add(app_label)
+ seen_apps.setdefault(app_label, set()).add(migration_name)
+ return dict((app_label, seen_apps[app_label]) for app_label in conflicting_apps)
+
class BadMigrationError(Exception):
"""
diff --git a/django/db/migrations/operations/fields.py b/django/db/migrations/operations/fields.py
index b609110f85..c5f0bd1e2b 100644
--- a/django/db/migrations/operations/fields.py
+++ b/django/db/migrations/operations/fields.py
@@ -1,4 +1,5 @@
from django.db import router
+from django.db.models.fields import NOT_PROVIDED
from .base import Operation
@@ -7,13 +8,20 @@ class AddField(Operation):
Adds a field to a model.
"""
- def __init__(self, model_name, name, field):
+ def __init__(self, model_name, name, field, preserve_default=True):
self.model_name = model_name
self.name = name
self.field = field
+ self.preserve_default = preserve_default
def state_forwards(self, app_label, state):
- state.models[app_label, self.model_name.lower()].fields.append((self.name, self.field))
+ # If preserve default is off, don't use the default for future state
+ if not self.preserve_default:
+ field = self.field.clone()
+ field.default = NOT_PROVIDED
+ else:
+ field = self.field
+ state.models[app_label, self.model_name.lower()].fields.append((self.name, field))
def database_forwards(self, app_label, schema_editor, from_state, to_state):
from_model = from_state.render().get_model(app_label, self.model_name)
diff --git a/django/db/migrations/optimizer.py b/django/db/migrations/optimizer.py
index 435ac6766e..3e9dc42aa3 100644
--- a/django/db/migrations/optimizer.py
+++ b/django/db/migrations/optimizer.py
@@ -176,12 +176,14 @@ class MigrationOptimizer(object):
Folds a model rename into its create
"""
if operation.name.lower() == other.old_name.lower():
- return [migrations.CreateModel(
- other.new_name,
- fields=operation.fields,
- options=operation.options,
- bases=operation.bases,
- )]
+ return [
+ migrations.CreateModel(
+ other.new_name,
+ fields=operation.fields,
+ options=operation.options,
+ bases=operation.bases,
+ )
+ ]
def reduce_model_rename_self(self, operation, other):
"""
@@ -197,57 +199,67 @@ class MigrationOptimizer(object):
def reduce_create_model_add_field(self, operation, other):
if operation.name.lower() == other.model_name.lower():
- return [migrations.CreateModel(
- operation.name,
- fields=operation.fields + [(other.name, other.field)],
- options=operation.options,
- bases=operation.bases,
- )]
+ return [
+ migrations.CreateModel(
+ operation.name,
+ fields=operation.fields + [(other.name, other.field)],
+ options=operation.options,
+ bases=operation.bases,
+ )
+ ]
def reduce_create_model_alter_field(self, operation, other):
if operation.name.lower() == other.model_name.lower():
- return [migrations.CreateModel(
- operation.name,
- fields=[
- (n, other.field if n == other.name else v)
- for n, v in operation.fields
- ],
- options=operation.options,
- bases=operation.bases,
- )]
+ return [
+ migrations.CreateModel(
+ operation.name,
+ fields=[
+ (n, other.field if n == other.name else v)
+ for n, v in operation.fields
+ ],
+ options=operation.options,
+ bases=operation.bases,
+ )
+ ]
def reduce_create_model_rename_field(self, operation, other):
if operation.name.lower() == other.model_name.lower():
- return [migrations.CreateModel(
- operation.name,
- fields=[
- (other.new_name if n == other.old_name else n, v)
- for n, v in operation.fields
- ],
- options=operation.options,
- bases=operation.bases,
- )]
+ return [
+ migrations.CreateModel(
+ operation.name,
+ fields=[
+ (other.new_name if n == other.old_name else n, v)
+ for n, v in operation.fields
+ ],
+ options=operation.options,
+ bases=operation.bases,
+ )
+ ]
def reduce_create_model_remove_field(self, operation, other):
if operation.name.lower() == other.model_name.lower():
- return [migrations.CreateModel(
- operation.name,
- fields=[
- (n, v)
- for n, v in operation.fields
- if n.lower() != other.name.lower()
- ],
- options=operation.options,
- bases=operation.bases,
- )]
+ return [
+ migrations.CreateModel(
+ operation.name,
+ fields=[
+ (n, v)
+ for n, v in operation.fields
+ if n.lower() != other.name.lower()
+ ],
+ options=operation.options,
+ bases=operation.bases,
+ )
+ ]
def reduce_add_field_alter_field(self, operation, other):
if operation.model_name.lower() == other.model_name.lower() and operation.name.lower() == other.name.lower():
- return [migrations.AddField(
- model_name=operation.model_name,
- name=operation.name,
- field=other.field,
- )]
+ return [
+ migrations.AddField(
+ model_name=operation.model_name,
+ name=operation.name,
+ field=other.field,
+ )
+ ]
def reduce_add_field_delete_field(self, operation, other):
if operation.model_name.lower() == other.model_name.lower() and operation.name.lower() == other.name.lower():
@@ -259,11 +271,13 @@ class MigrationOptimizer(object):
def reduce_add_field_rename_field(self, operation, other):
if operation.model_name.lower() == other.model_name.lower() and operation.name.lower() == other.old_name.lower():
- return [migrations.AddField(
- model_name=operation.model_name,
- name=other.new_name,
- field=operation.field,
- )]
+ return [
+ migrations.AddField(
+ model_name=operation.model_name,
+ name=other.new_name,
+ field=operation.field,
+ )
+ ]
def reduce_alter_field_rename_field(self, operation, other):
if operation.model_name.lower() == other.model_name.lower() and operation.name.lower() == other.old_name.lower():
diff --git a/django/db/migrations/questioner.py b/django/db/migrations/questioner.py
new file mode 100644
index 0000000000..8a11559993
--- /dev/null
+++ b/django/db/migrations/questioner.py
@@ -0,0 +1,119 @@
+import importlib
+import os
+import sys
+
+from django.core.apps import app_cache
+from django.utils import datetime_safe
+from django.utils.six.moves import input
+
+
+class MigrationQuestioner(object):
+ """
+ Gives the autodetector responses to questions it might have.
+ This base class has a built-in noninteractive mode, but the
+ interactive subclass is what the command-line arguments will use.
+ """
+
+ def __init__(self, defaults=None, specified_apps=None):
+ self.defaults = defaults or {}
+ self.specified_apps = specified_apps or set()
+
+ def ask_initial(self, app_label):
+ "Should we create an initial migration for the app?"
+ # If it was specified on the command line, definitely true
+ if app_label in self.specified_apps:
+ return True
+ # Otherwise, we look to see if it has a migrations module
+ # without any Python files in it, apart from __init__.py.
+ # Apps from the new app template will have these; the python
+ # file check will ensure we skip South ones.
+ try:
+ app_config = app_cache.get_app_config(app_label)
+ except LookupError: # It's a fake app.
+ return self.defaults.get("ask_initial", False)
+ migrations_import_path = "%s.migrations" % app_config.name
+ try:
+ migrations_module = importlib.import_module(migrations_import_path)
+ except ImportError:
+ return self.defaults.get("ask_initial", False)
+ else:
+ filenames = os.listdir(os.path.dirname(migrations_module.__file__))
+ return not any(x.endswith(".py") for x in filenames if x != "__init__.py")
+
+ def ask_not_null_addition(self, field_name, model_name):
+ "Adding a NOT NULL field to a model"
+ # None means quit
+ return None
+
+ def ask_rename(self, model_name, old_name, new_name, field_instance):
+ "Was this field really renamed?"
+ return self.defaults.get("ask_rename", False)
+
+ def ask_merge(self, app_label):
+ "Do you really want to merge these migrations?"
+ return self.defaults.get("ask_merge", False)
+
+
+class InteractiveMigrationQuestioner(MigrationQuestioner):
+
+ def _boolean_input(self, question, default=None):
+ result = input("%s " % question)
+ if not result and default is not None:
+ return default
+ while len(result) < 1 or result[0].lower() not in "yn":
+ result = input("Please answer yes or no: ")
+ return result[0].lower() == "y"
+
+ def _choice_input(self, question, choices):
+ print(question)
+ for i, choice in enumerate(choices):
+ print(" %s) %s" % (i + 1, choice))
+ result = input("Select an option: ")
+ while True:
+ try:
+ value = int(result)
+ if 0 < value <= len(choices):
+ return value
+ except ValueError:
+ pass
+ result = input("Please select a valid option: ")
+
+ def ask_not_null_addition(self, field_name, model_name):
+ "Adding a NOT NULL field to a model"
+ choice = self._choice_input(
+ "You are trying to add a non-nullable field '%s' to %s without a default;\n" % (field_name, model_name) +
+ "we can't do that (the database needs something to populate existing rows).\n" +
+ "Please select a fix:",
+ [
+ "Provide a one-off default now (will be set on all existing rows)",
+ "Quit, and let me add a default in models.py",
+ ]
+ )
+ if choice == 2:
+ sys.exit(3)
+ else:
+ print("Please enter the default value now, as valid Python")
+ print("The datetime module is available, so you can do e.g. datetime.date.today()")
+ while True:
+ code = input(">>> ")
+ if not code:
+ print("Please enter some code, or 'exit' (with no quotes) to exit.")
+ elif code == "exit":
+ sys.exit(1)
+ else:
+ try:
+ return eval(code, {}, {"datetime": datetime_safe})
+ except (SyntaxError, NameError) as e:
+ print("Invalid input: %s" % e)
+
+ def ask_rename(self, model_name, old_name, new_name, field_instance):
+ "Was this field really renamed?"
+ return self._boolean_input("Did you rename %s.%s to %s.%s (a %s)? [y/N]" % (model_name, old_name, model_name, new_name, field_instance.__class__.__name__), False)
+
+ def ask_merge(self, app_label):
+ return self._boolean_input(
+ "\nMerging will only work if the operations printed above do not conflict\n" +
+ "with each other (working on different fields or models)\n" +
+ "Do you want to merge these migration branches? [y/N]",
+ False,
+ )
diff --git a/django/db/migrations/recorder.py b/django/db/migrations/recorder.py
index be804e52aa..c2cfd2e9af 100644
--- a/django/db/migrations/recorder.py
+++ b/django/db/migrations/recorder.py
@@ -1,5 +1,5 @@
+from django.core.apps.cache import AppCache
from django.db import models
-from django.db.models.loading import BaseAppCache
from django.utils.timezone import now
@@ -22,7 +22,7 @@ class MigrationRecorder(object):
applied = models.DateTimeField(default=now)
class Meta:
- app_cache = BaseAppCache()
+ app_cache = AppCache()
app_label = "migrations"
db_table = "django_migrations"
diff --git a/django/db/migrations/state.py b/django/db/migrations/state.py
index f5019f65ee..c43728d58e 100644
--- a/django/db/migrations/state.py
+++ b/django/db/migrations/state.py
@@ -1,6 +1,6 @@
+from django.core.apps.cache import AppCache
from django.db import models
-from django.db.models.loading import BaseAppCache
-from django.db.models.options import DEFAULT_NAMES
+from django.db.models.options import DEFAULT_NAMES, normalize_unique_together
from django.utils import six
from django.utils.module_loading import import_by_path
@@ -32,7 +32,7 @@ class ProjectState(object):
def render(self):
"Turns the project state into actual models in a new AppCache"
if self.app_cache is None:
- self.app_cache = BaseAppCache()
+ self.app_cache = AppCache()
# We keep trying to render the models in a loop, ignoring invalid
# base errors, until the size of the unrendered models doesn't
# decrease by at least one, meaning there's a base dependency loop/
@@ -99,11 +99,26 @@ class ModelState(object):
for field in model._meta.local_fields:
name, path, args, kwargs = field.deconstruct()
field_class = import_by_path(path)
- fields.append((name, field_class(*args, **kwargs)))
+ try:
+ fields.append((name, field_class(*args, **kwargs)))
+ except TypeError as e:
+ raise TypeError("Couldn't reconstruct field %s on %s.%s: %s" % (
+ name,
+ model._meta.app_label,
+ model._meta.object_name,
+ e,
+ ))
for field in model._meta.local_many_to_many:
name, path, args, kwargs = field.deconstruct()
field_class = import_by_path(path)
- fields.append((name, field_class(*args, **kwargs)))
+ try:
+ fields.append((name, field_class(*args, **kwargs)))
+ except TypeError as e:
+ raise TypeError("Couldn't reconstruct m2m field %s on %s: %s" % (
+ name,
+ model._meta.object_name,
+ e,
+ ))
# Extract the options
options = {}
for name in DEFAULT_NAMES:
@@ -112,7 +127,8 @@ class ModelState(object):
continue
elif name in model._meta.original_attrs:
if name == "unique_together":
- options[name] = set(model._meta.original_attrs["unique_together"])
+ ut = model._meta.original_attrs["unique_together"]
+ options[name] = set(normalize_unique_together(ut))
else:
options[name] = model._meta.original_attrs[name]
# Make our record
@@ -162,7 +178,7 @@ class ModelState(object):
for base in self.bases
)
if None in bases:
- raise InvalidBasesError("Cannot resolve one or more bases from %r" % self.bases)
+ raise InvalidBasesError("Cannot resolve one or more bases from %r" % (self.bases,))
# Turn fields into a dict for the body, add other bits
body = dict(self.fields)
body['Meta'] = meta
diff --git a/django/db/migrations/writer.py b/django/db/migrations/writer.py
index 22b0977ba4..04e00501f9 100644
--- a/django/db/migrations/writer.py
+++ b/django/db/migrations/writer.py
@@ -1,14 +1,16 @@
from __future__ import unicode_literals
+
import datetime
-import types
-import os
from importlib import import_module
-from django.utils import six
+import os
+import types
+
+from django.core.apps import app_cache
from django.db import models
-from django.db.models.loading import cache
from django.db.migrations.loader import MigrationLoader
from django.utils.encoding import force_text
from django.utils.functional import Promise
+from django.utils import six
class MigrationWriter(object):
@@ -67,14 +69,12 @@ class MigrationWriter(object):
migrations_module = import_module(migrations_package_name)
basedir = os.path.dirname(migrations_module.__file__)
except ImportError:
- app = cache.get_app(self.migration.app_label)
- app_path = cache._get_app_path(app)
- app_package_name = cache._get_app_package(app)
+ app_config = app_cache.get_app_config(self.migration.app_label)
migrations_package_basename = migrations_package_name.split(".")[-1]
# Alright, see if it's a direct submodule of the app
- if '%s.%s' % (app_package_name, migrations_package_basename) == migrations_package_name:
- basedir = os.path.join(app_path, migrations_package_basename)
+ if '%s.%s' % (app_config.name, migrations_package_basename) == migrations_package_name:
+ basedir = os.path.join(app_config.path, migrations_package_basename)
else:
raise ImportError("Cannot open migrations module %s for app %s" % (migrations_package_name, self.migration.app_label))
return os.path.join(basedir, self.filename)
diff --git a/django/db/models/__init__.py b/django/db/models/__init__.py
index e2f7348ef0..454a693be7 100644
--- a/django/db/models/__init__.py
+++ b/django/db/models/__init__.py
@@ -1,9 +1,8 @@
from functools import wraps
+import sys
+import warnings
from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured # NOQA
-from django.db.models.loading import ( # NOQA
- get_apps, get_app_path, get_app_paths, get_app, get_models, get_model,
- register_models, UnavailableApp)
from django.db.models.query import Q, QuerySet, Prefetch # NOQA
from django.db.models.expressions import F # NOQA
from django.db.models.manager import Manager # NOQA
@@ -15,6 +14,7 @@ from django.db.models.fields.files import FileField, ImageField # NOQA
from django.db.models.fields.related import ( # NOQA
ForeignKey, ForeignObject, OneToOneField, ManyToManyField,
ManyToOneRel, ManyToManyRel, OneToOneRel)
+from django.db.models.fields.proxy import OrderWrt # NOQA
from django.db.models.deletion import ( # NOQA
CASCADE, PROTECT, SET, SET_NULL, SET_DEFAULT, DO_NOTHING, ProtectedError)
from django.db.models import signals # NOQA
@@ -37,3 +37,26 @@ def permalink(func):
bits = func(*args, **kwargs)
return reverse(bits[0], None, *bits[1:3])
return inner
+
+
+# Deprecated aliases for functions were exposed in this module.
+
+def make_alias(function_name):
+ # Close function_name.
+ def alias(*args, **kwargs):
+ warnings.warn(
+ "django.db.models.%s is deprecated." % function_name,
+ PendingDeprecationWarning, stacklevel=2)
+ # This raises a second warning.
+ from . import loading
+ return getattr(loading, function_name)(*args, **kwargs)
+ alias.__name__ = function_name
+ return alias
+
+this_module = sys.modules['django.db.models']
+
+for function_name in ('get_apps', 'get_app_path', 'get_app_paths', 'get_app',
+ 'get_models', 'get_model', 'register_models'):
+ setattr(this_module, function_name, make_alias(function_name))
+
+del this_module, make_alias, function_name
diff --git a/django/db/models/base.py b/django/db/models/base.py
index ce3f095055..56973d4e38 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -5,6 +5,8 @@ import sys
from functools import update_wrapper
from django.utils.six.moves import zip
+from django.core.apps import app_cache
+from django.core.apps.cache import MODELS_MODULE_NAME
import django.db.models.manager # NOQA: Imported to register signal handler.
from django.conf import settings
from django.core.exceptions import (ObjectDoesNotExist,
@@ -19,7 +21,6 @@ from django.db.models.query_utils import DeferredAttribute, deferred_class_facto
from django.db.models.deletion import Collector
from django.db.models.options import Options
from django.db.models import signals
-from django.db.models.loading import get_model, MODELS_MODULE_NAME
from django.utils.translation import ugettext_lazy as _
from django.utils.functional import curry
from django.utils.encoding import force_str, force_text
@@ -160,9 +161,11 @@ class ModelBase(type):
new_class.add_to_class(obj_name, obj)
# All the fields of any type declared on this model
- new_fields = new_class._meta.local_fields + \
- new_class._meta.local_many_to_many + \
- new_class._meta.virtual_fields
+ new_fields = (
+ new_class._meta.local_fields +
+ new_class._meta.local_many_to_many +
+ new_class._meta.virtual_fields
+ )
field_names = set(f.name for f in new_fields)
# Basic setup for proxy models.
@@ -216,10 +219,11 @@ class ModelBase(type):
# moment).
for field in parent_fields:
if field.name in field_names:
- raise FieldError('Local field %r in class %r clashes '
- 'with field of similar name from '
- 'base class %r' %
- (field.name, name, base.__name__))
+ raise FieldError(
+ 'Local field %r in class %r clashes '
+ 'with field of similar name from '
+ 'base class %r' % (field.name, name, base.__name__)
+ )
if not base._meta.abstract:
# Concrete classes...
base = base._meta.concrete_model
@@ -253,10 +257,11 @@ class ModelBase(type):
# class
for field in base._meta.virtual_fields:
if base._meta.abstract and field.name in field_names:
- raise FieldError('Local field %r in class %r clashes '
- 'with field of similar name from '
- 'abstract base class %r' %
- (field.name, name, base.__name__))
+ raise FieldError(
+ 'Local field %r in class %r clashes '
+ 'with field of similar name from '
+ 'abstract base class %r' % (field.name, name, base.__name__)
+ )
new_class.add_to_class(field.name, copy.deepcopy(field))
if abstract:
@@ -269,7 +274,7 @@ class ModelBase(type):
new_class._prepare()
- new_class._meta.app_cache.register_models(new_class._meta.app_label, new_class)
+ new_class._meta.app_cache.register_model(new_class._meta.app_label, new_class)
# Because of the way imports happen (recursively), we may or may not be
# the first time this model tries to register with the framework. There
# should only be one class for each model, so we always return the
@@ -987,7 +992,7 @@ class Model(six.with_metaclass(ModelBase)):
def clean_fields(self, exclude=None):
"""
- Cleans all fields and raises a ValidationError containing message_dict
+ Cleans all fields and raises a ValidationError containing a dict
of all validation errors if any occur.
"""
if exclude is None:
@@ -1062,7 +1067,7 @@ def model_unpickle(model_id, attrs, factory):
Used to unpickle Model subclasses with deferred fields.
"""
if isinstance(model_id, tuple):
- model = get_model(*model_id)
+ model = app_cache.get_model(*model_id)
else:
# Backwards compat - the model was cached directly in earlier versions.
model = model_id
diff --git a/django/db/models/deletion.py b/django/db/models/deletion.py
index 90e515cce5..8b48815a0d 100644
--- a/django/db/models/deletion.py
+++ b/django/db/models/deletion.py
@@ -135,7 +135,7 @@ class Collector(object):
# Foreign keys pointing to this model, both from m2m and other
# models.
for related in opts.get_all_related_objects(
- include_hidden=True, include_proxy_eq=True):
+ include_hidden=True, include_proxy_eq=True):
if related.field.rel.on_delete is not DO_NOTHING:
return False
# GFK deletes
@@ -145,7 +145,7 @@ class Collector(object):
return True
def collect(self, objs, source=None, nullable=False, collect_related=True,
- source_attr=None, reverse_dependency=False):
+ source_attr=None, reverse_dependency=False):
"""
Adds 'objs' to the collection of objects to be deleted as well as all
parent instances. 'objs' must be a homogenous iterable collection of
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index 85985ab2e5..a1b8ded06e 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -10,8 +10,8 @@ import warnings
from base64 import b64decode, b64encode
from itertools import tee
+from django.core.apps import app_cache
from django.db import connection
-from django.db.models.loading import get_model
from django.db.models.lookups import default_lookups
from django.db.models.query_utils import QueryWrapper
from django.conf import settings
@@ -53,7 +53,7 @@ BLANK_CHOICE_DASH = [("", "---------")]
def _load_field(app_label, model_name, field_name):
- return get_model(app_label, model_name)._meta.get_field_by_name(field_name)[0]
+ return app_cache.get_model(app_label, model_name)._meta.get_field_by_name(field_name)[0]
class FieldDoesNotExist(Exception):
@@ -233,6 +233,8 @@ class Field(object):
path = path.replace("django.db.models.fields.related", "django.db.models")
if path.startswith("django.db.models.fields.files"):
path = path.replace("django.db.models.fields.files", "django.db.models")
+ if path.startswith("django.db.models.fields.proxy"):
+ path = path.replace("django.db.models.fields.proxy", "django.db.models")
if path.startswith("django.db.models.fields"):
path = path.replace("django.db.models.fields", "django.db.models")
# Return basic info - other fields should override this.
@@ -243,6 +245,14 @@ class Field(object):
keywords,
)
+ def clone(self):
+ """
+ Uses deconstruct() to clone a new copy of this Field.
+ Will not preserve any class attachments/attribute names.
+ """
+ name, path, args, kwargs = self.deconstruct()
+ return self.__class__(*args, **kwargs)
+
def __eq__(self, other):
# Needed for @total_ordering
if isinstance(other, Field):
@@ -621,7 +631,7 @@ class Field(object):
rel_model = self.rel.to
if hasattr(self.rel, 'get_related_field'):
lst = [(getattr(x, self.rel.get_related_field().attname),
- smart_text(x))
+ smart_text(x))
for x in rel_model._default_manager.complex_filter(
self.rel.limit_choices_to)]
else:
@@ -1309,7 +1319,7 @@ class IntegerField(Field):
def get_prep_lookup(self, lookup_type, value):
if ((lookup_type == 'gte' or lookup_type == 'lt')
- and isinstance(value, float)):
+ and isinstance(value, float)):
value = math.ceil(value)
return super(IntegerField, self).get_prep_lookup(lookup_type, value)
@@ -1704,8 +1714,7 @@ class BinaryField(Field):
return default
def get_db_prep_value(self, value, connection, prepared=False):
- value = super(BinaryField, self
- ).get_db_prep_value(value, connection, prepared)
+ value = super(BinaryField, self).get_db_prep_value(value, connection, prepared)
if value is not None:
return connection.Database.Binary(value)
return value
diff --git a/django/db/models/fields/proxy.py b/django/db/models/fields/proxy.py
index 29c782b59a..19beb89011 100644
--- a/django/db/models/fields/proxy.py
+++ b/django/db/models/fields/proxy.py
@@ -16,3 +16,8 @@ class OrderWrt(fields.IntegerField):
kwargs['name'] = '_order'
kwargs['editable'] = False
super(OrderWrt, self).__init__(*args, **kwargs)
+
+ def deconstruct(self):
+ name, path, args, kwargs = super(OrderWrt, self).deconstruct()
+ del kwargs['editable']
+ return name, path, args, kwargs
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index a2d0a8dcd0..2d3e4bc448 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -158,6 +158,16 @@ class SingleRelatedObjectDescriptor(six.with_metaclass(RenameRelatedObjectDescri
self.related = related
self.cache_name = related.get_cache_name()
+ @cached_property
+ def RelatedObjectDoesNotExist(self):
+ # The exception isn't created at initialization time for the sake of
+ # consistency with `ReverseSingleRelatedObjectDescriptor`.
+ return type(
+ str('RelatedObjectDoesNotExist'),
+ (self.related.model.DoesNotExist, AttributeError),
+ {}
+ )
+
def is_cached(self, instance):
return hasattr(instance, self.cache_name)
@@ -202,9 +212,12 @@ class SingleRelatedObjectDescriptor(six.with_metaclass(RenameRelatedObjectDescri
setattr(rel_obj, self.related.field.get_cache_name(), instance)
setattr(instance, self.cache_name, rel_obj)
if rel_obj is None:
- raise self.related.model.DoesNotExist("%s has no %s." % (
- instance.__class__.__name__,
- self.related.get_accessor_name()))
+ raise self.RelatedObjectDoesNotExist(
+ "%s has no %s." % (
+ instance.__class__.__name__,
+ self.related.get_accessor_name()
+ )
+ )
else:
return rel_obj
@@ -216,12 +229,21 @@ class SingleRelatedObjectDescriptor(six.with_metaclass(RenameRelatedObjectDescri
# If null=True, we can assign null here, but otherwise the value needs
# to be an instance of the related class.
if value is None and self.related.field.null is False:
- raise ValueError('Cannot assign None: "%s.%s" does not allow null values.' %
- (instance._meta.object_name, self.related.get_accessor_name()))
+ raise ValueError(
+ 'Cannot assign None: "%s.%s" does not allow null values.' % (
+ instance._meta.object_name,
+ self.related.get_accessor_name(),
+ )
+ )
elif value is not None and not isinstance(value, self.related.model):
- raise ValueError('Cannot assign "%r": "%s.%s" must be a "%s" instance.' %
- (value, instance._meta.object_name,
- self.related.get_accessor_name(), self.related.opts.object_name))
+ raise ValueError(
+ 'Cannot assign "%r": "%s.%s" must be a "%s" instance.' % (
+ value,
+ instance._meta.object_name,
+ self.related.get_accessor_name(),
+ self.related.opts.object_name,
+ )
+ )
elif value is not None:
if instance._state.db is None:
instance._state.db = router.db_for_write(instance.__class__, instance=value)
@@ -233,8 +255,10 @@ class SingleRelatedObjectDescriptor(six.with_metaclass(RenameRelatedObjectDescri
related_pk = tuple(getattr(instance, field.attname) for field in self.related.field.foreign_related_fields)
if None in related_pk:
- raise ValueError('Cannot assign "%r": "%s" instance isn\'t saved in the database.' %
- (value, instance._meta.object_name))
+ raise ValueError(
+ 'Cannot assign "%r": "%s" instance isn\'t saved in the database.' %
+ (value, instance._meta.object_name)
+ )
# Set the value of the related field to the value of the related object's related field
for index, field in enumerate(self.related.field.local_related_fields):
@@ -257,6 +281,17 @@ class ReverseSingleRelatedObjectDescriptor(six.with_metaclass(RenameRelatedObjec
self.field = field_with_rel
self.cache_name = self.field.get_cache_name()
+ @cached_property
+ def RelatedObjectDoesNotExist(self):
+ # The exception can't be created at initialization time since the
+ # related model might not be resolved yet; `rel.to` might still be
+ # a string model reference.
+ return type(
+ str('RelatedObjectDoesNotExist'),
+ (self.field.rel.to.DoesNotExist, AttributeError),
+ {}
+ )
+
def is_cached(self, instance):
return hasattr(instance, self.cache_name)
@@ -323,8 +358,9 @@ class ReverseSingleRelatedObjectDescriptor(six.with_metaclass(RenameRelatedObjec
setattr(rel_obj, self.field.related.get_cache_name(), instance)
setattr(instance, self.cache_name, rel_obj)
if rel_obj is None and not self.field.null:
- raise self.field.rel.to.DoesNotExist(
- "%s has no %s." % (self.field.model.__name__, self.field.name))
+ raise self.RelatedObjectDoesNotExist(
+ "%s has no %s." % (self.field.model.__name__, self.field.name)
+ )
else:
return rel_obj
@@ -332,12 +368,19 @@ class ReverseSingleRelatedObjectDescriptor(six.with_metaclass(RenameRelatedObjec
# If null=True, we can assign null here, but otherwise the value needs
# to be an instance of the related class.
if value is None and self.field.null is False:
- raise ValueError('Cannot assign None: "%s.%s" does not allow null values.' %
- (instance._meta.object_name, self.field.name))
+ raise ValueError(
+ 'Cannot assign None: "%s.%s" does not allow null values.' %
+ (instance._meta.object_name, self.field.name)
+ )
elif value is not None and not isinstance(value, self.field.rel.to):
- raise ValueError('Cannot assign "%r": "%s.%s" must be a "%s" instance.' %
- (value, instance._meta.object_name,
- self.field.name, self.field.rel.to._meta.object_name))
+ raise ValueError(
+ 'Cannot assign "%r": "%s.%s" must be a "%s" instance.' % (
+ value,
+ instance._meta.object_name,
+ self.field.name,
+ self.field.rel.to._meta.object_name,
+ )
+ )
elif value is not None:
if instance._state.db is None:
instance._state.db = router.db_for_write(instance.__class__, instance=value)
@@ -494,7 +537,7 @@ def create_foreign_related_manager(superclass, rel_field, rel_model):
with transaction.commit_on_success_unless_managed(using=db, savepoint=False):
for obj in queryset:
setattr(obj, rel_field.name, None)
- obj.save()
+ obj.save(update_fields=[rel_field.name])
_clear.alters_data = True
return RelatedManager
@@ -683,7 +726,10 @@ def create_many_related_manager(superclass, rel):
# from the method lookup table, as we do with add and remove.
if not self.through._meta.auto_created:
opts = self.through._meta
- raise AttributeError("Cannot use create() on a ManyToManyField which specifies an intermediary model. Use %s.%s's Manager instead." % (opts.app_label, opts.object_name))
+ raise AttributeError(
+ "Cannot use create() on a ManyToManyField which specifies an intermediary model. Use %s.%s's Manager instead." %
+ (opts.app_label, opts.object_name)
+ )
db = router.db_for_write(self.instance.__class__, instance=self.instance)
new_obj = super(ManyRelatedManager, self.db_manager(db)).create(**kwargs)
self.add(new_obj)
@@ -713,16 +759,23 @@ def create_many_related_manager(superclass, rel):
for obj in objs:
if isinstance(obj, self.model):
if not router.allow_relation(obj, self.instance):
- raise ValueError('Cannot add "%r": instance is on database "%s", value is on database "%s"' %
- (obj, self.instance._state.db, obj._state.db))
+ raise ValueError(
+ 'Cannot add "%r": instance is on database "%s", value is on database "%s"' %
+ (obj, self.instance._state.db, obj._state.db)
+ )
fk_val = self.through._meta.get_field(
target_field_name).get_foreign_related_value(obj)[0]
if fk_val is None:
- raise ValueError('Cannot add "%r": the value for field "%s" is None' %
- (obj, target_field_name))
+ raise ValueError(
+ 'Cannot add "%r": the value for field "%s" is None' %
+ (obj, target_field_name)
+ )
new_ids.add(fk_val)
elif isinstance(obj, Model):
- raise TypeError("'%s' instance expected, got %r" % (self.model._meta.object_name, obj))
+ raise TypeError(
+ "'%s' instance expected, got %r" %
+ (self.model._meta.object_name, obj)
+ )
else:
new_ids.add(obj)
db = router.db_for_write(self.through, instance=self.instance)
@@ -970,8 +1023,7 @@ class OneToOneRel(ManyToOneRel):
parent_link=False, on_delete=None, related_query_name=None):
super(OneToOneRel, self).__init__(field, to, field_name,
related_name=related_name, limit_choices_to=limit_choices_to,
- parent_link=parent_link, on_delete=on_delete, related_query_name=related_query_name,
- )
+ parent_link=parent_link, on_delete=on_delete, related_query_name=related_query_name)
self.multiple = False
@@ -1026,6 +1078,16 @@ class ForeignObject(RelatedField):
super(ForeignObject, self).__init__(**kwargs)
+ def deconstruct(self):
+ name, path, args, kwargs = super(ForeignObject, self).deconstruct()
+ kwargs['from_fields'] = self.from_fields
+ kwargs['to_fields'] = self.to_fields
+ if isinstance(self.rel.to, six.string_types):
+ kwargs['to'] = self.rel.to
+ else:
+ kwargs['to'] = "%s.%s" % (self.rel.to._meta.app_label, self.rel.to._meta.object_name)
+ return name, path, args, kwargs
+
def resolve_related_fields(self):
if len(self.from_fields) < 1 or len(self.from_fields) != len(self.to_fields):
raise ValueError('Foreign Object from and to fields must be the same non-zero length')
@@ -1251,6 +1313,8 @@ class ForeignKey(ForeignObject):
def deconstruct(self):
name, path, args, kwargs = super(ForeignKey, self).deconstruct()
+ del kwargs['to_fields']
+ del kwargs['from_fields']
# Handle the simpler arguments
if self.db_index:
del kwargs['db_index']
@@ -1263,10 +1327,6 @@ class ForeignKey(ForeignObject):
# Rel needs more work.
if self.rel.field_name:
kwargs['to_field'] = self.rel.field_name
- if isinstance(self.rel.to, six.string_types):
- kwargs['to'] = self.rel.to
- else:
- kwargs['to'] = "%s.%s" % (self.rel.to._meta.app_label, self.rel.to._meta.object_name)
return name, path, args, kwargs
@property
@@ -1484,7 +1544,7 @@ class ManyToManyField(RelatedField):
name, path, args, kwargs = super(ManyToManyField, self).deconstruct()
# Handle the simpler arguments
if self.rel.db_constraint is not True:
- kwargs['db_constraint'] = self.db_constraint
+ kwargs['db_constraint'] = self.rel.db_constraint
if "help_text" in kwargs:
del kwargs['help_text']
# Rel needs more work.
diff --git a/django/db/models/loading.py b/django/db/models/loading.py
index ec32e86051..5745dbed4d 100644
--- a/django/db/models/loading.py
+++ b/django/db/models/loading.py
@@ -1,400 +1,35 @@
-"Utilities for loading models and the modules that contain them."
+import warnings
-from collections import OrderedDict
-import copy
-import imp
-from importlib import import_module
-import os
-import sys
+from django.core.apps import app_cache
-from django.conf import settings
-from django.core.exceptions import ImproperlyConfigured
-from django.utils.module_loading import module_has_submodule
-from django.utils._os import upath
-from django.utils import six
+warnings.warn(
+ "The utilities in django.db.models.loading are deprecated "
+ "in favor of the new application loading system.",
+ PendingDeprecationWarning, stacklevel=2)
__all__ = ('get_apps', 'get_app', 'get_models', 'get_model', 'register_models',
'load_app', 'app_cache_ready')
-MODELS_MODULE_NAME = 'models'
-
-
-class ModelDict(OrderedDict):
- """
- We need to special-case the deepcopy for this, as the keys are modules,
- which can't be deep copied.
- """
- def __deepcopy__(self, memo):
- return self.__class__([(key, copy.deepcopy(value, memo))
- for key, value in self.items()])
-
-
-class UnavailableApp(Exception):
- pass
-
-
-def _initialize():
- """
- Returns a dictionary to be used as the initial value of the
- [shared] state of the app cache.
- """
- return dict(
- # Keys of app_store are the model modules for each application.
- app_store=ModelDict(),
-
- # Mapping of installed app_labels to model modules for that app.
- app_labels={},
-
- # Mapping of app_labels to a dictionary of model names to model code.
- # May contain apps that are not installed.
- app_models=ModelDict(),
-
- # Mapping of app_labels to errors raised when trying to import the app.
- app_errors={},
-
- # Pending lookups for lazy relations
- pending_lookups={},
-
- # -- Everything below here is only used when populating the cache --
- loads_installed=True,
- loaded=False,
- handled=set(),
- postponed=[],
- nesting_level=0,
- _get_models_cache={},
- available_apps=None,
- )
-
-
-class BaseAppCache(object):
- """
- A cache that stores installed applications and their models. Used to
- provide reverse-relations and for app introspection (e.g. admin).
-
- This provides the base (non-Borg) AppCache class - the AppCache
- subclass adds borg-like behaviour for the few cases where it's needed,
- and adds the code that auto-loads from INSTALLED_APPS.
- """
-
- def __init__(self):
- self.__dict__ = _initialize()
- # This stops _populate loading from INSTALLED_APPS and ignores the
- # only_installed arguments to get_model[s]
- self.loads_installed = False
-
- def _populate(self):
- """
- Stub method - this base class does no auto-loading.
- """
- """
- Fill in all the cache information. This method is threadsafe, in the
- sense that every caller will see the same state upon return, and if the
- cache is already initialised, it does no work.
- """
- if self.loaded:
- return
- if not self.loads_installed:
- self.loaded = True
- return
- # Note that we want to use the import lock here - the app loading is
- # in many cases initiated implicitly by importing, and thus it is
- # possible to end up in deadlock when one thread initiates loading
- # without holding the importer lock and another thread then tries to
- # import something which also launches the app loading. For details of
- # this situation see #18251.
- imp.acquire_lock()
- try:
- if self.loaded:
- return
- for app_name in settings.INSTALLED_APPS:
- if app_name in self.handled:
- continue
- self.load_app(app_name, True)
- if not self.nesting_level:
- for app_name in self.postponed:
- self.load_app(app_name)
- self.loaded = True
- finally:
- imp.release_lock()
-
- def _label_for(self, app_mod):
- """
- Return app_label for given models module.
-
- """
- return app_mod.__name__.split('.')[-2]
-
- def load_app(self, app_name, can_postpone=False):
- """
- Loads the app with the provided fully qualified name, and returns the
- model module.
- """
- app_module = import_module(app_name)
- self.handled.add(app_name)
- self.nesting_level += 1
- try:
- models = import_module('%s.%s' % (app_name, MODELS_MODULE_NAME))
- except ImportError:
- self.nesting_level -= 1
- # If the app doesn't have a models module, we can just ignore the
- # ImportError and return no models for it.
- if not module_has_submodule(app_module, MODELS_MODULE_NAME):
- return None
- # But if the app does have a models module, we need to figure out
- # whether to suppress or propagate the error. If can_postpone is
- # True then it may be that the package is still being imported by
- # Python and the models module isn't available yet. So we add the
- # app to the postponed list and we'll try it again after all the
- # recursion has finished (in populate). If can_postpone is False
- # then it's time to raise the ImportError.
- else:
- if can_postpone:
- self.postponed.append(app_name)
- return None
- else:
- raise
-
- self.nesting_level -= 1
- if models not in self.app_store:
- self.app_store[models] = len(self.app_store)
- self.app_labels[self._label_for(models)] = models
- return models
-
- def app_cache_ready(self):
- """
- Returns true if the model cache is fully populated.
-
- Useful for code that wants to cache the results of get_models() for
- themselves once it is safe to do so.
- """
- return self.loaded
-
- def get_apps(self):
- """
- Returns a list of all installed modules that contain models.
- """
- self._populate()
-
- apps = self.app_store.items()
- if self.available_apps is not None:
- apps = [elt for elt in apps
- if self._label_for(elt[0]) in self.available_apps]
-
- # Ensure the returned list is always in the same order (with new apps
- # added at the end). This avoids unstable ordering on the admin app
- # list page, for example.
- apps = sorted(apps, key=lambda elt: elt[1])
-
- return [elt[0] for elt in apps]
-
- def _get_app_package(self, app):
- return '.'.join(app.__name__.split('.')[:-1])
-
- def get_app_package(self, app_label):
- return self._get_app_package(self.get_app(app_label))
-
- def _get_app_path(self, app):
- if hasattr(app, '__path__'): # models/__init__.py package
- app_path = app.__path__[0]
- else: # models.py module
- app_path = app.__file__
- return os.path.dirname(upath(app_path))
-
- def get_app_path(self, app_label):
- return self._get_app_path(self.get_app(app_label))
-
- def get_app_paths(self):
- """
- Returns a list of paths to all installed apps.
-
- Useful for discovering files at conventional locations inside apps
- (static files, templates, etc.)
- """
- self._populate()
-
- app_paths = []
- for app in self.get_apps():
- app_paths.append(self._get_app_path(app))
- return app_paths
-
- def get_app(self, app_label, emptyOK=False):
- """
- Returns the module containing the models for the given app_label.
-
- Returns None if the app has no models in it and emptyOK is True.
-
- Raises UnavailableApp when set_available_apps() in in effect and
- doesn't include app_label.
- """
- self._populate()
- imp.acquire_lock()
- try:
- for app_name in settings.INSTALLED_APPS:
- if app_label == app_name.split('.')[-1]:
- mod = self.load_app(app_name, False)
- if mod is None and not emptyOK:
- raise ImproperlyConfigured("App with label %s is missing a models.py module." % app_label)
- if self.available_apps is not None and app_label not in self.available_apps:
- raise UnavailableApp("App with label %s isn't available." % app_label)
- return mod
- raise ImproperlyConfigured("App with label %s could not be found" % app_label)
- finally:
- imp.release_lock()
-
- def get_app_errors(self):
- "Returns the map of known problems with the INSTALLED_APPS."
- self._populate()
- return self.app_errors
-
- def get_models(self, app_mod=None,
- include_auto_created=False, include_deferred=False,
- only_installed=True, include_swapped=False):
- """
- Given a module containing models, returns a list of the models.
- Otherwise returns a list of all installed models.
-
- By default, auto-created models (i.e., m2m models without an
- explicit intermediate table) are not included. However, if you
- specify include_auto_created=True, they will be.
-
- By default, models created to satisfy deferred attribute
- queries are *not* included in the list of models. However, if
- you specify include_deferred, they will be.
-
- By default, models that aren't part of installed apps will *not*
- be included in the list of models. However, if you specify
- only_installed=False, they will be. If you're using a non-default
- AppCache, this argument does nothing - all models will be included.
-
- By default, models that have been swapped out will *not* be
- included in the list of models. However, if you specify
- include_swapped, they will be.
- """
- if not self.loads_installed:
- only_installed = False
- cache_key = (app_mod, include_auto_created, include_deferred, only_installed, include_swapped)
- model_list = None
- try:
- model_list = self._get_models_cache[cache_key]
- if self.available_apps is not None and only_installed:
- model_list = [m for m in model_list
- if m._meta.app_label in self.available_apps]
- return model_list
- except KeyError:
- pass
- self._populate()
- if app_mod:
- if app_mod in self.app_store:
- app_list = [self.app_models.get(self._label_for(app_mod),
- ModelDict())]
- else:
- app_list = []
- else:
- if only_installed:
- app_list = [self.app_models.get(app_label, ModelDict())
- for app_label in six.iterkeys(self.app_labels)]
- else:
- app_list = six.itervalues(self.app_models)
- model_list = []
- for app in app_list:
- model_list.extend(
- model for model in app.values()
- if ((not model._deferred or include_deferred) and
- (not model._meta.auto_created or include_auto_created) and
- (not model._meta.swapped or include_swapped))
- )
- self._get_models_cache[cache_key] = model_list
- if self.available_apps is not None and only_installed:
- model_list = [m for m in model_list
- if m._meta.app_label in self.available_apps]
- return model_list
-
- def get_model(self, app_label, model_name,
- seed_cache=True, only_installed=True):
- """
- Returns the model matching the given app_label and case-insensitive
- model_name.
-
- Returns None if no model is found.
-
- Raises UnavailableApp when set_available_apps() in in effect and
- doesn't include app_label.
- """
- if not self.loads_installed:
- only_installed = False
- if seed_cache:
- self._populate()
- if only_installed and app_label not in self.app_labels:
- return None
- if (self.available_apps is not None and only_installed
- and app_label not in self.available_apps):
- raise UnavailableApp("App with label %s isn't available." % app_label)
- try:
- return self.app_models[app_label][model_name.lower()]
- except KeyError:
- return None
-
- def register_models(self, app_label, *models):
- """
- Register a set of models as belonging to an app.
- """
- for model in models:
- # Store as 'name: model' pair in a dictionary
- # in the app_models dictionary
- model_name = model._meta.model_name
- model_dict = self.app_models.setdefault(app_label, ModelDict())
- if model_name in model_dict:
- # The same model may be imported via different paths (e.g.
- # appname.models and project.appname.models). We use the source
- # filename as a means to detect identity.
- fname1 = os.path.abspath(upath(sys.modules[model.__module__].__file__))
- fname2 = os.path.abspath(upath(sys.modules[model_dict[model_name].__module__].__file__))
- # Since the filename extension could be .py the first time and
- # .pyc or .pyo the second time, ignore the extension when
- # comparing.
- if os.path.splitext(fname1)[0] == os.path.splitext(fname2)[0]:
- continue
- model_dict[model_name] = model
- self._get_models_cache.clear()
-
- def set_available_apps(self, available):
- if not set(available).issubset(set(settings.INSTALLED_APPS)):
- extra = set(available) - set(settings.INSTALLED_APPS)
- raise ValueError("Available apps isn't a subset of installed "
- "apps, extra apps: " + ", ".join(extra))
- self.available_apps = set(app.rsplit('.', 1)[-1] for app in available)
-
- def unset_available_apps(self):
- self.available_apps = None
-
-
-class AppCache(BaseAppCache):
- """
- A cache that stores installed applications and their models. Used to
- provide reverse-relations and for app introspection (e.g. admin).
-
- Borg version of the BaseAppCache class.
- """
-
- __shared_state = _initialize()
-
- def __init__(self):
- self.__dict__ = self.__shared_state
-
-
-cache = AppCache()
-
-
# These methods were always module level, so are kept that way for backwards
# compatibility.
-get_apps = cache.get_apps
-get_app_package = cache.get_app_package
-get_app_path = cache.get_app_path
-get_app_paths = cache.get_app_paths
-get_app = cache.get_app
-get_app_errors = cache.get_app_errors
-get_models = cache.get_models
-get_model = cache.get_model
-register_models = cache.register_models
-load_app = cache.load_app
-app_cache_ready = cache.app_cache_ready
+get_apps = app_cache.get_apps
+get_app_package = app_cache.get_app_package
+get_app_path = app_cache.get_app_path
+get_app_paths = app_cache.get_app_paths
+get_app = app_cache.get_app
+get_models = app_cache.get_models
+get_model = app_cache.get_model
+register_models = app_cache.register_models
+load_app = app_cache.load_app
+app_cache_ready = app_cache.app_cache_ready
+
+
+# This method doesn't return anything interesting in Django 1.6. Maintain it
+# just for backwards compatibility until this module is deprecated.
+def get_app_errors():
+ try:
+ return app_cache.app_errors
+ except AttributeError:
+ app_cache.populate()
+ app_cache.app_errors = {}
+ return app_cache.app_errors
diff --git a/django/db/models/options.py b/django/db/models/options.py
index 5c4a9dcad7..cd68d5eae9 100644
--- a/django/db/models/options.py
+++ b/django/db/models/options.py
@@ -6,10 +6,10 @@ from bisect import bisect
import warnings
from django.conf import settings
+from django.core.apps import app_cache
from django.db.models.fields.related import ManyToManyRel
from django.db.models.fields import AutoField, FieldDoesNotExist
from django.db.models.fields.proxy import OrderWrt
-from django.db.models.loading import app_cache_ready, cache
from django.utils import six
from django.utils.functional import cached_property
from django.utils.encoding import force_text, smart_text, python_2_unicode_compatible
@@ -32,10 +32,13 @@ def normalize_unique_together(unique_together):
tuple of two strings. Normalize it to a tuple of tuples, so that
calling code can uniformly expect that.
"""
- unique_together = tuple(unique_together)
- if unique_together and not isinstance(unique_together[0], (tuple, list)):
+ if not unique_together:
+ return ()
+ first_element = next(iter(unique_together))
+ if not isinstance(first_element, (tuple, list)):
unique_together = (unique_together,)
- return unique_together
+ # Normalize everything to tuples
+ return tuple(tuple(ut) for ut in unique_together)
@python_2_unicode_compatible
@@ -86,7 +89,16 @@ class Options(object):
self.related_fkey_lookups = []
# A custom AppCache to use, if you're making a separate model set.
- self.app_cache = cache
+ self.app_cache = app_cache
+
+ @property
+ def app_config(self):
+ # Don't go through get_app_config to avoid triggering populate().
+ return self.app_cache.app_configs[self.app_label]
+
+ @property
+ def installed(self):
+ return self.app_config.installed
def contribute_to_class(self, cls, name):
from django.db import connection
@@ -94,7 +106,6 @@ class Options(object):
cls._meta = self
self.model = cls
- self.installed = re.sub('\.models$', '', cls.__module__) in settings.INSTALLED_APPS
# First, construct the default values for these options.
self.object_name = cls.__name__
self.model_name = self.object_name.lower()
@@ -429,7 +440,7 @@ class Options(object):
if hasattr(f, 'related'):
cache[f.name] = cache[f.attname] = (
f.related, None if f.model == self.model else f.model, True, False)
- if app_cache_ready():
+ if app_cache.app_cache_ready():
self._name_map = cache
return cache
@@ -555,7 +566,7 @@ class Options(object):
and not isinstance(f.rel.to, six.string_types)
and self == f.rel.to._meta):
cache[f.related] = None
- if app_cache_ready():
+ if app_cache.app_cache_ready():
self._related_many_to_many_cache = cache
return cache
diff --git a/django/db/models/query.py b/django/db/models/query.py
index 1737626d16..48d295ccca 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -150,10 +150,10 @@ class QuerySet(object):
"""
if not isinstance(k, (slice,) + six.integer_types):
raise TypeError
- assert ((not isinstance(k, slice) and (k >= 0))
- or (isinstance(k, slice) and (k.start is None or k.start >= 0)
- and (k.stop is None or k.stop >= 0))), \
- "Negative indexing is not supported."
+ assert ((not isinstance(k, slice) and (k >= 0)) or
+ (isinstance(k, slice) and (k.start is None or k.start >= 0) and
+ (k.stop is None or k.stop >= 0))), \
+ "Negative indexing is not supported."
if self._result_cache is not None:
return self._result_cache[k]
@@ -389,10 +389,10 @@ class QuerySet(object):
return objs
self._for_write = True
connection = connections[self.db]
- fields = self.model._meta.local_fields
+ fields = self.model._meta.local_concrete_fields
with transaction.commit_on_success_unless_managed(using=self.db):
if (connection.features.can_combine_inserts_with_and_without_auto_increment_pk
- and self.model._meta.has_auto_field):
+ and self.model._meta.has_auto_field):
self._batched_insert(objs, fields, batch_size)
else:
objs_with_pk, objs_without_pk = partition(lambda o: o.pk is None, objs)
@@ -521,7 +521,7 @@ class QuerySet(object):
that ID.
"""
assert self.query.can_filter(), \
- "Cannot use 'limit' or 'offset' with in_bulk"
+ "Cannot use 'limit' or 'offset' with in_bulk"
if not id_list:
return {}
qs = self.filter(pk__in=id_list).order_by()
@@ -532,7 +532,7 @@ class QuerySet(object):
Deletes the records in the current QuerySet.
"""
assert self.query.can_filter(), \
- "Cannot use 'limit' or 'offset' with delete."
+ "Cannot use 'limit' or 'offset' with delete."
del_query = self._clone()
@@ -569,7 +569,7 @@ class QuerySet(object):
fields to the appropriate values.
"""
assert self.query.can_filter(), \
- "Cannot update a query once a slice has been taken."
+ "Cannot update a query once a slice has been taken."
self._for_write = True
query = self.query.clone(sql.UpdateQuery)
query.add_update_values(kwargs)
@@ -587,7 +587,7 @@ class QuerySet(object):
useful at that level).
"""
assert self.query.can_filter(), \
- "Cannot update a query once a slice has been taken."
+ "Cannot update a query once a slice has been taken."
query = self.query.clone(sql.UpdateQuery)
query.add_update_fields(values)
self._result_cache = None
@@ -635,11 +635,11 @@ class QuerySet(object):
the given field_name, scoped to 'kind'.
"""
assert kind in ("year", "month", "day"), \
- "'kind' must be one of 'year', 'month' or 'day'."
+ "'kind' must be one of 'year', 'month' or 'day'."
assert order in ('ASC', 'DESC'), \
- "'order' must be either 'ASC' or 'DESC'."
+ "'order' must be either 'ASC' or 'DESC'."
return self._clone(klass=DateQuerySet, setup=True,
- _field_name=field_name, _kind=kind, _order=order)
+ _field_name=field_name, _kind=kind, _order=order)
def datetimes(self, field_name, kind, order='ASC', tzinfo=None):
"""
@@ -647,9 +647,9 @@ class QuerySet(object):
datetimes for the given field_name, scoped to 'kind'.
"""
assert kind in ("year", "month", "day", "hour", "minute", "second"), \
- "'kind' must be one of 'year', 'month', 'day', 'hour', 'minute' or 'second'."
+ "'kind' must be one of 'year', 'month', 'day', 'hour', 'minute' or 'second'."
assert order in ('ASC', 'DESC'), \
- "'order' must be either 'ASC' or 'DESC'."
+ "'order' must be either 'ASC' or 'DESC'."
if settings.USE_TZ:
if tzinfo is None:
tzinfo = timezone.get_current_timezone()
@@ -804,7 +804,7 @@ class QuerySet(object):
Returns a new QuerySet instance with the ordering changed.
"""
assert self.query.can_filter(), \
- "Cannot reorder a query once a slice has been taken."
+ "Cannot reorder a query once a slice has been taken."
obj = self._clone()
obj.query.clear_ordering(force_empty=False)
obj.query.add_ordering(*field_names)
@@ -815,7 +815,7 @@ class QuerySet(object):
Returns a new QuerySet instance that will select only distinct results.
"""
assert self.query.can_filter(), \
- "Cannot create distinct fields once a slice has been taken."
+ "Cannot create distinct fields once a slice has been taken."
obj = self._clone()
obj.query.add_distinct_fields(*field_names)
return obj
@@ -826,7 +826,7 @@ class QuerySet(object):
Adds extra SQL fragments to the query.
"""
assert self.query.can_filter(), \
- "Cannot change a query once a slice has been taken"
+ "Cannot change a query once a slice has been taken"
clone = self._clone()
clone.query.add_extra(select, select_params, where, params, tables, order_by)
return clone
@@ -1494,7 +1494,7 @@ class RawQuerySet(object):
annotated model instances.
"""
def __init__(self, raw_query, model=None, query=None, params=None,
- translations=None, using=None, hints=None):
+ translations=None, using=None, hints=None):
self.raw_query = raw_query
self.model = model
self._db = using
diff --git a/django/db/models/signals.py b/django/db/models/signals.py
index 6b011c2099..a6822309a3 100644
--- a/django/db/models/signals.py
+++ b/django/db/models/signals.py
@@ -1,6 +1,6 @@
from collections import defaultdict
-from django.db.models.loading import get_model
+from django.core.apps import app_cache
from django.dispatch import Signal
from django.utils import six
@@ -41,7 +41,7 @@ class ModelSignal(Signal):
"Specified sender must either be a model or a "
"model name of the 'app_label.ModelName' form."
)
- sender = get_model(app_label, object_name, only_installed=False)
+ sender = app_cache.get_model(app_label, object_name, only_installed=False)
if sender is None:
reference = (app_label, object_name)
self.unresolved_references[reference].append(
diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py
index ef561e663f..9e355ee6c5 100644
--- a/django/db/models/sql/compiler.py
+++ b/django/db/models/sql/compiler.py
@@ -297,25 +297,32 @@ class SQLCompiler(object):
continue
alias = self.query.join_parent_model(opts, model, start_alias,
seen_models)
+ column = field.column
+ for seen_model, seen_alias in seen_models.items():
+ if seen_model and seen_alias == alias:
+ ancestor_link = seen_model._meta.get_ancestor_link(model)
+ if ancestor_link:
+ column = ancestor_link.column
+ break
table = self.query.alias_map[alias].table_name
- if table in only_load and field.column not in only_load[table]:
+ if table in only_load and column not in only_load[table]:
continue
if as_pairs:
- result.append((alias, field.column))
+ result.append((alias, column))
aliases.add(alias)
continue
- if with_aliases and field.column in col_aliases:
+ if with_aliases and column in col_aliases:
c_alias = 'Col%d' % len(col_aliases)
result.append('%s.%s AS %s' % (qn(alias),
- qn2(field.column), c_alias))
+ qn2(column), c_alias))
col_aliases.add(c_alias)
aliases.add(c_alias)
else:
- r = '%s.%s' % (qn(alias), qn2(field.column))
+ r = '%s.%s' % (qn(alias), qn2(column))
result.append(r)
aliases.add(r)
if with_aliases:
- col_aliases.add(field.column)
+ col_aliases.add(column)
return result, aliases
def get_distinct(self):
@@ -898,7 +905,7 @@ class SQLDeleteCompiler(SQLCompiler):
parameters.
"""
assert len(self.query.tables) == 1, \
- "Can only delete from one table at a time."
+ "Can only delete from one table at a time."
qn = self
result = ['DELETE FROM %s' % qn(self.query.tables[0])]
where, params = self.compile(self.query.where)
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index ef7df8552b..c5b9e0326f 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -9,6 +9,7 @@ all about the internals of models in order to get the information it needs.
from collections import OrderedDict
import copy
+import warnings
from django.utils.encoding import force_text
from django.utils.tree import Node
@@ -453,9 +454,9 @@ class Query(object):
'rhs' query.
"""
assert self.model == rhs.model, \
- "Cannot combine queries on two different base models."
+ "Cannot combine queries on two different base models."
assert self.can_filter(), \
- "Cannot combine queries once a slice has been taken."
+ "Cannot combine queries once a slice has been taken."
assert self.distinct == rhs.distinct, \
"Cannot combine a unique query with a non-unique query."
assert self.distinct_fields == rhs.distinct_fields, \
@@ -995,9 +996,9 @@ class Query(object):
raise FieldError("Cannot compute %s('%s'): '%s' is an aggregate" % (
aggregate.name, field_name, field_name))
elif ((len(field_list) > 1) or
- (field_list[0] not in [i.name for i in opts.fields]) or
- self.group_by is None or
- not is_summary):
+ (field_list[0] not in [i.name for i in opts.fields]) or
+ self.group_by is None or
+ not is_summary):
# If:
# - the field descriptor has more than one part (foo__bar), or
# - the field descriptor is referencing an m2m/m2o field, or
@@ -1035,11 +1036,14 @@ class Query(object):
# Interpret '__exact=None' as the sql 'is NULL'; otherwise, reject all
# uses of None as a query value.
if value is None:
- if lookups[-1] != 'exact':
+ if lookups[-1] not in ('exact', 'iexact'):
raise ValueError("Cannot use None as a query value")
lookups[-1] = 'isnull'
value = True
elif callable(value):
+ warnings.warn(
+ "Passing callable arguments to queryset is deprecated.",
+ PendingDeprecationWarning, stacklevel=2)
value = value()
elif isinstance(value, ExpressionNode):
# If value is a query expression, evaluate it
@@ -1274,7 +1278,7 @@ class Query(object):
q_object.clone(), q_object.negated)
# For join promotion this case is doing an AND for the added q_object
# and existing conditions. So, any existing inner join forces the join
- # type to remain inner. Exsting outer joins can however be demoted.
+ # type to remain inner. Existing outer joins can however be demoted.
# (Consider case where rel_a is LOUTER and rel_a__col=1 is added - if
# rel_a doesn't produce any rows, then the whole condition must fail.
# So, demotion is OK.
@@ -1321,7 +1325,7 @@ class Query(object):
single name in 'names' can generate multiple PathInfos (m2m for
example).
- 'names' is the path of names to travle, 'opts' is the model Options we
+ 'names' is the path of names to travel, 'opts' is the model Options we
start the name resolving from, 'allow_many' is as for setup_joins().
Returns a list of PathInfo tuples. In addition returns the final field
@@ -1949,7 +1953,7 @@ class Query(object):
# is_nullable() is needed to the compiler stage, but that is not easy
# to do currently.
if ((connections[DEFAULT_DB_ALIAS].features.interprets_empty_strings_as_nulls)
- and field.empty_strings_allowed):
+ and field.empty_strings_allowed):
return True
else:
return field.null
diff --git a/django/db/models/sql/subqueries.py b/django/db/models/sql/subqueries.py
index 7130f7472e..86b1efd3f8 100644
--- a/django/db/models/sql/subqueries.py
+++ b/django/db/models/sql/subqueries.py
@@ -61,7 +61,7 @@ class DeleteQuery(Query):
innerq_used_tables = [t for t in innerq.tables
if innerq.alias_refcount[t]]
if ((not innerq_used_tables or innerq_used_tables == self.tables)
- and not len(innerq.having)):
+ and not len(innerq.having)):
# There is only the base table in use in the query, and there is
# no aggregate filtering going on.
self.where = innerq.where
diff --git a/django/db/models/sql/where.py b/django/db/models/sql/where.py
index 256413cc83..1b241a1942 100644
--- a/django/db/models/sql/where.py
+++ b/django/db/models/sql/where.py
@@ -70,7 +70,7 @@ class WhereNode(tree.Node):
# and empty values need special handling. Other types could be used
# here in the future (using Python types is suggested for consistency).
if (isinstance(value, datetime.datetime)
- or (isinstance(obj.field, DateTimeField) and lookup_type != 'isnull')):
+ or (isinstance(obj.field, DateTimeField) and lookup_type != 'isnull')):
value_annotation = datetime.datetime
elif hasattr(value, 'value_annotation'):
value_annotation = value.value_annotation
@@ -211,7 +211,7 @@ class WhereNode(tree.Node):
params = field_params + params
if (len(params) == 1 and params[0] == '' and lookup_type == 'exact'
- and connection.features.interprets_empty_strings_as_nulls):
+ and connection.features.interprets_empty_strings_as_nulls):
lookup_type = 'isnull'
value_annotation = True
diff --git a/django/db/utils.py b/django/db/utils.py
index 43abaf9b5a..4d53d252bf 100644
--- a/django/db/utils.py
+++ b/django/db/utils.py
@@ -282,6 +282,6 @@ class ConnectionRouter(object):
"""
Return app models allowed to be synchronized on provided db.
"""
- from .models import get_models
- return [model for model in get_models(app, include_auto_created=include_auto_created)
+ from django.core.apps import app_cache
+ return [model for model in app_cache.get_models(app, include_auto_created=include_auto_created)
if self.allow_migrate(db, model)]
diff --git a/django/dispatch/saferef.py b/django/dispatch/saferef.py
index 8826c476f1..d040ecdbde 100644
--- a/django/dispatch/saferef.py
+++ b/django/dispatch/saferef.py
@@ -221,7 +221,7 @@ class BoundNonDescriptorMethodWeakref(BoundMethodWeakref):
which will be passed a pointer to this object.
"""
assert getattr(target.__self__, target.__name__) == target, \
- ("method %s isn't available as the attribute %s of %s" %
+ ("method %s isn't available as the attribute %s of %s" %
(target, target.__name__, target.__self__))
super(BoundNonDescriptorMethodWeakref, self).__init__(target, onDelete)
diff --git a/django/forms/fields.py b/django/forms/fields.py
index 56d0e316f2..3176ce65fa 100644
--- a/django/forms/fields.py
+++ b/django/forms/fields.py
@@ -15,7 +15,7 @@ from io import BytesIO
from django.core import validators
from django.core.exceptions import ValidationError
-from django.forms.utils import ErrorList, from_current_timezone, to_current_timezone
+from django.forms.utils import from_current_timezone, to_current_timezone
from django.forms.widgets import (
TextInput, NumberInput, EmailInput, URLInput, HiddenInput,
MultipleHiddenInput, ClearableFileInput, CheckboxInput, Select,
@@ -35,7 +35,7 @@ from django.core.validators import EMPTY_VALUES # NOQA
__all__ = (
'Field', 'CharField', 'IntegerField',
- 'DateField', 'TimeField', 'DateTimeField', 'TimeField',
+ 'DateField', 'TimeField', 'DateTimeField',
'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField',
'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField',
'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
@@ -998,7 +998,7 @@ class MultiValueField(Field):
DateField.clean(value[0]) and TimeField.clean(value[1]).
"""
clean_data = []
- errors = ErrorList()
+ errors = []
if not value or isinstance(value, (list, tuple)):
if not value or not [v for v in value if v not in self.empty_values]:
if self.required:
@@ -1102,8 +1102,8 @@ class FilePathField(ChoiceField):
continue
full_file = os.path.join(self.path, f)
if (((self.allow_files and os.path.isfile(full_file)) or
- (self.allow_folders and os.path.isdir(full_file))) and
- (self.match is None or self.match_re.search(f))):
+ (self.allow_folders and os.path.isdir(full_file))) and
+ (self.match is None or self.match_re.search(f))):
self.choices.append((full_file, f))
except OSError:
pass
diff --git a/django/forms/forms.py b/django/forms/forms.py
index 15bbff229b..880a74f49e 100644
--- a/django/forms/forms.py
+++ b/django/forms/forms.py
@@ -290,6 +290,51 @@ class BaseForm(object):
prefix = self.add_prefix(fieldname)
return field.widget.value_from_datadict(self.data, self.files, prefix)
+ def add_error(self, field, error):
+ """
+ Update the content of `self._errors`.
+
+ The `field` argument is the name of the field to which the errors
+ should be added. If its value is None the errors will be treated as
+ NON_FIELD_ERRORS.
+
+ The `error` argument can be a single error, a list of errors, or a
+ dictionary that maps field names to lists of errors. What we define as
+ an "error" can be either a simple string or an instance of
+ ValidationError with its message attribute set and what we define as
+ list or dictionary can be an actual `list` or `dict` or an instance
+ of ValidationError with its `error_list` or `error_dict` attribute set.
+
+ If `error` is a dictionary, the `field` argument *must* be None and
+ errors will be added to the fields that correspond to the keys of the
+ dictionary.
+ """
+ if not isinstance(error, ValidationError):
+ # Normalize to ValidationError and let its constructor
+ # do the hard work of making sense of the input.
+ error = ValidationError(error)
+
+ if hasattr(error, 'error_dict'):
+ if field is not None:
+ raise TypeError(
+ "The argument `field` must be `None` when the `error` "
+ "argument contains errors for multiple fields."
+ )
+ else:
+ error = error.error_dict
+ else:
+ error = {field or NON_FIELD_ERRORS: error.error_list}
+
+ for field, error_list in error.items():
+ if field not in self.errors:
+ if field != NON_FIELD_ERRORS and field not in self.fields:
+ raise ValueError(
+ "'%s' has no field named '%s'." % (self.__class__.__name__, field))
+ self._errors[field] = self.error_class()
+ self._errors[field].extend(error_list)
+ if field in self.cleaned_data:
+ del self.cleaned_data[field]
+
def full_clean(self):
"""
Cleans all of self.data and populates self._errors and
@@ -303,6 +348,7 @@ class BaseForm(object):
# changed from the initial data, short circuit any validation.
if self.empty_permitted and not self.has_changed():
return
+
self._clean_fields()
self._clean_form()
self._post_clean()
@@ -324,15 +370,13 @@ class BaseForm(object):
value = getattr(self, 'clean_%s' % name)()
self.cleaned_data[name] = value
except ValidationError as e:
- self._errors[name] = self.error_class(e.messages)
- if name in self.cleaned_data:
- del self.cleaned_data[name]
+ self.add_error(name, e)
def _clean_form(self):
try:
cleaned_data = self.clean()
except ValidationError as e:
- self._errors[NON_FIELD_ERRORS] = self.error_class(e.messages)
+ self.add_error(None, e)
else:
if cleaned_data is not None:
self.cleaned_data = cleaned_data
diff --git a/django/forms/formsets.py b/django/forms/formsets.py
index 3759d3381d..215ec1d876 100644
--- a/django/forms/formsets.py
+++ b/django/forms/formsets.py
@@ -325,15 +325,15 @@ class BaseFormSet(object):
self._errors.append(form.errors)
try:
if (self.validate_max and
- self.total_form_count() - len(self.deleted_forms) > self.max_num) or \
- self.management_form.cleaned_data[TOTAL_FORM_COUNT] > self.absolute_max:
+ self.total_form_count() - len(self.deleted_forms) > self.max_num) or \
+ self.management_form.cleaned_data[TOTAL_FORM_COUNT] > self.absolute_max:
raise ValidationError(ungettext(
"Please submit %d or fewer forms.",
"Please submit %d or fewer forms.", self.max_num) % self.max_num,
code='too_many_forms',
)
if (self.validate_min and
- self.total_form_count() - len(self.deleted_forms) < self.min_num):
+ self.total_form_count() - len(self.deleted_forms) < self.min_num):
raise ValidationError(ungettext(
"Please submit %d or more forms.",
"Please submit %d or more forms.", self.min_num) % self.min_num,
@@ -341,7 +341,7 @@ class BaseFormSet(object):
# Give self.clean() a chance to do cross-form validation.
self.clean()
except ValidationError as e:
- self._non_form_errors = self.error_class(e.messages)
+ self._non_form_errors = self.error_class(e.error_list)
def clean(self):
"""
diff --git a/django/forms/models.py b/django/forms/models.py
index e9bd338064..a8494360c2 100644
--- a/django/forms/models.py
+++ b/django/forms/models.py
@@ -240,8 +240,7 @@ class ModelFormMetaclass(DeclarativeFieldsMetaclass):
def __new__(mcs, name, bases, attrs):
formfield_callback = attrs.pop('formfield_callback', None)
- new_class = (super(ModelFormMetaclass, mcs)
- .__new__(mcs, name, bases, attrs))
+ new_class = super(ModelFormMetaclass, mcs).__new__(mcs, name, bases, attrs)
if bases == (BaseModelForm,):
return new_class
@@ -326,27 +325,6 @@ class BaseModelForm(BaseForm):
super(BaseModelForm, self).__init__(data, files, auto_id, prefix, object_data,
error_class, label_suffix, empty_permitted)
- def _update_errors(self, errors):
- for field, messages in errors.error_dict.items():
- if field not in self.fields:
- continue
- field = self.fields[field]
- for message in messages:
- if isinstance(message, ValidationError):
- if message.code in field.error_messages:
- message.message = field.error_messages[message.code]
-
- message_dict = errors.message_dict
- for k, v in message_dict.items():
- if k != NON_FIELD_ERRORS:
- self._errors.setdefault(k, self.error_class()).extend(v)
- # Remove the data from the cleaned_data dict since it was invalid
- if k in self.cleaned_data:
- del self.cleaned_data[k]
- if NON_FIELD_ERRORS in message_dict:
- messages = message_dict[NON_FIELD_ERRORS]
- self._errors.setdefault(NON_FIELD_ERRORS, self.error_class()).extend(messages)
-
def _get_validation_exclusions(self):
"""
For backwards-compatibility, several types of fields need to be
@@ -393,6 +371,20 @@ class BaseModelForm(BaseForm):
self._validate_unique = True
return self.cleaned_data
+ def _update_errors(self, errors):
+ # Override any validation error messages defined at the model level
+ # with those defined on the form fields.
+ for field, messages in errors.error_dict.items():
+ if field not in self.fields:
+ continue
+ field = self.fields[field]
+ for message in messages:
+ if (isinstance(message, ValidationError) and
+ message.code in field.error_messages):
+ message.message = field.error_messages[message.code]
+
+ self.add_error(None, errors)
+
def _post_clean(self):
opts = self._meta
# Update the model instance with self.cleaned_data.
@@ -407,13 +399,12 @@ class BaseModelForm(BaseForm):
# object being referred to may not yet fully exist (#12749).
# However, these fields *must* be included in uniqueness checks,
# so this can't be part of _get_validation_exclusions().
- for f_name, field in self.fields.items():
+ for name, field in self.fields.items():
if isinstance(field, InlineForeignKeyField):
- exclude.append(f_name)
+ exclude.append(name)
try:
- self.instance.full_clean(exclude=exclude,
- validate_unique=False)
+ self.instance.full_clean(exclude=exclude, validate_unique=False)
except ValidationError as e:
self._update_errors(e)
@@ -524,7 +515,7 @@ def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
# be difficult to debug for code that needs updating, so we produce the
# warning here too.
if (getattr(Meta, 'fields', None) is None and
- getattr(Meta, 'exclude', None) is None):
+ getattr(Meta, 'exclude', None) is None):
warnings.warn("Calling modelform_factory without defining 'fields' or "
"'exclude' explicitly is deprecated",
DeprecationWarning, stacklevel=2)
@@ -675,7 +666,7 @@ class BaseModelFormSet(BaseFormSet):
for form in valid_forms:
# see if we have data for both fields
if (form.cleaned_data and form.cleaned_data[field] is not None
- and form.cleaned_data[unique_for] is not None):
+ and form.cleaned_data[unique_for] is not None):
# if it's a date lookup we need to get the data for all the fields
if lookup == 'date':
date = form.cleaned_data[unique_for]
@@ -695,6 +686,7 @@ class BaseModelFormSet(BaseFormSet):
del form.cleaned_data[field]
# mark the data as seen
seen_data.add(data)
+
if errors:
raise ValidationError(errors)
@@ -815,7 +807,7 @@ def modelformset_factory(model, form=ModelForm, formfield_callback=None,
if meta is None:
meta = type(str('Meta'), (object,), {})
if (getattr(meta, 'fields', fields) is None and
- getattr(meta, 'exclude', exclude) is None):
+ getattr(meta, 'exclude', exclude) is None):
warnings.warn("Calling modelformset_factory without defining 'fields' or "
"'exclude' explicitly is deprecated",
DeprecationWarning, stacklevel=2)
@@ -1182,6 +1174,12 @@ class ModelMultipleChoiceField(ModelChoiceField):
msg = _('Hold down "Control", or "Command" on a Mac, to select more than one.')
self.help_text = string_concat(self.help_text, ' ', msg)
+ def to_python(self, value):
+ if not value:
+ return []
+ to_py = super(ModelMultipleChoiceField, self).to_python
+ return [to_py(val) for val in value]
+
def clean(self, value):
if self.required and not value:
raise ValidationError(self.error_messages['required'], code='required')
diff --git a/django/forms/utils.py b/django/forms/utils.py
index 85297b1a1e..df75e94f81 100644
--- a/django/forms/utils.py
+++ b/django/forms/utils.py
@@ -1,5 +1,7 @@
from __future__ import unicode_literals
+import json
+import sys
import warnings
from django.conf import settings
@@ -8,12 +10,16 @@ from django.utils.encoding import force_text, python_2_unicode_compatible
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django.utils import six
-import sys
# Import ValidationError so that it can be imported from this
# module to maintain backwards compatibility.
from django.core.exceptions import ValidationError
+try:
+ from collections import UserList
+except ImportError: # Python 2
+ from UserList import UserList
+
def flatatt(attrs):
"""
@@ -46,46 +52,80 @@ class ErrorDict(dict):
The dictionary keys are the field names, and the values are the errors.
"""
- def __str__(self):
- return self.as_ul()
+ def as_data(self):
+ return {f: e.as_data() for f, e in self.items()}
+
+ def as_json(self):
+ errors = {f: json.loads(e.as_json()) for f, e in self.items()}
+ return json.dumps(errors)
def as_ul(self):
if not self:
return ''
- return format_html('',
- format_html_join('', '{0}{1}',
- ((k, force_text(v))
- for k, v in self.items())
- ))
+ return format_html(
+ '',
+ format_html_join('', '{0}{1}', ((k, force_text(v)) for k, v in self.items()))
+ )
def as_text(self):
- return '\n'.join('* %s\n%s' % (k, '\n'.join(' * %s' % force_text(i) for i in v)) for k, v in self.items())
+ output = []
+ for field, errors in self.items():
+ output.append('* %s' % field)
+ output.append('\n'.join(' * %s' % e for e in errors))
+ return '\n'.join(output)
+
+ def __str__(self):
+ return self.as_ul()
@python_2_unicode_compatible
-class ErrorList(list):
+class ErrorList(UserList, list):
"""
A collection of errors that knows how to display itself in various formats.
"""
+ def as_data(self):
+ return self.data
+
+ def as_json(self):
+ errors = []
+ for error in ValidationError(self.data).error_list:
+ errors.append({
+ 'message': list(error)[0],
+ 'code': error.code or '',
+ })
+ return json.dumps(errors)
+
+ def as_ul(self):
+ if not self.data:
+ return ''
+ return format_html(
+ '',
+ format_html_join('', '{0}', ((force_text(e),) for e in self))
+ )
+
+ def as_text(self):
+ return '\n'.join('* %s' % e for e in self)
+
def __str__(self):
return self.as_ul()
- def as_ul(self):
- if not self:
- return ''
- return format_html('',
- format_html_join('', '{0}',
- ((force_text(e),) for e in self)
- )
- )
-
- def as_text(self):
- if not self:
- return ''
- return '\n'.join('* %s' % force_text(e) for e in self)
-
def __repr__(self):
- return repr([force_text(e) for e in self])
+ return repr(list(self))
+
+ def __contains__(self, item):
+ return item in list(self)
+
+ def __eq__(self, other):
+ return list(self) == other
+
+ def __ne__(self, other):
+ return list(self) != other
+
+ def __getitem__(self, i):
+ error = self.data[i]
+ if isinstance(error, ValidationError):
+ return list(error)[0]
+ return force_text(error)
# Utilities for time zone support in DateTimeField et al.
diff --git a/django/forms/widgets.py b/django/forms/widgets.py
index 79f3961138..aeb674d013 100644
--- a/django/forms/widgets.py
+++ b/django/forms/widgets.py
@@ -72,7 +72,7 @@ class Media(object):
return path
if prefix is None:
if settings.STATIC_URL is None:
- # backwards compatibility
+ # backwards compatibility
prefix = settings.MEDIA_URL
else:
prefix = settings.STATIC_URL
diff --git a/django/http/multipartparser.py b/django/http/multipartparser.py
index 1c64195f25..eecf6150d4 100644
--- a/django/http/multipartparser.py
+++ b/django/http/multipartparser.py
@@ -259,10 +259,9 @@ class MultiPartParser(object):
file_obj = handler.file_complete(counters[i])
if file_obj:
# If it returns a file object, then set the files dict.
- self._files.appendlist(force_text(old_field_name,
- self._encoding,
- errors='replace'),
- file_obj)
+ self._files.appendlist(
+ force_text(old_field_name, self._encoding, errors='replace'),
+ file_obj)
break
def IE_sanitize(self, filename):
diff --git a/django/http/request.py b/django/http/request.py
index 1aefe6bf22..6bce1f718d 100644
--- a/django/http/request.py
+++ b/django/http/request.py
@@ -66,7 +66,7 @@ class HttpRequest(object):
"""Returns the HTTP host using the environment or request headers."""
# We try three options, in order of decreasing preference.
if settings.USE_X_FORWARDED_HOST and (
- 'HTTP_X_FORWARDED_HOST' in self.META):
+ 'HTTP_X_FORWARDED_HOST' in self.META):
host = self.META['HTTP_X_FORWARDED_HOST']
elif 'HTTP_HOST' in self.META:
host = self.META['HTTP_HOST']
diff --git a/django/middleware/common.py b/django/middleware/common.py
index 51fcf1b2af..6ff8a8d6e7 100644
--- a/django/middleware/common.py
+++ b/django/middleware/common.py
@@ -122,7 +122,7 @@ class CommonMiddleware(object):
etag = '"%s"' % hashlib.md5(response.content).hexdigest()
if etag is not None:
if (200 <= response.status_code < 300
- and request.META.get('HTTP_IF_NONE_MATCH') == etag):
+ and request.META.get('HTTP_IF_NONE_MATCH') == etag):
cookies = response.cookies
response = http.HttpResponseNotModified()
response.cookies = cookies
diff --git a/django/middleware/locale.py b/django/middleware/locale.py
index 87f904d7dd..1f64387d78 100644
--- a/django/middleware/locale.py
+++ b/django/middleware/locale.py
@@ -55,16 +55,6 @@ class LocaleMiddleware(object):
request.get_full_path())
return self.response_redirect_class(language_url)
- # Store language back into session if it is not present
- if hasattr(request, 'session') and '_language' not in request.session:
- # Backwards compatibility check on django_language (remove in 1.8);
- # revert to: `request.session.setdefault('_language', language)`.
- if 'django_language' in request.session:
- request.session['_language'] = request.session['django_language']
- del request.session['django_language']
- else:
- request.session['_language'] = language
-
if not (self.is_language_prefix_patterns_used()
and language_from_path):
patch_vary_headers(response, ('Accept-Language',))
diff --git a/django/template/base.py b/django/template/base.py
index fd87b33c5d..c314637c42 100644
--- a/django/template/base.py
+++ b/django/template/base.py
@@ -92,8 +92,7 @@ class VariableDoesNotExist(Exception):
self.params = params
def __str__(self):
- return self.msg % tuple(force_text(p, errors='replace')
- for p in self.params)
+ return self.msg % tuple(force_text(p, errors='replace') for p in self.params)
class InvalidTemplateLibrary(Exception):
@@ -1055,8 +1054,7 @@ class TagHelperNode(Node):
resolved_args = [var.resolve(context) for var in self.args]
if self.takes_context:
resolved_args = [context] + resolved_args
- resolved_kwargs = dict((k, v.resolve(context))
- for k, v in self.kwargs.items())
+ resolved_kwargs = dict((k, v.resolve(context)) for k, v in self.kwargs.items())
return resolved_args, resolved_kwargs
diff --git a/django/template/context.py b/django/template/context.py
index ba2c3d4d07..774f7af6b1 100644
--- a/django/template/context.py
+++ b/django/template/context.py
@@ -145,10 +145,10 @@ class RenderContext(BaseContext):
return key in self.dicts[-1]
def get(self, key, otherwise=None):
- d = self.dicts[-1]
- if key in d:
- return d[key]
- return otherwise
+ return self.dicts[-1].get(key, otherwise)
+
+ def __getitem__(self, key):
+ return self.dicts[-1][key]
# This is a function rather than module-level procedural code because we only
diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py
index 1bd54404b3..865f5fe7f3 100644
--- a/django/template/defaultfilters.py
+++ b/django/template/defaultfilters.py
@@ -13,14 +13,15 @@ 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, avoid_wrapping)
+ escape, urlize as _urlize, linebreaks, strip_tags, avoid_wrapping,
+ remove_tags)
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
from django.utils import six
from django.utils.timesince import timesince, timeuntil
from django.utils.translation import ugettext, ungettext
-from django.utils.text import normalize_newlines
+from django.utils.text import normalize_newlines, slugify as _slugify
register = Library()
@@ -40,7 +41,7 @@ def stringfilter(func):
args = list(args)
args[0] = force_text(args[0])
if (isinstance(args[0], SafeData) and
- getattr(_dec._decorated_function, 'is_safe', False)):
+ getattr(_dec._decorated_function, 'is_safe', False)):
return mark_safe(func(*args, **kwargs))
return func(*args, **kwargs)
@@ -235,8 +236,7 @@ def slugify(value):
underscores) and converts spaces to hyphens. Also strips leading and
trailing whitespace.
"""
- from django.utils.text import slugify
- return slugify(value)
+ return _slugify(value)
@register.filter(is_safe=True)
@@ -341,7 +341,7 @@ def urlencode(value, safe=None):
@stringfilter
def urlize(value, autoescape=None):
"""Converts URLs in plain text into clickable links."""
- return mark_safe(urlize_impl(value, nofollow=True, autoescape=autoescape))
+ return mark_safe(_urlize(value, nofollow=True, autoescape=autoescape))
@register.filter(is_safe=True, needs_autoescape=True)
@@ -353,7 +353,7 @@ def urlizetrunc(value, limit, autoescape=None):
Argument: Length to truncate URLs to.
"""
- return mark_safe(urlize_impl(value, trim_url_limit=int(limit), nofollow=True,
+ return mark_safe(_urlize(value, trim_url_limit=int(limit), nofollow=True,
autoescape=autoescape))
@@ -490,7 +490,6 @@ def safeseq(value):
@stringfilter
def removetags(value, tags):
"""Removes a space separated list of [X]HTML tags from the output."""
- from django.utils.html import remove_tags
return remove_tags(value, tags)
diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
index 011d15ea65..6daf234d12 100644
--- a/django/template/defaulttags.py
+++ b/django/template/defaulttags.py
@@ -430,7 +430,7 @@ class URLNode(Node):
from django.core.urlresolvers import reverse, NoReverseMatch
args = [arg.resolve(context) for arg in self.args]
kwargs = dict((smart_text(k, 'ascii'), v.resolve(context))
- for k, v in self.kwargs.items())
+ for k, v in self.kwargs.items())
view_name = self.view_name.resolve(context)
@@ -525,7 +525,7 @@ class WithNode(Node):
def render(self, context):
values = dict((key, val.resolve(context)) for key, val in
- six.iteritems(self.extra_context))
+ six.iteritems(self.extra_context))
with context.push(**values):
return self.nodelist.render(context)
diff --git a/django/template/loader_tags.py b/django/template/loader_tags.py
index 740b503cc5..e5e79725d2 100644
--- a/django/template/loader_tags.py
+++ b/django/template/loader_tags.py
@@ -70,7 +70,7 @@ class BlockNode(Node):
def super(self):
render_context = self.context.render_context
if (BLOCK_CONTEXT_KEY in render_context and
- render_context[BLOCK_CONTEXT_KEY].get_block(self.name) is not None):
+ render_context[BLOCK_CONTEXT_KEY].get_block(self.name) is not None):
return mark_safe(self.render(self.context))
return ''
@@ -117,7 +117,7 @@ class ExtendsNode(Node):
if not isinstance(node, TextNode):
if not isinstance(node, ExtendsNode):
blocks = dict((n.name, n) for n in
- compiled_parent.nodelist.get_nodes_by_type(BlockNode))
+ compiled_parent.nodelist.get_nodes_by_type(BlockNode))
block_context.add_blocks(blocks)
break
diff --git a/django/test/__init__.py b/django/test/__init__.py
index 98ff4393a3..4b17ed18c9 100644
--- a/django/test/__init__.py
+++ b/django/test/__init__.py
@@ -3,7 +3,8 @@ Django Unit Test and Doctest framework.
"""
from django.test.client import Client, RequestFactory
-from django.test.testcases import (TestCase, TransactionTestCase,
+from django.test.testcases import (
+ TestCase, TransactionTestCase,
SimpleTestCase, LiveServerTestCase, skipIfDBFeature,
skipUnlessDBFeature
)
diff --git a/django/test/runner.py b/django/test/runner.py
index 44adaab141..21e4a81604 100644
--- a/django/test/runner.py
+++ b/django/test/runner.py
@@ -272,7 +272,7 @@ def setup_databases(verbosity, interactive, **kwargs):
mirrors = []
for signature, (db_name, aliases) in dependency_ordered(
- test_databases.items(), dependencies):
+ test_databases.items(), dependencies):
test_db_name = None
# Actually create the database for the first connection
for alias in aliases:
diff --git a/django/test/signals.py b/django/test/signals.py
index 4af408ea9d..8086d50b32 100644
--- a/django/test/signals.py
+++ b/django/test/signals.py
@@ -97,4 +97,7 @@ def file_storage_changed(**kwargs):
@receiver(setting_changed)
def complex_setting_changed(**kwargs):
if kwargs['enter'] and kwargs['setting'] in COMPLEX_OVERRIDE_SETTINGS:
- warnings.warn("Overriding setting %s can lead to unexpected behaviour." % kwargs['setting'])
+ # Considering the current implementation of the signals framework,
+ # stacklevel=5 shows the line containing the override_settings call.
+ warnings.warn("Overriding setting %s can lead to unexpected behaviour."
+ % kwargs['setting'], stacklevel=5)
diff --git a/django/test/simple.py b/django/test/simple.py
index 05a7329fae..6129ce1305 100644
--- a/django/test/simple.py
+++ b/django/test/simple.py
@@ -9,7 +9,7 @@ import re
import unittest as real_unittest
import warnings
-from django.db.models import get_app, get_apps
+from django.core.apps import app_cache
from django.test import _doctest as doctest
from django.test import runner
from django.test.utils import compare_xml, strip_quotes
@@ -96,22 +96,13 @@ class DocTestRunner(doctest.DocTestRunner):
doctestOutputChecker = OutputChecker()
-def get_tests(app_module):
- parts = app_module.__name__.split('.')
- prefix, last = parts[:-1], parts[-1]
+def get_tests(app_config):
try:
- test_module = import_module('.'.join(prefix + [TEST_MODULE]))
+ test_module = import_module('%s.%s' % (app_config.name, TEST_MODULE))
except ImportError:
# Couldn't import tests.py. Was it due to a missing file, or
# due to an import error in a tests.py that actually exists?
- # app_module either points to a models.py file, or models/__init__.py
- # Tests are therefore either in same directory, or one level up
- if last == 'models':
- app_root = import_module('.'.join(prefix))
- else:
- app_root = app_module
-
- if not module_has_submodule(app_root, TEST_MODULE):
+ if not module_has_submodule(app_config.app_module, TEST_MODULE):
test_module = None
else:
# The module exists, so there must be an import error in the test
@@ -123,11 +114,10 @@ def get_tests(app_module):
def make_doctest(module):
return doctest.DocTestSuite(module,
checker=doctestOutputChecker,
- runner=DocTestRunner,
- )
+ runner=DocTestRunner)
-def build_suite(app_module):
+def build_suite(app_config):
"""
Create a complete Django test suite for the provided application module.
"""
@@ -135,30 +125,32 @@ def build_suite(app_module):
# Load unit and doctests in the models.py module. If module has
# a suite() method, use it. Otherwise build the test suite ourselves.
- if hasattr(app_module, 'suite'):
- suite.addTest(app_module.suite())
- else:
- suite.addTest(unittest.defaultTestLoader.loadTestsFromModule(
- app_module))
- try:
- suite.addTest(make_doctest(app_module))
- except ValueError:
- # No doc tests in models.py
- pass
+ models_module = app_config.models_module
+ if models_module:
+ if hasattr(models_module, 'suite'):
+ suite.addTest(models_module.suite())
+ else:
+ suite.addTest(unittest.defaultTestLoader.loadTestsFromModule(
+ models_module))
+ try:
+ suite.addTest(make_doctest(models_module))
+ except ValueError:
+ # No doc tests in models.py
+ pass
# Check to see if a separate 'tests' module exists parallel to the
# models module
- test_module = get_tests(app_module)
- if test_module:
+ tests_module = get_tests(app_config)
+ if tests_module:
# Load unit and doctests in the tests.py module. If module has
# a suite() method, use it. Otherwise build the test suite ourselves.
- if hasattr(test_module, 'suite'):
- suite.addTest(test_module.suite())
+ if hasattr(tests_module, 'suite'):
+ suite.addTest(tests_module.suite())
else:
suite.addTest(unittest.defaultTestLoader.loadTestsFromModule(
- test_module))
+ tests_module))
try:
- suite.addTest(make_doctest(test_module))
+ suite.addTest(make_doctest(tests_module))
except ValueError:
# No doc tests in tests.py
pass
@@ -168,26 +160,29 @@ def build_suite(app_module):
def build_test(label):
"""
Construct a test case with the specified label. Label should be of the
- form model.TestClass or model.TestClass.test_method. Returns an
+ form app_label.TestClass or app_label.TestClass.test_method. Returns an
instantiated test or test suite corresponding to the label provided.
-
"""
parts = label.split('.')
if len(parts) < 2 or len(parts) > 3:
raise ValueError("Test label '%s' should be of the form app.TestCase "
"or app.TestCase.test_method" % label)
- #
- # First, look for TestCase instances with a name that matches
- #
- app_module = get_app(parts[0])
- test_module = get_tests(app_module)
- TestClass = getattr(app_module, parts[1], None)
+ app_config = app_cache.get_app_config(parts[0])
+ models_module = app_config.models_module
+ tests_module = get_tests(app_config)
- # Couldn't find the test class in models.py; look in tests.py
- if TestClass is None:
- if test_module:
- TestClass = getattr(test_module, parts[1], None)
+ test_modules = []
+ if models_module:
+ test_modules.append(models_module)
+ if tests_module:
+ test_modules.append(tests_module)
+
+ TestClass = None
+ for module in test_modules:
+ TestClass = getattr(models_module, parts[1], None)
+ if TestClass is not None:
+ break
try:
if issubclass(TestClass, (unittest.TestCase, real_unittest.TestCase)):
@@ -209,7 +204,7 @@ def build_test(label):
# If there isn't a TestCase, look for a doctest that matches
#
tests = []
- for module in app_module, test_module:
+ for module in test_modules:
try:
doctests = make_doctest(module)
# Now iterate over the suite, looking for doctests whose name
@@ -242,11 +237,11 @@ class DjangoTestSuiteRunner(runner.DiscoverRunner):
if '.' in label:
suite.addTest(build_test(label))
else:
- app = get_app(label)
- suite.addTest(build_suite(app))
+ app_config = app_cache.get_app_config(label)
+ suite.addTest(build_suite(app_config))
else:
- for app in get_apps():
- suite.addTest(build_suite(app))
+ for app_config in app_cache.get_app_configs():
+ suite.addTest(build_suite(app_config))
if extra_tests:
for test in extra_tests:
diff --git a/django/test/testcases.py b/django/test/testcases.py
index bb40e5c4fd..13f56fa6cc 100644
--- a/django/test/testcases.py
+++ b/django/test/testcases.py
@@ -17,6 +17,7 @@ from unittest.util import safe_repr
from django.conf import settings
from django.core import mail
+from django.core.apps import app_cache
from django.core.exceptions import ValidationError, ImproperlyConfigured
from django.core.handlers.wsgi import get_path_info, WSGIHandler
from django.core.management import call_command
@@ -25,7 +26,6 @@ from django.core.management.commands import flush
from django.core.servers.basehttp import WSGIRequestHandler, WSGIServer
from django.core.urlresolvers import clear_url_caches, set_urlconf
from django.db import connection, connections, DEFAULT_DB_ALIAS, transaction
-from django.db.models.loading import cache
from django.forms.fields import CharField
from django.http import QueryDict
from django.test.client import Client
@@ -103,8 +103,12 @@ class _AssertNumQueriesContext(CaptureQueriesContext):
return
executed = len(self)
self.test_case.assertEqual(
- executed, self.num, "%d queries executed, %d expected" % (
- executed, self.num
+ executed, self.num,
+ "%d queries executed, %d expected\nCaptured queries were:\n%s" % (
+ executed, self.num,
+ '\n'.join(
+ query['sql'] for query in self.captured_queries
+ )
)
)
@@ -298,7 +302,7 @@ class SimpleTestCase(unittest.TestCase):
# If the response supports deferred rendering and hasn't been rendered
# yet, then ensure that it does get rendered before proceeding further.
if (hasattr(response, 'render') and callable(response.render)
- and not response.is_rendered):
+ and not response.is_rendered):
response.render()
if msg_prefix:
@@ -390,15 +394,15 @@ class SimpleTestCase(unittest.TestCase):
msg_prefix + "The field '%s' on form '%s' in"
" context %d does not contain the error '%s'"
" (actual errors: %s)" %
- (field, form, i, err, repr(field_errors)))
+ (field, form, i, err, repr(field_errors)))
elif field in context[form].fields:
self.fail(msg_prefix + "The field '%s' on form '%s'"
" in context %d contains no errors" %
- (field, form, i))
+ (field, form, i))
else:
self.fail(msg_prefix + "The form '%s' in context %d"
" does not contain the field '%s'" %
- (form, i, field))
+ (form, i, field))
else:
non_field_errors = context[form].non_field_errors()
self.assertTrue(err in non_field_errors,
@@ -448,30 +452,30 @@ class SimpleTestCase(unittest.TestCase):
msg_prefix + "The field '%s' on formset '%s', "
"form %d in context %d does not contain the "
"error '%s' (actual errors: %s)" %
- (field, formset, form_index, i, err,
- repr(field_errors)))
+ (field, formset, form_index, i, err,
+ repr(field_errors)))
elif field in context[formset].forms[form_index].fields:
self.fail(msg_prefix + "The field '%s' "
"on formset '%s', form %d in "
"context %d contains no errors" %
- (field, formset, form_index, i))
+ (field, formset, form_index, i))
else:
self.fail(msg_prefix + "The formset '%s', form %d in "
- "context %d does not contain the field '%s'" %
- (formset, form_index, i, field))
+ "context %d does not contain the field '%s'" %
+ (formset, form_index, i, field))
elif form_index is not None:
non_field_errors = context[formset].forms[form_index].non_field_errors()
self.assertFalse(len(non_field_errors) == 0,
- msg_prefix + "The formset '%s', form %d in "
- "context %d does not contain any non-field "
- "errors." % (formset, form_index, i))
+ msg_prefix + "The formset '%s', form %d in "
+ "context %d does not contain any non-field "
+ "errors." % (formset, form_index, i))
self.assertTrue(err in non_field_errors,
msg_prefix + "The formset '%s', form %d "
"in context %d does not contain the "
"non-field error '%s' "
"(actual errors: %s)" %
- (formset, form_index, i, err,
- repr(non_field_errors)))
+ (formset, form_index, i, err,
+ repr(non_field_errors)))
else:
non_form_errors = context[formset].non_form_errors()
self.assertFalse(len(non_form_errors) == 0,
@@ -482,7 +486,7 @@ class SimpleTestCase(unittest.TestCase):
msg_prefix + "The formset '%s' in context "
"%d does not contain the "
"non-form error '%s' (actual errors: %s)" %
- (formset, i, err, repr(non_form_errors)))
+ (formset, i, err, repr(non_form_errors)))
if not found_formset:
self.fail(msg_prefix + "The formset '%s' was not used to render "
"the response" % formset)
@@ -721,14 +725,14 @@ class TransactionTestCase(SimpleTestCase):
"""
super(TransactionTestCase, self)._pre_setup()
if self.available_apps is not None:
- cache.set_available_apps(self.available_apps)
+ app_cache.set_available_apps(self.available_apps)
for db_name in self._databases_names(include_mirrors=False):
flush.Command.emit_post_migrate(verbosity=0, interactive=False, database=db_name)
try:
self._fixture_setup()
except Exception:
if self.available_apps is not None:
- cache.unset_available_apps()
+ app_cache.unset_available_apps()
raise
def _databases_names(self, include_mirrors=True):
@@ -782,7 +786,7 @@ class TransactionTestCase(SimpleTestCase):
for conn in connections.all():
conn.close()
finally:
- cache.unset_available_apps()
+ app_cache.unset_available_apps()
def _fixture_teardown(self):
# Allow TRUNCATE ... CASCADE and don't emit the post_migrate signal
@@ -1043,7 +1047,7 @@ class LiveServerThread(threading.Thread):
(self.host, port), QuietWSGIRequestHandler)
except socket.error as e:
if (index + 1 < len(self.possible_ports) and
- e.errno == errno.EADDRINUSE):
+ e.errno == errno.EADDRINUSE):
# This port is already in use, so we go on and try with
# the next one in the list.
continue
@@ -1097,7 +1101,7 @@ class LiveServerTestCase(TransactionTestCase):
# If using in-memory sqlite databases, pass the connections to
# the server thread.
if (conn.vendor == 'sqlite'
- and conn.settings_dict['NAME'] == ':memory:'):
+ and conn.settings_dict['NAME'] == ':memory:'):
# Explicitly enable thread-shareability for this connection
conn.allow_thread_sharing = True
connections_override[conn.alias] = conn
@@ -1154,7 +1158,7 @@ class LiveServerTestCase(TransactionTestCase):
# Restore sqlite connections' non-sharability
for conn in connections.all():
if (conn.vendor == 'sqlite'
- and conn.settings_dict['NAME'] == ':memory:'):
+ and conn.settings_dict['NAME'] == ':memory:'):
conn.allow_thread_sharing = False
@classmethod
diff --git a/django/utils/_os.py b/django/utils/_os.py
index 11fc5afe49..1cf250e09e 100644
--- a/django/utils/_os.py
+++ b/django/utils/_os.py
@@ -74,8 +74,8 @@ def safe_join(base, *paths):
# b) The final path must be the same as the base path.
# c) The base path must be the most root path (meaning either "/" or "C:\\")
if (not normcase(final_path).startswith(normcase(base_path + sep)) and
- normcase(final_path) != normcase(base_path) and
- dirname(normcase(base_path)) != normcase(base_path)):
+ normcase(final_path) != normcase(base_path) and
+ dirname(normcase(base_path)) != normcase(base_path)):
raise ValueError('The joined path (%s) is located outside of the base '
'path component (%s)' % (final_path, base_path))
return final_path
diff --git a/django/utils/archive.py b/django/utils/archive.py
index 0a95fa84a6..3e5d22ae1b 100644
--- a/django/utils/archive.py
+++ b/django/utils/archive.py
@@ -160,7 +160,7 @@ class TarArchive(BaseArchive):
# Some corrupt tar files seem to produce this
# (specifically bad symlinks)
print("In the tar file %s the member %s is invalid: %s" %
- (name, member.name, exc))
+ (name, member.name, exc))
else:
dirname = os.path.dirname(filename)
if dirname and not os.path.exists(dirname):
diff --git a/django/utils/autoreload.py b/django/utils/autoreload.py
index 4b0a97f12b..1913317822 100644
--- a/django/utils/autoreload.py
+++ b/django/utils/autoreload.py
@@ -33,7 +33,6 @@ from __future__ import absolute_import # Avoid importing `importlib` from this
import os
import signal
import sys
-import tempfile
import time
import traceback
@@ -41,7 +40,6 @@ from django.conf import settings
from django.core.signals import request_finished
from django.utils._os import upath
from importlib import import_module
-from django.utils import six
try:
from django.utils.six.moves import _thread as thread
except ImportError:
@@ -71,20 +69,6 @@ try:
except ImportError:
pass
-try:
- import select
- select.kevent, select.kqueue
- USE_KQUEUE = True
-
- import resource
- NOFILES_SOFT, NOFILES_HARD = resource.getrlimit(resource.RLIMIT_NOFILE)
-
- import subprocess
- command = ["sysctl", "-n", "kern.maxfilesperproc"]
- NOFILES_KERN = int(subprocess.check_output(command).strip())
-except (AttributeError, OSError):
- USE_KQUEUE = False
-
RUN_RELOADER = True
_mtimes = {}
@@ -163,88 +147,6 @@ def inotify_code_changed():
return True
-def kqueue_code_changed():
- """
- Checks for changed code using kqueue. After being called
- it blocks until a change event has been fired.
- """
- kqueue = select.kqueue()
-
- # Utility function to create kevents.
- _filter = select.KQ_FILTER_VNODE
- flags = select.KQ_EV_ADD
- fflags = select.KQ_NOTE_DELETE | select.KQ_NOTE_WRITE | select.KQ_NOTE_RENAME
-
- def make_kevent(descriptor):
- return select.kevent(descriptor, _filter, flags, fflags)
-
- # New modules may get imported when a request is processed. We add a file
- # descriptor to the kqueue to exit the kqueue.control after each request.
- buf_kwargs = {'buffering' if six.PY3 else 'bufsize': 0}
- watcher = tempfile.TemporaryFile(**buf_kwargs)
- kqueue.control([make_kevent(watcher)], 0)
-
- def update_watch(sender=None, **kwargs):
- watcher.write(b'.')
-
- request_finished.connect(update_watch)
-
- # We have to manage a set of descriptors to avoid the overhead of opening
- # and closing every files whenever we reload the set of files to watch.
- filenames = set()
- descriptors = set()
-
- while True:
- old_filenames = filenames
- filenames = set(gen_filenames())
- new_filenames = filenames - old_filenames
-
- # If new files were added since the last time we went through the loop,
- # add them to the kqueue.
- if new_filenames:
-
- # We must increase the maximum number of open file descriptors
- # because each kevent uses one file descriptor and resource limits
- # are too low by default.
- #
- # In fact there are two limits:
- # - kernel limit: `sysctl kern.maxfilesperproc` -> 10240 on OS X.9
- # - resource limit: `launchctl limit maxfiles` -> 256 on OS X.9
- #
- # The latter can be changed with Python's resource module, but it
- # can never exceed the former. Unfortunately, getrlimit(3) -- used
- # by both launchctl and the resource module -- reports no "hard
- # limit", even though the kernel sets one.
-
- # If project is too large or kernel limits are too tight, use polling.
- if len(filenames) >= NOFILES_KERN:
- return code_changed()
-
- # Add the number of file descriptors we're going to use to the current
- # resource limit, while staying within the kernel limit.
- nofiles_target = min(len(filenames) + NOFILES_SOFT, NOFILES_KERN)
- resource.setrlimit(resource.RLIMIT_NOFILE, (nofiles_target, NOFILES_HARD))
-
- new_descriptors = set(open(filename) for filename in new_filenames)
- descriptors |= new_descriptors
-
- kqueue.control([make_kevent(descriptor) for descriptor in new_descriptors], 0)
-
- events = kqueue.control([], 1)
-
- # After a request, reload the set of watched files.
- if len(events) == 1 and events[0].ident == watcher.fileno():
- continue
-
- # If the change affected another file, clean up and exit.
- for descriptor in descriptors:
- descriptor.close()
- watcher.close()
- kqueue.close()
-
- return True
-
-
def code_changed():
global _mtimes, _win
for filename in gen_filenames():
@@ -307,8 +209,6 @@ def reloader_thread():
ensure_echo_on()
if USE_INOTIFY:
fn = inotify_code_changed
- elif USE_KQUEUE:
- fn = kqueue_code_changed
else:
fn = code_changed
while RUN_RELOADER:
diff --git a/django/utils/feedgenerator.py b/django/utils/feedgenerator.py
index 00d982025a..52c0ecca7f 100644
--- a/django/utils/feedgenerator.py
+++ b/django/utils/feedgenerator.py
@@ -113,9 +113,9 @@ class SyndicationFeed(object):
self.items = []
def add_item(self, title, link, description, author_email=None,
- author_name=None, author_link=None, pubdate=None, comments=None,
- unique_id=None, unique_id_is_permalink=None, enclosure=None,
- categories=(), item_copyright=None, ttl=None, updateddate=None, **kwargs):
+ author_name=None, author_link=None, pubdate=None, comments=None,
+ unique_id=None, unique_id_is_permalink=None, enclosure=None,
+ categories=(), item_copyright=None, ttl=None, updateddate=None, **kwargs):
"""
Adds an item to the feed. All args are expected to be Python Unicode
objects except pubdate and updateddate, which are datetime.datetime
diff --git a/django/utils/html.py b/django/utils/html.py
index 3ad549de19..b6b639d538 100644
--- a/django/utils/html.py
+++ b/django/utils/html.py
@@ -80,8 +80,7 @@ def format_html(format_string, *args, **kwargs):
of str.format or % interpolation to build up small HTML fragments.
"""
args_safe = map(conditional_escape, args)
- kwargs_safe = dict((k, conditional_escape(v)) for (k, v) in
- six.iteritems(kwargs))
+ kwargs_safe = dict((k, conditional_escape(v)) for (k, v) in six.iteritems(kwargs))
return mark_safe(format_string.format(*args_safe, **kwargs_safe))
@@ -238,7 +237,7 @@ def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False):
lead = lead + opening
# Keep parentheses at the end only if they're balanced.
if (middle.endswith(closing)
- and middle.count(closing) == middle.count(opening) + 1):
+ and middle.count(closing) == middle.count(opening) + 1):
middle = middle[:-len(closing)]
trail = closing + trail
@@ -291,8 +290,7 @@ def clean_html(text):
* Remove stuff like "
", but only if it's at the
bottom of the text.
"""
- from django.utils.text import normalize_newlines
- text = normalize_newlines(force_text(text))
+ text = normalize_newlines(text)
text = re.sub(r'<(/?)\s*b\s*>', '<\\1strong>', text)
text = re.sub(r'<(/?)\s*i\s*>', '<\\1em>', text)
text = fix_ampersands(text)
diff --git a/django/utils/importlib.py b/django/utils/importlib.py
index 483cea5f94..8059f16095 100644
--- a/django/utils/importlib.py
+++ b/django/utils/importlib.py
@@ -17,8 +17,7 @@ def _resolve_name(name, package, level):
try:
dot = package.rindex('.', 0, dot)
except ValueError:
- raise ValueError("attempted relative import beyond top-level "
- "package")
+ raise ValueError("attempted relative import beyond top-level package")
return "%s.%s" % (package[:dot], name)
diff --git a/django/utils/text.py b/django/utils/text.py
index bb05afefc4..d172dc91f8 100644
--- a/django/utils/text.py
+++ b/django/utils/text.py
@@ -24,6 +24,7 @@ capfirst = allow_lazy(capfirst, six.text_type)
# Set up regular expressions
re_words = re.compile(r'<.*?>|((?:\w[-\w]*|&.*?;)+)', re.U | re.S)
re_tag = re.compile(r'<(/)?([^ ]+?)(?:(\s*/)| .*?)?>', re.S)
+re_newlines = re.compile(r'\r\n|\r') # Used in normalize_newlines
def wrap(text, width):
@@ -249,12 +250,14 @@ get_text_list = allow_lazy(get_text_list, six.text_type)
def normalize_newlines(text):
- return force_text(re.sub(r'\r\n|\r|\n', '\n', text))
+ """Normalizes CRLF and CR newlines to just LF."""
+ text = force_text(text)
+ return re_newlines.sub('\n', text)
normalize_newlines = allow_lazy(normalize_newlines, six.text_type)
def recapitalize(text):
- "Recapitalizes text, placing caps after end-of-sentence punctuation."
+ """Recapitalizes text, placing caps after end-of-sentence punctuation."""
text = force_text(text).lower()
capsRE = re.compile(r'(?:^|(?<=[\.\?\!] ))([a-z])')
text = capsRE.sub(lambda x: x.group(1).upper(), text)
@@ -263,12 +266,11 @@ recapitalize = allow_lazy(recapitalize)
def phone2numeric(phone):
- "Converts a phone number with letters into its numeric equivalent."
+ """Converts a phone number with letters into its numeric equivalent."""
char2number = {'a': '2', 'b': '2', 'c': '2', 'd': '3', 'e': '3', 'f': '3',
'g': '4', 'h': '4', 'i': '4', 'j': '5', 'k': '5', 'l': '5', 'm': '6',
'n': '6', 'o': '6', 'p': '7', 'q': '7', 'r': '7', 's': '7', 't': '8',
- 'u': '8', 'v': '8', 'w': '9', 'x': '9', 'y': '9', 'z': '9',
- }
+ 'u': '8', 'v': '8', 'w': '9', 'x': '9', 'y': '9', 'z': '9'}
return ''.join(char2number.get(c, c) for c in phone.lower())
phone2numeric = allow_lazy(phone2numeric)
diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py
index 9dfac47381..e547800b92 100644
--- a/django/utils/translation/trans_real.py
+++ b/django/utils/translation/trans_real.py
@@ -564,6 +564,12 @@ def templatize(src, origin=None):
lineno_comment_map = {}
comment_lineno_cache = None
+ def join_tokens(tokens, trim=False):
+ message = ''.join(tokens)
+ if trim:
+ message = trim_whitespace(message)
+ return message
+
for t in Lexer(src, origin).tokenize():
if incomment:
if t.token_type == TOKEN_BLOCK and t.contents == 'endcomment':
@@ -586,29 +592,28 @@ def templatize(src, origin=None):
endbmatch = endblock_re.match(t.contents)
pluralmatch = plural_re.match(t.contents)
if endbmatch:
- if trimmed:
- singular = trim_whitespace(''.join(singular))
- else:
- singular = ''.join(singular)
-
if inplural:
- if trimmed:
- plural = trim_whitespace(''.join(plural))
- else:
- plural = ''.join(plural)
if message_context:
- out.write(' npgettext(%r, %r, %r,count) ' % (message_context, singular, plural))
+ out.write(' npgettext(%r, %r, %r,count) ' % (
+ message_context,
+ join_tokens(singular, trimmed),
+ join_tokens(plural, trimmed)))
else:
- out.write(' ngettext(%r, %r, count) ' % (singular, plural))
+ out.write(' ngettext(%r, %r, count) ' % (
+ join_tokens(singular, trimmed),
+ join_tokens(plural, trimmed)))
for part in singular:
out.write(blankout(part, 'S'))
for part in plural:
out.write(blankout(part, 'P'))
else:
if message_context:
- out.write(' pgettext(%r, %r) ' % (message_context, singular))
+ out.write(' pgettext(%r, %r) ' % (
+ message_context,
+ join_tokens(singular, trimmed)))
else:
- out.write(' gettext(%r) ' % singular)
+ out.write(' gettext(%r) ' % join_tokens(singular,
+ trimmed))
for part in singular:
out.write(blankout(part, 'S'))
message_context = None
diff --git a/django/views/debug.py b/django/views/debug.py
index e3f399ef9d..8f7f9dc00a 100644
--- a/django/views/debug.py
+++ b/django/views/debug.py
@@ -189,7 +189,7 @@ class SafeExceptionReporterFilter(ExceptionReporterFilter):
sensitive_variables = None
while current_frame is not None:
if (current_frame.f_code.co_name == 'sensitive_variables_wrapper'
- and 'sensitive_variables_wrapper' in current_frame.f_locals):
+ and 'sensitive_variables_wrapper' in current_frame.f_locals):
# The sensitive_variables decorator was used, so we take note
# of the sensitive variables' names.
wrapper = current_frame.f_locals['sensitive_variables_wrapper']
@@ -218,7 +218,7 @@ class SafeExceptionReporterFilter(ExceptionReporterFilter):
cleansed[name] = self.cleanse_special_types(request, value)
if (tb_frame.f_code.co_name == 'sensitive_variables_wrapper'
- and 'sensitive_variables_wrapper' in tb_frame.f_locals):
+ and 'sensitive_variables_wrapper' in tb_frame.f_locals):
# For good measure, obfuscate the decorated function's arguments in
# the sensitive_variables decorator's frame, in case the variables
# associated with those arguments were meant to be obfuscated from
@@ -287,7 +287,7 @@ class ExceptionReporter(object):
'templates': template_list,
})
if (settings.TEMPLATE_DEBUG and
- hasattr(self.exc_value, 'django_template_source')):
+ hasattr(self.exc_value, 'django_template_source')):
self.get_template_exception_info()
frames = self.get_traceback_frames()
@@ -483,7 +483,7 @@ def technical_404_response(request, exception):
or (request.path == '/'
and len(tried) == 1 # default URLconf
and len(tried[0]) == 1
- and tried[0][0].app_name == tried[0][0].namespace == 'admin')):
+ and getattr(tried[0][0], 'app_name', '') == getattr(tried[0][0], 'namespace', '') == 'admin')):
return default_urlconf(request)
urlconf = getattr(request, 'urlconf', settings.ROOT_URLCONF)
diff --git a/django/views/generic/base.py b/django/views/generic/base.py
index 745c5ec1fc..d66f45b599 100644
--- a/django/views/generic/base.py
+++ b/django/views/generic/base.py
@@ -90,7 +90,7 @@ class View(object):
logger.warning('Method Not Allowed (%s): %s', request.method, request.path,
extra={
'status_code': 405,
- 'request': self.request
+ 'request': request
}
)
return http.HttpResponseNotAllowed(self._allowed_methods())
@@ -193,10 +193,10 @@ class RedirectView(View):
else:
return http.HttpResponseRedirect(url)
else:
- logger.warning('Gone: %s', self.request.path,
+ logger.warning('Gone: %s', request.path,
extra={
'status_code': 410,
- 'request': self.request
+ 'request': request
})
return http.HttpResponseGone()
diff --git a/django/views/generic/detail.py b/django/views/generic/detail.py
index 466954c03a..f25b416498 100644
--- a/django/views/generic/detail.py
+++ b/django/views/generic/detail.py
@@ -1,6 +1,6 @@
from __future__ import unicode_literals
-from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
+from django.core.exceptions import ImproperlyConfigured
from django.db import models
from django.http import Http404
from django.utils.translation import ugettext as _
@@ -50,7 +50,7 @@ class SingleObjectMixin(ContextMixin):
try:
# Get the single item from the filtered queryset
obj = queryset.get()
- except ObjectDoesNotExist:
+ except queryset.model.DoesNotExist:
raise Http404(_("No %(verbose_name)s found matching the query") %
{'verbose_name': queryset.model._meta.verbose_name})
return obj
diff --git a/django/views/generic/list.py b/django/views/generic/list.py
index b2dfd38ece..0df46ec8f4 100644
--- a/django/views/generic/list.py
+++ b/django/views/generic/list.py
@@ -150,7 +150,7 @@ class BaseListView(MultipleObjectMixin, View):
# it's better to do a cheap query than to load the unpaginated
# queryset in memory.
if (self.get_paginate_by(self.object_list) is not None
- and hasattr(self.object_list, 'exists')):
+ and hasattr(self.object_list, 'exists')):
is_empty = not self.object_list.exists()
else:
is_empty = len(self.object_list) == 0
diff --git a/docs/_ext/djangodocs.py b/docs/_ext/djangodocs.py
index 8946eb063b..7c46f3287f 100644
--- a/docs/_ext/djangodocs.py
+++ b/docs/_ext/djangodocs.py
@@ -179,7 +179,7 @@ class SnippetWithFilename(Directive):
option_spec = {'filename': directives.unchanged_required}
def run(self):
- code = u'\n'.join(self.content)
+ code = '\n'.join(self.content)
literal = snippet_with_filename(code, code)
if self.arguments:
@@ -344,9 +344,9 @@ class DjangoStandaloneHTMLBuilder(StandaloneHTMLBuilder):
xrefs = self.env.domaindata["std"]["objects"]
templatebuiltins = {
"ttags": [n for ((t, n), (l, a)) in xrefs.items()
- if t == "templatetag" and l == "ref/templates/builtins"],
+ if t == "templatetag" and l == "ref/templates/builtins"],
"tfilters": [n for ((t, n), (l, a)) in xrefs.items()
- if t == "templatefilter" and l == "ref/templates/builtins"],
+ if t == "templatefilter" and l == "ref/templates/builtins"],
}
outfilename = os.path.join(self.outdir, "templatebuiltins.js")
with open(outfilename, 'w') as fp:
diff --git a/docs/_theme/djangodocs/layout.html b/docs/_theme/djangodocs/layout.html
index fa0b497450..6bb3653084 100644
--- a/docs/_theme/djangodocs/layout.html
+++ b/docs/_theme/djangodocs/layout.html
@@ -17,6 +17,9 @@
{%- endmacro %}
{% block extrahead %}
+{# When building htmlhelp (CHM format) disable JQuery inclusion, #}
+{# as it causes problems in compiled CHM files. #}
+{% if builder != "htmlhelp" %}
{{ super() }}
+{% endif %}
{% endblock %}
{% block document %}
diff --git a/docs/faq/general.txt b/docs/faq/general.txt
index 5db3141f82..801cb334ca 100644
--- a/docs/faq/general.txt
+++ b/docs/faq/general.txt
@@ -159,7 +159,7 @@ Django has special conveniences for building "CMS-y" apps, that doesn't mean
it's not just as appropriate for building "non-CMS-y" apps (whatever that
means!).
-.. _Drupal: http://drupal.org/
+.. _Drupal: https://drupal.org/
How can I download the Django documentation to read it offline?
---------------------------------------------------------------
@@ -183,7 +183,7 @@ Where can I find Django developers for hire?
Consult our `developers for hire page`_ for a list of Django developers who
would be happy to help you.
-You might also be interested in posting a job to http://djangogigs.com/ .
+You might also be interested in posting a job to https://djangogigs.com/ .
If you want to find Django-capable people in your local area, try
https://people.djangoproject.com/ .
@@ -198,7 +198,7 @@ software are still a matter of some debate.
For example, `APA style`_, would dictate something like::
- Django (Version 1.5) [Computer Software]. (2013). Retrieved from http://djangoproject.com.
+ Django (Version 1.5) [Computer Software]. (2013). Retrieved from https://djangoproject.com.
However, the only true guide is what your publisher will accept, so get a copy
of those guidelines and fill in the gaps as best you can.
@@ -208,7 +208,7 @@ Foundation".
If you need a publishing location, use "Lawrence, Kansas".
-If you need a web address, use http://djangoproject.com.
+If you need a web address, use https://djangoproject.com.
If you need a name, just use "Django", without any tagline.
diff --git a/docs/howto/custom-model-fields.txt b/docs/howto/custom-model-fields.txt
index 9c75f27c02..6054a83f10 100644
--- a/docs/howto/custom-model-fields.txt
+++ b/docs/howto/custom-model-fields.txt
@@ -251,7 +251,7 @@ you'll need to supplement the values being passed.
The contract of ``deconstruct`` is simple; it returns a tuple of four items:
the field's attribute name, the full import path of the field class, the
-position arguments (as a list), and the keyword arguments (as a dict).
+positional arguments (as a list), and the keyword arguments (as a dict).
As a custom field author, you don't need to care about the first two values;
the base ``Field`` class has all the code to work out the field's attribute
@@ -304,7 +304,7 @@ Pay extra attention if you set new default values for arguments in the
than disappearing if they take on the old default value.
In addition, try to avoid returning values as positional arguments; where
-possible, return values as keyword arguments for maximum future compatability.
+possible, return values as keyword arguments for maximum future compatibility.
Of course, if you change the names of things more often than their position
in the constructor's argument list, you might prefer positional, but bear in
mind that people will be reconstructing your field from the serialized version
diff --git a/docs/howto/deployment/index.txt b/docs/howto/deployment/index.txt
index 688bae2397..1a5d137596 100644
--- a/docs/howto/deployment/index.txt
+++ b/docs/howto/deployment/index.txt
@@ -31,4 +31,4 @@ the easiest, fastest, and most stable deployment choice.
``mod_python`` was first deprecated, then completely removed in
Django 1.5.
-.. _chapter 12 of the django book (second edition): http://djangobook.com/en/2.0/chapter12/
+.. _chapter 12 of the django book (second edition): http://djangobook.com/en/2.0/chapter12.html
diff --git a/docs/howto/deployment/wsgi/gunicorn.txt b/docs/howto/deployment/wsgi/gunicorn.txt
index 14c80af0a0..95051b690b 100644
--- a/docs/howto/deployment/wsgi/gunicorn.txt
+++ b/docs/howto/deployment/wsgi/gunicorn.txt
@@ -13,7 +13,7 @@ There are two ways to use Gunicorn with Django. One is to have Gunicorn treat
Django as just another WSGI application. The second is to use Gunicorn's
special `integration with Django`_.
-.. _integration with Django: http://gunicorn.org/run.html#django-manage-py
+.. _integration with Django: http://docs.gunicorn.org/en/latest/run.html#django-manage-py
Installing Gunicorn
===================
diff --git a/docs/howto/deployment/wsgi/uwsgi.txt b/docs/howto/deployment/wsgi/uwsgi.txt
index 59dddda418..47f1e7a61d 100644
--- a/docs/howto/deployment/wsgi/uwsgi.txt
+++ b/docs/howto/deployment/wsgi/uwsgi.txt
@@ -32,7 +32,7 @@ command. For example:
# Or install LTS (long term support).
$ sudo pip install http://projects.unbit.it/downloads/uwsgi-lts.tar.gz
-.. _installation procedures: http://projects.unbit.it/uwsgi/wiki/Install
+.. _installation procedures: http://uwsgi-docs.readthedocs.org/en/latest/Install.html
.. warning::
@@ -58,7 +58,7 @@ Configuring and starting the uWSGI server for Django
uWSGI supports multiple ways to configure the process. See uWSGI's
`configuration documentation`_ and `examples`_
-.. _configuration documentation: http://projects.unbit.it/uwsgi/wiki/Doc
+.. _configuration documentation: https://uwsgi.readthedocs.org/en/latest/Configuration.html
.. _examples: http://projects.unbit.it/uwsgi/wiki/Example
Here's an example command to start a uWSGI server::
@@ -111,4 +111,4 @@ Example ini configuration file usage::
See the uWSGI docs on `managing the uWSGI process`_ for information on
starting, stopping and reloading the uWSGI workers.
-.. _managing the uWSGI process: http://projects.unbit.it/uwsgi/wiki/Management
+.. _managing the uWSGI process: http://uwsgi-docs.readthedocs.org/en/latest/Management.html
diff --git a/docs/howto/jython.txt b/docs/howto/jython.txt
index 96a3d64251..2d2c22872a 100644
--- a/docs/howto/jython.txt
+++ b/docs/howto/jython.txt
@@ -30,7 +30,7 @@ such as `Apache Tomcat`_. Full JavaEE applications servers such as `GlassFish`_
or `JBoss`_ are also OK, if you need the extra features they include.
.. _`Apache Tomcat`: http://tomcat.apache.org/
-.. _GlassFish: http://glassfish.java.net/
+.. _GlassFish: https://glassfish.java.net/
.. _JBoss: http://www.jboss.org/
Installing Django
diff --git a/docs/index.txt b/docs/index.txt
index df6861aa1c..bd3baf8a9c 100644
--- a/docs/index.txt
+++ b/docs/index.txt
@@ -26,7 +26,7 @@ Having trouble? We'd like to help!
* Report bugs with Django in our `ticket tracker`_.
.. _archives: http://groups.google.com/group/django-users/
-.. _post a question: http://groups.google.com/group/django-users/
+.. _post a question: https://groups.google.com/d/forum/django-users
.. _#django IRC channel: irc://irc.freenode.net/django
.. _IRC logs: http://django-irc-logs.com/
.. _ticket tracker: https://code.djangoproject.com/
@@ -229,7 +229,8 @@ regions:
* :doc:`Overview ` |
:doc:`Internationalization ` |
- :ref:`Localization `
+ :ref:`Localization ` |
+ :doc:`Localized Web UI formatting and form input `
* :doc:`"Local flavor" `
* :doc:`Time zones `
diff --git a/docs/internals/committers.txt b/docs/internals/committers.txt
index 3b37a2ca58..0da26a31eb 100644
--- a/docs/internals/committers.txt
+++ b/docs/internals/committers.txt
@@ -223,7 +223,7 @@ Karen Tracey
Jannis lives in Berlin, Germany.
- .. _Jannis Leidel: http://jezdez.com/
+ .. _Jannis Leidel: https://jezdez.com/
.. _Bauhaus-University Weimar: http://www.uni-weimar.de/
.. _virtualenv: http://www.virtualenv.org/
.. _pip: http://www.pip-installer.org/
@@ -396,7 +396,7 @@ Tim Graham
He works in a `management consulting company`_ in Paris, France.
- .. _Aymeric Augustin: http://myks.org/
+ .. _Aymeric Augustin: https://myks.org/
.. _management consulting company: http://www.polyconseil.fr/
`Claude Paroz`_
@@ -410,7 +410,7 @@ Tim Graham
Django-based `l10n.gnome.org`_.
.. _Claude Paroz: http://www.2xlibre.net
- .. _l10n.gnome.org: http://l10n.gnome.org
+ .. _l10n.gnome.org: https://l10n.gnome.org
Anssi Kääriäinen
Anssi works as a developer at Finnish National Institute for Health and
diff --git a/docs/internals/contributing/localizing.txt b/docs/internals/contributing/localizing.txt
index 01b88d8d9a..1b23a2296c 100644
--- a/docs/internals/contributing/localizing.txt
+++ b/docs/internals/contributing/localizing.txt
@@ -61,6 +61,6 @@ Django source tree, as for any code change:
``Translations``, and attach the patch to it.
.. _Transifex: https://www.transifex.com/
-.. _Django i18n mailing list: http://groups.google.com/group/django-i18n/
+.. _Django i18n mailing list: https://groups.google.com/d/forum/django-i18n
.. _Django project page: https://www.transifex.com/projects/p/django-core/
-.. _Transifex User Guide: http://help.transifex.com/
+.. _Transifex User Guide: http://support.transifex.com/
diff --git a/docs/internals/contributing/writing-code/coding-style.txt b/docs/internals/contributing/writing-code/coding-style.txt
index afcf9c072c..c3d1b7c758 100644
--- a/docs/internals/contributing/writing-code/coding-style.txt
+++ b/docs/internals/contributing/writing-code/coding-style.txt
@@ -213,4 +213,4 @@ Miscellaneous
change to the ``AUTHORS`` file in your patch if you make more than a
single trivial change.
-.. _flake8: http://pypi.python.org/pypi/flake8
+.. _flake8: https://pypi.python.org/pypi/flake8
diff --git a/docs/internals/contributing/writing-code/unit-tests.txt b/docs/internals/contributing/writing-code/unit-tests.txt
index e83d0bef17..2bbd9dcf0e 100644
--- a/docs/internals/contributing/writing-code/unit-tests.txt
+++ b/docs/internals/contributing/writing-code/unit-tests.txt
@@ -154,6 +154,7 @@ dependencies:
* bcrypt_
* docutils_
+* numpy_
* Pillow_
* PyYAML_
* pytz_
@@ -182,14 +183,15 @@ associated tests will be skipped.
.. _bcrypt: https://pypi.python.org/pypi/bcrypt
.. _docutils: https://pypi.python.org/pypi/docutils
+.. _numpy: https://pypi.python.org/pypi/numpy
.. _Pillow: https://pypi.python.org/pypi/Pillow/
.. _PyYAML: http://pyyaml.org/wiki/PyYAML
.. _pytz: https://pypi.python.org/pypi/pytz/
-.. _setuptools: http://pypi.python.org/pypi/setuptools/
+.. _setuptools: https://pypi.python.org/pypi/setuptools/
.. _memcached: http://memcached.org/
.. _gettext: http://www.gnu.org/software/gettext/manual/gettext.html
-.. _selenium: http://pypi.python.org/pypi/selenium
-.. _pip requirements files: http://www.pip-installer.org/en/latest/requirements.html
+.. _selenium: https://pypi.python.org/pypi/selenium
+.. _pip requirements files: http://www.pip-installer.org/en/latest/cookbook.html#requirements-files
Code coverage
~~~~~~~~~~~~~
diff --git a/docs/internals/contributing/writing-documentation.txt b/docs/internals/contributing/writing-documentation.txt
index e565b4d54f..94a9ae3cb4 100644
--- a/docs/internals/contributing/writing-documentation.txt
+++ b/docs/internals/contributing/writing-documentation.txt
@@ -65,6 +65,19 @@ Primer `. After that, you'll want to read about the
:ref:`Sphinx-specific markup ` that's used to manage
metadata, indexing, and cross-references.
+Writing style
+-------------
+
+When using pronouns in reference to a hypothetical person, such as "a user with
+a session cookie", gender neutral pronouns (they/their/them) should be used.
+Instead of:
+
+* he or she... use they.
+* him or her... use them.
+* his or her... use their.
+* his or hers... use theirs.
+* himself or herself... use themselves.
+
Commonly used terms
-------------------
@@ -250,23 +263,26 @@ example:
.. code-block:: rst
- .. setting:: ADMIN_FOR
+ .. setting:: ADMINS
- ADMIN_FOR
- ---------
+ ADMINS
+ ------
Default: ``()`` (Empty tuple)
- Used for admin-site settings modules, this should be a tuple of
- settings modules (in the format ``'foo.bar.baz'``) for which this site
- is an admin.
+ A tuple that lists people who get code error notifications. When
+ ``DEBUG=False`` and a view raises an exception, Django will email these people
+ with the full exception information. Each member of the tuple should be a tuple
+ of (Full name, email address). Example::
- The admin site uses this in its automatically-introspected
- documentation of models, views and template tags.
+ (('John', 'john@example.com'), ('Mary', 'mary@example.com'))
+
+ Note that Django will email *all* of these people whenever an error happens.
+ See :doc:`/howto/error-reporting` for more information.
This marks up the following header as the "canonical" target for the
- setting ``ADMIN_FOR`` This means any time I talk about ``ADMIN_FOR``,
- I can reference it using ``:setting:`ADMIN_FOR```.
+ setting ``ADMINS``. This means any time I talk about ``ADMINS``,
+ I can reference it using ``:setting:`ADMINS```.
That's basically how everything fits together.
@@ -307,8 +323,8 @@ look better:
__ http://sphinx-doc.org/markup/desc.html#info-field-lists
-* Whenever possible, use links. So, use ``:setting:`ADMIN_FOR``` instead
- of ````ADMIN_FOR````.
+* Whenever possible, use links. So, use ``:setting:`ADMINS``` instead
+ of ````ADMINS````.
* Use directives where appropriate. Some directives
(e.g. ``.. setting::``) are prefix-style directives; they go *before*
diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt
index 4c44c70501..ef2561a05d 100644
--- a/docs/internals/deprecation.txt
+++ b/docs/internals/deprecation.txt
@@ -147,6 +147,10 @@ these changes.
* The session key ``django_language`` will no longer be read for backwards
compatibility.
+* Geographic Sitemaps will be removed
+ (``django.contrib.gis.sitemaps.views.index`` and
+ ``django.contrib.gis.sitemaps.views.sitemap``).
+
1.9
---
@@ -218,6 +222,12 @@ these changes.
* ``django.core.cache.get_cache`` will be removed. Add suitable entries
to :setting:`CACHES` and use :data:`django.core.cache.caches` instead.
+* ``django.db.models.loading`` will be removed. Use the new application
+ loading APIs instead. Several undocumented methods of the ``AppCache`` class
+ will also be removed.
+
+* Passing callable arguments to querysets will no longer be possible.
+
2.0
---
diff --git a/docs/internals/mailing-lists.txt b/docs/internals/mailing-lists.txt
index e5e6ea9a3d..68b8035758 100644
--- a/docs/internals/mailing-lists.txt
+++ b/docs/internals/mailing-lists.txt
@@ -31,7 +31,7 @@ installation, usage, or debugging of Django.
* `django-users subscription email address`_
* `django-users posting email`_
-.. _django-users mailing archive: http://groups.google.com/group/django-users/
+.. _django-users mailing archive: https://groups.google.com/d/forum/django-users
.. _django-users subscription email address: mailto:django-users+subscribe@googlegroups.com
.. _django-users posting email: mailto:django-users@googlegroups.com
@@ -48,7 +48,7 @@ Django development.
* `django-core-mentorship subscription email address`_
* `django-core-mentorship posting email`_
-.. _django-core-mentorship mailing archive: http://groups.google.com/group/django-core-mentorship/
+.. _django-core-mentorship mailing archive: https://groups.google.com/d/forum/django-core-mentorship
.. _django-core-mentorship subscription email address: mailto:django-core-mentorship+subscribe@googlegroups.com
.. _django-core-mentorship posting email: mailto:django-core-mentorship@googlegroups.com
@@ -69,7 +69,7 @@ The discussion about the development of Django itself takes place here.
* `django-developers subscription email address`_
* `django-developers posting email`_
-.. _django-developers mailing archive: http://groups.google.com/group/django-developers/
+.. _django-developers mailing archive: https://groups.google.com/d/forum/django-developers
.. _django-developers subscription email address: mailto:django-developers+subscribe@googlegroups.com
.. _django-developers posting email: mailto:django-developers@googlegroups.com
@@ -85,7 +85,7 @@ bugfixes.
* `django-announce subscription email address`_
* `django-announce posting email`_
-.. _django-announce mailing archive: http://groups.google.com/group/django-announce/
+.. _django-announce mailing archive: https://groups.google.com/d/forum/django-announce
.. _django-announce subscription email address: mailto:django-announce+subscribe@googlegroups.com
.. _django-announce posting email: mailto:django-announce@googlegroups.com
@@ -101,6 +101,6 @@ by developers and interested community members.
* `django-updates subscription email address`_
* `django-updates posting email`_
-.. _django-updates mailing archive: http://groups.google.com/group/django-updates/
+.. _django-updates mailing archive: https://groups.google.com/d/forum/django-updates
.. _django-updates subscription email address: mailto:django-updates+subscribe@googlegroups.com
.. _django-updates posting email: mailto:django-updates@googlegroups.com
diff --git a/docs/internals/security.txt b/docs/internals/security.txt
index 7c0a9242d5..d9aab377f5 100644
--- a/docs/internals/security.txt
+++ b/docs/internals/security.txt
@@ -113,7 +113,7 @@ On the day of disclosure, we will take the following steps:
4. Post a notice to the |django-announce| mailing list that links to the blog
post.
-.. _the Python Package Index: http://pypi.python.org/pypi
+.. _the Python Package Index: https://pypi.python.org/pypi
.. _the official Django development blog: https://www.djangoproject.com/weblog/
If a reported issue is believed to be particularly time-sensitive --
diff --git a/docs/intro/index.txt b/docs/intro/index.txt
index 9e88402f6d..a7872017b9 100644
--- a/docs/intro/index.txt
+++ b/docs/intro/index.txt
@@ -35,8 +35,8 @@ place: read this material to quickly get up and running.
a few other `books about Python`_.
.. _python: http://python.org/
- .. _list of Python resources for non-programmers: http://wiki.python.org/moin/BeginnersGuide/NonProgrammers
+ .. _list of Python resources for non-programmers: https://wiki.python.org/moin/BeginnersGuide/NonProgrammers
.. _Python 2: http://diveintopython.net/
.. _Python 3: http://diveintopython3.net/
.. _dead-tree version: http://www.amazon.com/exec/obidos/ASIN/1590593561/ref=nosim/jacobian20
- .. _books about Python: http://wiki.python.org/moin/PythonBooks
+ .. _books about Python: https://wiki.python.org/moin/PythonBooks
diff --git a/docs/intro/reusable-apps.txt b/docs/intro/reusable-apps.txt
index 99b383585f..8959aae06a 100644
--- a/docs/intro/reusable-apps.txt
+++ b/docs/intro/reusable-apps.txt
@@ -19,7 +19,7 @@ could save some of this repeated work?
Reusability is the way of life in Python. `The Python Package Index (PyPI)
`_ has a vast
range of packages you can use in your own Python programs. Check out `Django
-Packages `_ for existing reusable apps you could
+Packages `_ for existing reusable apps you could
incorporate in your project. Django itself is also just a Python package. This
means that you can take existing Python packages or Django apps and compose
them into your own web project. You only need to write the parts that make
@@ -108,7 +108,7 @@ Django with pip`. You can install ``setuptools``
the same way.
.. _setuptools: https://pypi.python.org/pypi/setuptools
-.. _pip: http://pypi.python.org/pypi/pip
+.. _pip: https://pypi.python.org/pypi/pip
Packaging your app
==================
@@ -128,6 +128,13 @@ this. For a small app like polls, this process isn't too difficult.
This helps others looking for Django apps identify your app as Django
specific.
+ The application names (that is, the final dotted part of the
+ path to the module containing ``models.py``) defined in
+ :setting:`INSTALLED_APPS` *must* be unique. Avoid using the
+ same name as any of the Django :doc:`contrib packages
+ `, for example ``auth``, ``admin`` or
+ ``messages``.
+
2. Move the ``polls`` directory into the ``django-polls`` directory.
3. Create a file ``django-polls/README.rst`` with the following contents:
@@ -175,7 +182,7 @@ this. For a small app like polls, this process isn't too difficult.
5. Next we'll create a ``setup.py`` file which provides details about how to
build and install the app. A full explanation of this file is beyond the
scope of this tutorial, but the `setuptools docs
- `_ have a good
+ `_ have a good
explanation. Create a file ``django-polls/setup.py`` with the following
contents:
@@ -239,7 +246,7 @@ this. For a small app like polls, this process isn't too difficult.
Note that the ``docs`` directory won't be included in your package unless
you add some files to it. Many Django apps also provide their documentation
- online through sites like `readthedocs.org `_.
+ online through sites like `readthedocs.org `_.
8. Try building your package with ``python setup.py sdist`` (run from inside
``django-polls``). This creates a directory called ``dist`` and builds your
@@ -278,7 +285,7 @@ working. We'll now fix this by installing our new ``django-polls`` package.
pip uninstall django-polls
-.. _pip: http://pypi.python.org/pypi/pip
+.. _pip: https://pypi.python.org/pypi/pip
Publishing your app
===================
diff --git a/docs/intro/tutorial04.txt b/docs/intro/tutorial04.txt
index f7aa79e6f7..fbccb93a70 100644
--- a/docs/intro/tutorial04.txt
+++ b/docs/intro/tutorial04.txt
@@ -34,7 +34,8 @@ A quick rundown:
``value`` of each radio button is the associated question choice's ID. The
``name`` of each radio button is ``"choice"``. That means, when somebody
selects one of the radio buttons and submits the form, it'll send the
- POST data ``choice=3``. This is the basic concept of HTML forms.
+ POST data ``choice=#`` where # is the ID of the selected choice. This is the
+ basic concept of HTML forms.
* We set the form's ``action`` to ``{% url 'polls:vote' question.id %}``, and we
set ``method="post"``. Using ``method="post"`` (as opposed to
diff --git a/docs/intro/tutorial05.txt b/docs/intro/tutorial05.txt
index 90d3894738..90acfe62d3 100644
--- a/docs/intro/tutorial05.txt
+++ b/docs/intro/tutorial05.txt
@@ -677,7 +677,7 @@ a piece of code, it usually means that code should be refactored or removed.
Coverage will help to identify dead code. See
:ref:`topics-testing-code-coverage` for details.
-:doc:`Testing Django applications ` has comprehensive
+:doc:`Testing in Django ` has comprehensive
information about testing.
.. _Selenium: http://seleniumhq.org/
diff --git a/docs/man/django-admin.1 b/docs/man/django-admin.1
index f1b568daf5..c72c3520b5 100644
--- a/docs/man/django-admin.1
+++ b/docs/man/django-admin.1
@@ -192,7 +192,8 @@ Ignore files or directories matching this glob-style pattern. Use multiple
times to ignore more (makemessages command).
.TP
.I \-\-no\-default\-ignore
-Don't ignore the common private glob-style patterns 'CVS', '.*' and '*~' (makemessages command).
+Don't ignore the common private glob-style patterns 'CVS', '.*', '*~' and '*.pyc'
+(makemessages command).
.TP
.I \-\-no\-wrap
Don't break long message lines into several lines (makemessages command).
diff --git a/docs/ref/clickjacking.txt b/docs/ref/clickjacking.txt
index cb3ac1bcd1..bb50f97f08 100644
--- a/docs/ref/clickjacking.txt
+++ b/docs/ref/clickjacking.txt
@@ -20,8 +20,8 @@ purchase an item. A user has chosen to stay logged into the store all the time
for convenience. An attacker site might create an "I Like Ponies" button on one
of their own pages, and load the store's page in a transparent iframe such that
the "Buy Now" button is invisibly overlaid on the "I Like Ponies" button. If the
-user visits the attacker site and clicks "I Like Ponies" he or she will inadvertently
-click on the online store's "Buy Now" button and unknowingly purchase the item.
+user visits the attacker's site, clicking "I Like Ponies" will cause an
+inadvertent click on the "Buy Now" button and an unknowing purchase of the item.
.. _clickjacking-prevention:
diff --git a/docs/ref/contrib/admin/admindocs.txt b/docs/ref/contrib/admin/admindocs.txt
index 8809502288..f903e8efff 100644
--- a/docs/ref/contrib/admin/admindocs.txt
+++ b/docs/ref/contrib/admin/admindocs.txt
@@ -28,8 +28,6 @@ the following:
``r'^admin/'`` entry, so that requests to ``/admin/doc/`` don't get
handled by the latter entry.
* Install the docutils Python module (http://docutils.sf.net/).
-* **Optional:** Linking to templates requires the :setting:`ADMIN_FOR`
- setting to be configured.
* **Optional:** Using the admindocs bookmarklets requires
``django.contrib.admindocs.middleware.XViewMiddleware`` to be installed.
diff --git a/docs/ref/contrib/gis/admin.txt b/docs/ref/contrib/gis/admin.txt
index d1a9fc1dcb..d3bc1d26a1 100644
--- a/docs/ref/contrib/gis/admin.txt
+++ b/docs/ref/contrib/gis/admin.txt
@@ -67,6 +67,6 @@ GeoDjango's admin site
.. class:: OSMGeoAdmin
A subclass of :class:`GeoModelAdmin` that uses a spherical mercator projection
- with `OpenStreetMap `_ street data tiles.
+ with `OpenStreetMap `_ street data tiles.
See the :ref:`OSMGeoAdmin introduction `
in the tutorial for a usage example.
diff --git a/docs/ref/contrib/gis/feeds.txt b/docs/ref/contrib/gis/feeds.txt
index 7b1b6ebccf..72c51b57ab 100644
--- a/docs/ref/contrib/gis/feeds.txt
+++ b/docs/ref/contrib/gis/feeds.txt
@@ -13,7 +13,7 @@ Django's, please consult :doc:`Django's syndication documentation
.. _W3C Geo: http://www.w3.org/2003/01/geo/
-__ http://georss.org/1.0#simple
+__ http://georss.org/simple.html
Example
=======
diff --git a/docs/ref/contrib/gis/forms-api.txt b/docs/ref/contrib/gis/forms-api.txt
index da4c96792e..435bcfc8b1 100644
--- a/docs/ref/contrib/gis/forms-api.txt
+++ b/docs/ref/contrib/gis/forms-api.txt
@@ -14,7 +14,7 @@ display and edit geolocalized data on a map. By default, they use
`OpenLayers`_-powered maps, with a base WMS layer provided by `Metacarta`_.
.. _OpenLayers: http://openlayers.org/
-.. _Metacarta: http://metacarta.com/
+.. _Metacarta: http://www.metacarta.com/
Field arguments
===============
diff --git a/docs/ref/contrib/gis/install/index.txt b/docs/ref/contrib/gis/install/index.txt
index 3cf8a29822..4bd0471572 100644
--- a/docs/ref/contrib/gis/install/index.txt
+++ b/docs/ref/contrib/gis/install/index.txt
@@ -140,7 +140,7 @@ community! You can:
sure to provide a complete description of the problem, versions used,
and specify the component as "GIS".
-__ http://groups.google.com/group/geodjango
+__ https://groups.google.com/d/forum/geodjango
__ https://code.djangoproject.com/newticket
.. _libsettings:
@@ -273,7 +273,7 @@ Summary::
$ brew install gdal
$ brew install libgeoip
-__ http://mxcl.github.com/homebrew/
+__ http://brew.sh/
.. _Apple Developer Tools: https://developer.apple.com/technologies/tools/
.. _kyngchaos:
@@ -285,7 +285,7 @@ William Kyngesburye provides a number of `geospatial library binary packages`__
that make it simple to get GeoDjango installed on OS X without compiling
them from source. However, the `Apple Developer Tools`_ are still necessary
for compiling the Python database adapters :ref:`psycopg2_kyngchaos` (for PostGIS)
-and ``pysqlite2`` (for SpatiaLite).
+and :ref:`pysqlite2` (for SpatiaLite).
.. note::
diff --git a/docs/ref/contrib/gis/install/postgis.txt b/docs/ref/contrib/gis/install/postgis.txt
index c651fe8fca..75597fbb38 100644
--- a/docs/ref/contrib/gis/install/postgis.txt
+++ b/docs/ref/contrib/gis/install/postgis.txt
@@ -15,7 +15,7 @@ might also need additional libraries, see `PostGIS requirements`_.
when using GeoDjango with PostGIS.
.. _psycopg2: http://initd.org/psycopg/
-.. _PostGIS requirements: http://www.postgis.org/documentation/manual-2.0/postgis_installation.html#id2711662
+.. _PostGIS requirements: http://www.postgis.org/documentation/manual-2.0/postgis_installation.html#id554707
On Debian/Ubuntu, you are advised to install the following packages:
postgresql-x.x, postgresql-x.x-postgis, postgresql-server-dev-x.x,
diff --git a/docs/ref/contrib/gis/install/spatialite.txt b/docs/ref/contrib/gis/install/spatialite.txt
index ab40600205..b625942fa6 100644
--- a/docs/ref/contrib/gis/install/spatialite.txt
+++ b/docs/ref/contrib/gis/install/spatialite.txt
@@ -107,6 +107,60 @@ Finally, do the same for the SpatiaLite tools::
__ http://www.gaia-gis.it/gaia-sins/libspatialite-sources/
+.. _pysqlite2:
+
+pysqlite2
+^^^^^^^^^
+
+If you've decided to use a :ref:`newer version of pysqlite2
+` instead of the ``sqlite3`` Python stdlib
+module, then you need to make sure it can load external extensions (i.e. the
+required ``enable_load_extension`` method is available so ``SpatiaLite`` can be
+loaded).
+
+This might involve building it yourself. For this, download pysqlite2 2.6, and
+untar::
+
+ $ wget https://pypi.python.org/packages/source/p/pysqlite/pysqlite-2.6.3.tar.gz
+ $ tar xzf pysqlite-2.6.3.tar.gz
+ $ cd pysqlite-2.6.3
+
+Next, use a text editor (e.g., ``emacs`` or ``vi``) to edit the ``setup.cfg`` file
+to look like the following:
+
+.. code-block:: ini
+
+ [build_ext]
+ #define=
+ include_dirs=/usr/local/include
+ library_dirs=/usr/local/lib
+ libraries=sqlite3
+ #define=SQLITE_OMIT_LOAD_EXTENSION
+
+or if you are on Mac OS X:
+
+.. code-block:: ini
+
+ [build_ext]
+ #define=
+ include_dirs=/Library/Frameworks/SQLite3.framework/unix/include
+ library_dirs=/Library/Frameworks/SQLite3.framework/unix/lib
+ libraries=sqlite3
+ #define=SQLITE_OMIT_LOAD_EXTENSION
+
+.. note::
+
+ The important thing here is to make sure you comment out the
+ ``define=SQLITE_OMIT_LOAD_EXTENSION`` flag and that the ``include_dirs``
+ and ``library_dirs`` settings are uncommented and set to the appropriate
+ path if the SQLite header files and libraries are not in ``/usr/include``
+ and ``/usr/lib``, respectively.
+
+After modifying ``setup.cfg`` appropriately, then run the ``setup.py`` script
+to build and install::
+
+ $ sudo python setup.py install
+
.. _spatialite_macosx:
Mac OS X-specific instructions
diff --git a/docs/ref/contrib/gis/measure.txt b/docs/ref/contrib/gis/measure.txt
index 2a05b42600..197f584729 100644
--- a/docs/ref/contrib/gis/measure.txt
+++ b/docs/ref/contrib/gis/measure.txt
@@ -175,6 +175,6 @@ Measurement API
Alias for :class:`Area` class.
.. rubric:: Footnotes
-.. [#] `Robert Coup `_ is the initial author of the measure objects,
+.. [#] `Robert Coup `_ is the initial author of the measure objects,
and was inspired by Brian Beck's work in `geopy `_
and Geoff Biggs' PhD work on dimensioned units for robotics.
diff --git a/docs/ref/contrib/gis/sitemaps.txt b/docs/ref/contrib/gis/sitemaps.txt
index 0ab8f75825..60df2b87b2 100644
--- a/docs/ref/contrib/gis/sitemaps.txt
+++ b/docs/ref/contrib/gis/sitemaps.txt
@@ -23,4 +23,4 @@ Reference
-----------------
.. rubric:: Footnotes
-.. [#] Google, Inc., `What is a Geo Sitemap? `_.
+.. [#] Google, Inc., `What is a Geo Sitemap? `_.
diff --git a/docs/ref/contrib/gis/tutorial.txt b/docs/ref/contrib/gis/tutorial.txt
index 17ce1edbf0..183b2b9875 100644
--- a/docs/ref/contrib/gis/tutorial.txt
+++ b/docs/ref/contrib/gis/tutorial.txt
@@ -747,7 +747,7 @@ entries -- the borders may be edited by clicking on a polygon and dragging
the vertexes to the desired position.
.. _OpenLayers: http://openlayers.org/
-.. _Open Street Map: http://openstreetmap.org/
+.. _Open Street Map: http://www.openstreetmap.org/
.. _Vector Map Level 0: http://earth-info.nga.mil/publications/vmap0.html
.. _OSGeo: http://www.osgeo.org
diff --git a/docs/ref/contrib/messages.txt b/docs/ref/contrib/messages.txt
index 690b73d195..487e1dd86c 100644
--- a/docs/ref/contrib/messages.txt
+++ b/docs/ref/contrib/messages.txt
@@ -177,8 +177,9 @@ used tags (which are usually represented as HTML classes for the message)::
Displaying messages
-------------------
+.. function:: get_messages(request)
-In your template, use something like::
+**In your template**, use something like::
{% if messages %}
@@ -212,6 +213,25 @@ is a mapping of the message level names to their numeric value::
{% endif %}
+
+
+**Outside of templates**, you can use
+:func:`~django.contrib.messages.get_messages`::
+
+ from django.contrib.messages import get_messages
+
+ storage = get_messages(request)
+ for message in storage:
+ do_something_with_the_message(message)
+
+For instance, you can fetch all the messages to return them in a
+:ref:`JSONResponseMixin ` instead of a
+:class:`~django.views.generic.base.TemplateResponseMixin`.
+
+:func:`~django.contrib.messages.get_messages` will return an
+instance of the configured storage backend.
+
+
The ``Message`` class
---------------------
diff --git a/docs/ref/contrib/sitemaps.txt b/docs/ref/contrib/sitemaps.txt
index 7b53fc8c12..fefe90a420 100644
--- a/docs/ref/contrib/sitemaps.txt
+++ b/docs/ref/contrib/sitemaps.txt
@@ -454,7 +454,7 @@ generate a Google News compatible sitemap:
{% endspaceless %}
-.. _`Google news sitemaps`: http://support.google.com/webmasters/bin/answer.py?hl=en&answer=74288
+.. _`Google news sitemaps`: https://support.google.com/webmasters/answer/74288?hl=en
Pinging Google
==============
diff --git a/docs/ref/contrib/sites.txt b/docs/ref/contrib/sites.txt
index 225475803f..24b6294278 100644
--- a/docs/ref/contrib/sites.txt
+++ b/docs/ref/contrib/sites.txt
@@ -172,7 +172,7 @@ Getting the current domain for display
LJWorld.com and Lawrence.com both have email alert functionality, which lets
readers sign up to get notifications when news happens. It's pretty basic: A
-reader signs up on a Web form, and he or she immediately gets an email saying,
+reader signs up on a Web form and immediately gets an email saying,
"Thanks for your subscription."
It'd be inefficient and redundant to implement this signup-processing code
diff --git a/docs/ref/contrib/staticfiles.txt b/docs/ref/contrib/staticfiles.txt
index c65533fa0f..3202321269 100644
--- a/docs/ref/contrib/staticfiles.txt
+++ b/docs/ref/contrib/staticfiles.txt
@@ -60,16 +60,19 @@ by the :class:`~django.contrib.staticfiles.storage.CachedStaticFilesStorage`
by default.
By default, collected files receive permissions from
-:setting:`FILE_UPLOAD_PERMISSIONS`. If you would like different permissions for
-these files, you can subclass either of the :ref:`static files storage
-classes ` and specify the ``file_permissions_mode``
-parameter. For example::
+:setting:`FILE_UPLOAD_PERMISSIONS` and collected directories receive permissions
+from :setting:`FILE_UPLOAD_DIRECTORY_PERMISSIONS`. If you would like different
+permissions for these files and/or directories, you can subclass either of the
+:ref:`static files storage classes ` and specify the
+``file_permissions_mode`` and/or ``directory_permissions_mode`` parameters,
+respectively. For example::
from django.contrib.staticfiles import storage
class MyStaticFilesStorage(storage.StaticFilesStorage):
def __init__(self, *args, **kwargs):
kwargs['file_permissions_mode'] = 0o640
+ kwargs['directory_permissions_mode'] = 0o760
super(CustomStaticFilesStorage, self).__init__(*args, **kwargs)
Then set the :setting:`STATICFILES_STORAGE` setting to
@@ -77,9 +80,10 @@ Then set the :setting:`STATICFILES_STORAGE` setting to
.. versionadded:: 1.7
- The ability to override ``file_permissions_mode`` is new in Django 1.7.
- Previously the file permissions always used
- :setting:`FILE_UPLOAD_PERMISSIONS`.
+ The ability to override ``file_permissions_mode`` and
+ ``directory_permissions_mode`` is new in Django 1.7. Previously the file
+ permissions always used :setting:`FILE_UPLOAD_PERMISSIONS` and the directory
+ permissions always used :setting:`FILE_UPLOAD_DIRECTORY_PERMISSIONS`.
.. highlight:: console
diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt
index 2b13af8ca2..f04b8830f7 100644
--- a/docs/ref/databases.txt
+++ b/docs/ref/databases.txt
@@ -604,9 +604,8 @@ version of SQLite.
Using newer versions of the SQLite DB-API 2.0 driver
----------------------------------------------------
-For versions of Python 2.5 or newer that include ``sqlite3`` in the standard
-library Django will now use a ``pysqlite2`` interface in preference to
-``sqlite3`` if it finds one is available.
+Django will use a ``pysqlite2`` module in preference to ``sqlite3`` as shipped
+with the Python standard library if it finds one is available.
This provides the ability to upgrade both the DB-API 2.0 interface or SQLite 3
itself to versions newer than the ones included with your particular Python
@@ -851,9 +850,9 @@ Using a 3rd-party database backend
In addition to the officially supported databases, there are backends provided
by 3rd parties that allow you to use other databases with Django:
-* `Sybase SQL Anywhere`_
+* `SAP SQL Anywhere`_
* `IBM DB2`_
-* `Microsoft SQL Server 2005`_
+* `Microsoft SQL Server`_
* Firebird_
* ODBC_
* ADSDB_
@@ -863,9 +862,9 @@ vary considerably. Queries regarding the specific capabilities of these
unofficial backends, along with any support queries, should be directed to
the support channels provided by each 3rd party project.
-.. _Sybase SQL Anywhere: http://code.google.com/p/sqlany-django/
+.. _SAP SQL Anywhere: https://github.com/sqlanywhere/sqlany-django
.. _IBM DB2: http://code.google.com/p/ibm-db/
-.. _Microsoft SQL Server 2005: http://code.google.com/p/django-mssql/
-.. _Firebird: http://code.google.com/p/django-firebird/
-.. _ODBC: https://github.com/aurorasoftware/django-pyodbc/
+.. _Microsoft SQL Server: http://django-mssql.readthedocs.org/en/latest/
+.. _Firebird: https://github.com/maxirobaina/django-firebird
+.. _ODBC: https://github.com/lionheart/django-pyodbc/
.. _ADSDB: http://code.google.com/p/adsdb-django/
diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt
index 02c6cd5851..cac7a5bc22 100644
--- a/docs/ref/django-admin.txt
+++ b/docs/ref/django-admin.txt
@@ -557,7 +557,7 @@ Example usage::
Use the ``--ignore`` or ``-i`` option to ignore files or directories matching
the given :mod:`glob`-style pattern. Use multiple times to ignore more.
-These patterns are used by default: ``'CVS'``, ``'.*'``, ``'*~'``
+These patterns are used by default: ``'CVS'``, ``'.*'``, ``'*~'``, ``'*.pyc'``
Example usage::
@@ -584,7 +584,7 @@ for technically skilled translators to understand each message's context.
.. versionadded:: 1.6
Use the ``--keep-pot`` option to prevent Django from deleting the temporary
-.pot file it generates before creating the .po file. This is useful for
+.pot files it generates before creating the .po file. This is useful for
debugging errors which may prevent the final language files from being created.
makemigrations []
@@ -1552,6 +1552,11 @@ color-coded output if your terminal supports ANSI-colored output. It
won't use the color codes if you're piping the command's output to
another program.
+Under Windows, the native console doesn't support ANSI escape sequences so by
+default there is no color output. But you can install the `ANSICON`_
+third-party tool, the Django commands will detect its presence and will make
+use of its services to color output just like on Unix-based platforms.
+
The colors used for syntax highlighting can be customized. Django
ships with three color palettes:
@@ -1636,6 +1641,14 @@ would specify the use of all the colors in the light color palette,
*except* for the colors for errors and notices which would be
overridden as specified.
+.. versionadded:: 1.7
+
+Support for color-coded output from ``django-admin.py`` / ``manage.py``
+utilities on Windows by relying on the ANSICON application was added in Django
+1.7.
+
+.. _ANSICON: http://adoxa.hostmyway.net/ansicon/
+
Bash completion
---------------
diff --git a/docs/ref/files/storage.txt b/docs/ref/files/storage.txt
index bd8fa56787..6d367b70fc 100644
--- a/docs/ref/files/storage.txt
+++ b/docs/ref/files/storage.txt
@@ -29,7 +29,7 @@ Django provides two convenient ways to access the current storage class:
The FileSystemStorage Class
---------------------------
-.. class:: FileSystemStorage([location=None, base_url=None, file_permissions_mode=None])
+.. class:: FileSystemStorage([location=None, base_url=None, file_permissions_mode=None, directory_permissions_mode=None])
The :class:`~django.core.files.storage.FileSystemStorage` class implements
basic file storage on a local filesystem. It inherits from
@@ -46,6 +46,17 @@ The FileSystemStorage Class
The ``file_permissions_mode`` attribute was added. Previously files
always received :setting:`FILE_UPLOAD_PERMISSIONS` permissions.
+ .. attribute:: directory_permissions_mode
+
+ The file system permissions that the directory will receive when it is
+ saved. Defaults to :setting:`FILE_UPLOAD_DIRECTORY_PERMISSIONS`.
+
+ .. versionadded:: 1.7
+
+ The ``directory_permissions_mode`` attribute was added. Previously
+ directories always received
+ :setting:`FILE_UPLOAD_DIRECTORY_PERMISSIONS` permissions.
+
.. note::
The ``FileSystemStorage.delete()`` method will not raise
diff --git a/docs/ref/forms/api.txt b/docs/ref/forms/api.txt
index c15f748308..ef4da9479e 100644
--- a/docs/ref/forms/api.txt
+++ b/docs/ref/forms/api.txt
@@ -117,6 +117,46 @@ The validation routines will only get called once, regardless of how many times
you access :attr:`~Form.errors` or call :meth:`~Form.is_valid`. This means that
if validation has side effects, those side effects will only be triggered once.
+.. method:: Form.errors.as_data()
+
+.. versionadded:: 1.7
+
+Returns a ``dict`` that maps fields to their original ``ValidationError``
+instances.
+
+ >>> f.errors.as_data()
+ {'sender': [ValidationError(['Enter a valid email address.'])],
+ 'subject': [ValidationError(['This field is required.'])]}
+
+.. method:: Form.errors.as_json()
+
+.. versionadded:: 1.7
+
+Returns the errors serialized as JSON.
+
+ >>> f.errors.as_json()
+ {"sender": [{"message": "Enter a valid email address.", "code": "invalid"}],
+ "subject": [{"message": "This field is required.", "code": "required"}]}
+
+.. method:: Form.add_error(field, error)
+
+.. versionadded:: 1.7
+
+This method allows adding errors to specific fields from within the
+``Form.clean()`` method, or from outside the form altogether; for instance
+from a view.
+
+The ``field`` argument is the name of the field to which the errors
+should be added. If its value is ``None`` the error will be treated as
+a non-field error as returned by ``Form.non_field_errors()``.
+
+The ``error`` argument can be a simple string, or preferably an instance of
+``ValidationError``. See :ref:`raising-validation-error` for best practices
+when defining form errors.
+
+Note that ``Form.add_error()`` automatically removes the relevant field from
+``cleaned_data``.
+
Behavior of unbound forms
~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/docs/ref/forms/validation.txt b/docs/ref/forms/validation.txt
index ea294c4108..a07cb93183 100644
--- a/docs/ref/forms/validation.txt
+++ b/docs/ref/forms/validation.txt
@@ -86,9 +86,8 @@ overridden:
be associated with any field in particular. They go into a special
"field" (called ``__all__``), which you can access via the
``non_field_errors()`` method if you need to. If you want to attach
- errors to a specific field in the form, you will need to access the
- ``_errors`` attribute on the form, which is
- :ref:`described later `.
+ errors to a specific field in the form, you need to call
+ :meth:`~django.forms.Form.add_error()`.
Also note that there are special considerations when overriding
the ``clean()`` method of a ``ModelForm`` subclass. (see the
@@ -202,52 +201,6 @@ with ``code``\s and ``params`` but a list of strings will also work::
_('Error 2'),
])
-.. _modifying-field-errors:
-
-Form subclasses and modifying field errors
-------------------------------------------
-
-Sometimes, in a form's ``clean()`` method, you will want to add an error
-message to a particular field in the form. This won't always be appropriate
-and the more typical situation is to raise a ``ValidationError`` from
-``Form.clean()``, which is turned into a form-wide error that is available
-through the ``Form.non_field_errors()`` method.
-
-When you really do need to attach the error to a particular field, you should
-store (or amend) a key in the ``Form._errors`` attribute. This attribute is an
-instance of a ``django.forms.utils.ErrorDict`` class. Essentially, though, it's
-just a dictionary. There is a key in the dictionary for each field in the form
-that has an error. Each value in the dictionary is a
-``django.forms.utils.ErrorList`` instance, which is a list that knows how to
-display itself in different ways. So you can treat ``_errors`` as a dictionary
-mapping field names to lists.
-
-If you want to add a new error to a particular field, you should check whether
-the key already exists in ``self._errors`` or not. If not, create a new entry
-for the given key, holding an empty ``ErrorList`` instance. In either case,
-you can then append your error message to the list for the field name in
-question and it will be displayed when the form is displayed.
-
-There is an example of modifying ``self._errors`` in the following section.
-
-.. admonition:: What's in a name?
-
- You may be wondering why is this attribute called ``_errors`` and not
- ``errors``. Normal Python practice is to prefix a name with an underscore
- if it's not for external usage. In this case, you are subclassing the
- ``Form`` class, so you are essentially writing new internals. In effect,
- you are given permission to access some of the internals of ``Form``.
-
- Of course, any code outside your form should never access ``_errors``
- directly. The data is available to external code through the ``errors``
- property, which populates ``_errors`` before returning it).
-
- Another reason is purely historical: the attribute has been called
- ``_errors`` since the early days of the forms module and changing it now
- (particularly since ``errors`` is used for the read-only property name)
- would be inconvenient for a number of reasons. You can use whichever
- explanation makes you feel more comfortable. The result is the same.
-
Using validation in practice
----------------------------
@@ -366,6 +319,13 @@ write a cleaning method that operates on the ``recipients`` field, like so::
# not.
return data
+Sometimes you may want to add an error message to a particular field from the
+form's ``clean()`` method, in which case you can use
+:meth:`~django.forms.Form.add_error()`. Note that this won't always be
+appropriate and the more typical situation is to raise a ``ValidationError``
+from , which is turned into a form-wide error that is available through the
+``Form.non_field_errors()`` method.
+
Cleaning and validating fields that depend on each other
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -441,26 +401,11 @@ sample) looks like this::
subject = cleaned_data.get("subject")
if cc_myself and subject and "help" not in subject:
- # We know these are not in self._errors now (see discussion
- # below).
msg = u"Must put 'help' in subject when cc'ing yourself."
- self._errors["cc_myself"] = self.error_class([msg])
- self._errors["subject"] = self.error_class([msg])
+ self.add_error('cc_myself', msg)
+ self.add_error('subject', msg)
- # These fields are no longer valid. Remove them from the
- # cleaned data.
- del cleaned_data["cc_myself"]
- del cleaned_data["subject"]
-
-As you can see, this approach requires a bit more effort, not withstanding the
-extra design effort to create a sensible form display. The details are worth
-noting, however. Firstly, earlier we mentioned that you might need to check if
-the field name keys already exist in the ``_errors`` dictionary. In this case,
-since we know the fields exist in ``self.cleaned_data``, they must have been
-valid when cleaned as individual fields, so there will be no corresponding
-entries in ``_errors``.
-
-Secondly, once we have decided that the combined data in the two fields we are
-considering aren't valid, we must remember to remove them from the
-``cleaned_data``. `cleaned_data`` is present even if the form doesn't
-validate, but it contains only field values that did validate.
+The second argument of ``add_error()`` can be a simple string, or preferably
+an instance of ``ValidationError``. See :ref:`raising-validation-error` for
+more details. Note that ``add_error()` automatically removes the field
+from ``cleaned_data``.
diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt
index d56da87091..cc2a4cdcf4 100644
--- a/docs/ref/models/fields.txt
+++ b/docs/ref/models/fields.txt
@@ -1068,14 +1068,13 @@ define the details of how the relation works.
.. attribute:: ForeignKey.limit_choices_to
A dictionary of lookup arguments and values (see :doc:`/topics/db/queries`)
- that limit the available admin or ModelForm choices for this object. Use
- this with functions from the Python ``datetime`` module to limit choices of
- objects by date. For example::
+ that limit the available admin or :class:`ModelForm `
+ choices for this object. For example::
- limit_choices_to = {'pub_date__lte': datetime.date.today}
+ staff_member = models.ForeignKey(User, limit_choices_to={'is_staff': True})
- only allows the choice of related objects with a ``pub_date`` before the
- current date to be chosen.
+ causes the corresponding field on the ``ModelForm`` to list only ``Users``
+ that have ``is_staff=True``.
Instead of a dictionary this can also be a :class:`Q object
` for more :ref:`complex queries
@@ -1234,7 +1233,7 @@ that control how the relationship functions.
Same as :attr:`ForeignKey.related_name`.
-.. attribute:: ForeignKey.related_query_name
+.. attribute:: ManyToManyField.related_query_name
.. versionadded:: 1.6
diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt
index fa4a09756f..e15e55d05c 100644
--- a/docs/ref/models/querysets.txt
+++ b/docs/ref/models/querysets.txt
@@ -2028,9 +2028,15 @@ iexact
Case-insensitive exact match.
+.. versionchanged:: 1.7
+
+ If the value provided for comparision is ``None``, it will be interpreted
+ as an SQL ``NULL`` (see :lookup:`isnull` for more details).
+
Example::
Blog.objects.get(name__iexact='beatles blog')
+ Blog.objects.get(name__iexact=None)
SQL equivalent::
diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt
index e5ded0c9e0..5c466a6c6e 100644
--- a/docs/ref/request-response.txt
+++ b/docs/ref/request-response.txt
@@ -627,7 +627,7 @@ Attributes
.. attribute:: HttpResponse.content
- A string representing the content, encoded from a Unicode
+ A bytestring representing the content, encoded from a Unicode
object if necessary.
.. attribute:: HttpResponse.status_code
diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
index b9a7fd1a81..3e625ace85 100644
--- a/docs/ref/settings.txt
+++ b/docs/ref/settings.txt
@@ -1135,9 +1135,15 @@ FILE_UPLOAD_DIRECTORY_PERMISSIONS
Default: ``None``
-The numeric mode to apply to directories created in the process of
-uploading files. This value mirrors the functionality and caveats of
-the :setting:`FILE_UPLOAD_PERMISSIONS` setting.
+The numeric mode to apply to directories created in the process of uploading
+files.
+
+This setting also determines the default permissions for collected static
+directories when using the :djadmin:`collectstatic` management command. See
+:djadmin:`collectstatic` for details on overriding it.
+
+This value mirrors the functionality and caveats of the
+:setting:`FILE_UPLOAD_PERMISSIONS` setting.
.. setting:: FILE_UPLOAD_PERMISSIONS
@@ -1157,7 +1163,7 @@ system's standard umask.
This setting also determines the default permissions for collected static files
when using the :djadmin:`collectstatic` management command. See
-:djadmin:`collectstatic` for details on overridding it.
+:djadmin:`collectstatic` for details on overriding it.
.. warning::
@@ -1481,6 +1487,12 @@ to a non-empty value.
Example: ``"http://media.example.com/"``
+.. warning::
+
+ There are security risks if you are accepting uploaded content from
+ untrusted users! See the security guide's topic on
+ :ref:`user-uploaded-content-security` for mitigation details.
+
.. warning::
:setting:`MEDIA_URL` and :setting:`STATIC_URL` must have different
@@ -2085,25 +2097,6 @@ The default value for the X-Frame-Options header used by
:doc:`clickjacking protection ` documentation.
-Admindocs
-=========
-
-Settings for :mod:`django.contrib.admindocs`.
-
-.. setting:: ADMIN_FOR
-
-ADMIN_FOR
----------
-
-Default: ``()`` (Empty tuple)
-
-Used for admin-site settings modules, this should be a tuple of settings
-modules (in the format ``'foo.bar.baz'``) for which this site is an admin.
-
-The admin site uses this in its automatically-introspected documentation of
-models, views and template tags.
-
-
Auth
====
@@ -2456,7 +2449,7 @@ SESSION_EXPIRE_AT_BROWSER_CLOSE
Default: ``False``
-Whether to expire the session when the user closes his or her browser. See
+Whether to expire the session when the user closes their browser. See
:ref:`browser-length-vs-persistent-sessions`.
.. setting:: SESSION_FILE_PATH
diff --git a/docs/ref/templates/api.txt b/docs/ref/templates/api.txt
index b3e49b6773..7627c02b6e 100644
--- a/docs/ref/templates/api.txt
+++ b/docs/ref/templates/api.txt
@@ -306,10 +306,12 @@ If you ``pop()`` too much, it'll raise
>>> c = Context()
>>> c['foo'] = 'first level'
>>> c.push()
+ {}
>>> c['foo'] = 'second level'
>>> c['foo']
'second level'
>>> c.pop()
+ {'foo': 'second level'}
>>> c['foo']
'first level'
>>> c['foo'] = 'overwritten'
@@ -833,9 +835,9 @@ dictionary
positional argument.
context_instance
- An instance of ``Context`` or a subclass (e.g., an instance of
- ``RequestContext``) to use as the template's context. This can
- also be passed as the third positional argument.
+ An instance of :class:`~django.template.Context` or a subclass (e.g., an
+ instance of :class:`~django.template.RequestContext`) to use as the
+ template's context. This can also be passed as the third positional argument.
See also the :func:`~django.shortcuts.render_to_response()` shortcut, which
calls ``render_to_string`` and feeds the result into an :class:`~django.http.HttpResponse`
diff --git a/docs/releases/1.2.txt b/docs/releases/1.2.txt
index ac355a1d87..387f2508ef 100644
--- a/docs/releases/1.2.txt
+++ b/docs/releases/1.2.txt
@@ -50,7 +50,7 @@ be found below`_.
`Django Advent`_ covered the release of Django 1.2 with a series of
articles and tutorials that cover some of the new features in depth.
-.. _django advent: http://djangoadvent.com/
+.. _django advent: https://github.com/djangoadvent/djangoadvent-articles
Wherever possible these features have been introduced in a backwards-compatible
manner per :doc:`our API stability policy ` policy.
diff --git a/docs/releases/1.3-beta-1.txt b/docs/releases/1.3-beta-1.txt
index 8719221ddd..907625a7f4 100644
--- a/docs/releases/1.3-beta-1.txt
+++ b/docs/releases/1.3-beta-1.txt
@@ -73,8 +73,7 @@ The Django admin has long had an undocumented "feature" allowing savvy
users to manipulate the query string of changelist pages to filter the
list of objects displayed. However, this also creates a security
issue, as a staff user with sufficient knowledge of model structure
-could use this "feature" to gain access to information he or she would
-not normally have.
+could use this "feature" to gain access to information not normally accessible.
As a result, changelist filtering now explicitly validates all lookup
arguments in the query string, and permits only fields which are
diff --git a/docs/releases/1.4.6.txt b/docs/releases/1.4.6.txt
index e3ccbbb344..cac640ad97 100644
--- a/docs/releases/1.4.6.txt
+++ b/docs/releases/1.4.6.txt
@@ -19,7 +19,7 @@ The security checks for these redirects (namely
``django.util.http.is_safe_url()``) didn't check if the scheme is ``http(s)``
and as such allowed ``javascript:...`` URLs to be entered. If a developer
relied on ``is_safe_url()`` to provide safe redirect targets and put such a
-URL into a link, he or she could suffer from a XSS attack. This bug doesn't affect
+URL into a link, they could suffer from a XSS attack. This bug doesn't affect
Django currently, since we only put this URL into the ``Location`` response
header and browsers seem to ignore JavaScript there.
diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt
index 1ba2c6ac7f..f8c23728b9 100644
--- a/docs/releases/1.4.txt
+++ b/docs/releases/1.4.txt
@@ -811,7 +811,7 @@ instance:
* Consequences: The user will see an error about the form having expired
and will be sent back to the first page of the wizard, losing the data
- he or she has entered so far.
+ entered so far.
* Time period: The amount of time you expect users to take filling out the
affected forms.
diff --git a/docs/releases/1.5.2.txt b/docs/releases/1.5.2.txt
index 2a2cf9195d..6414aab509 100644
--- a/docs/releases/1.5.2.txt
+++ b/docs/releases/1.5.2.txt
@@ -16,7 +16,7 @@ The security checks for these redirects (namely
``django.util.http.is_safe_url()``) didn't check if the scheme is ``http(s)``
and as such allowed ``javascript:...`` URLs to be entered. If a developer
relied on ``is_safe_url()`` to provide safe redirect targets and put such a
-URL into a link, he or she could suffer from a XSS attack. This bug doesn't affect
+URL into a link, they could suffer from a XSS attack. This bug doesn't affect
Django currently, since we only put this URL into the ``Location`` response
header and browsers seem to ignore JavaScript there.
diff --git a/docs/releases/1.6.1.txt b/docs/releases/1.6.1.txt
index 91c6a9e261..28fb4358e0 100644
--- a/docs/releases/1.6.1.txt
+++ b/docs/releases/1.6.1.txt
@@ -2,11 +2,11 @@
Django 1.6.1 release notes
==========================
-*Under development*
+*December 12, 2013*
-This is Django 1.6.1, a bugfix release for Django 1.6.
-
-...
+This is Django 1.6.1, a bugfix release for Django 1.6. In addition to the bug
+fixes listed below, translations submitted since the 1.6 release are also
+included.
Bug fixes
=========
@@ -21,21 +21,41 @@ Bug fixes
``UnboundLocalError`` if :func:`~django.contrib.auth.get_user_model`
raised an error (#21439).
* Fixed a regression that prevented editable ``GenericRelation`` subclasses
- from working in ``ModelForms``.
+ from working in ``ModelForms`` (#21428).
+* Added missing ``to_python`` method for ``ModelMultipleChoiceField`` which
+ is required in Django 1.6 to properly detect changes from initial values
+ (#21568).
* Fixed ``django.contrib.humanize`` translations where the unicode sequence
for the non-breaking space was returned verbatim (#21415).
* Fixed :djadmin:`loaddata` error when fixture file name contained any dots
- non related to file extensions (#21457).
+ not related to file extensions (#21457) or when fixture path was relative
+ but located in a subdirectory (#21551).
* Fixed display of inline instances in formsets when parent has 0 for primary
key (#21472).
* Fixed a regression where custom querysets for foreign keys were overwritten
if ``ModelAdmin`` had ordering set (#21405).
-* Removed mention of a feature in the ``--locale``/``-l`` option of
+* Removed mention of a feature in the ``--locale``/``-l`` option of the
``makemessages`` and ``compilemessages`` commands that never worked as
promised: Support of multiple locale names separated by commas. It's still
- possible to specify multiplle locales in one run by suing the option
+ possible to specify multiple locales in one run by using the option
multiple times (#21488, #17181).
* Fixed a regression that unnecessarily triggered settings configuration when
importing ``get_wsgi_application`` (#21486).
* Fixed test client ``logout()`` method when using the cookie-based session
backend (#21448).
+* Fixed a crash when a ``GeometryField`` uses a non-geometric widget (#21496).
+* Fixed password hash upgrade when changing the iteration count (#21535).
+* Fixed a bug in the debug view when the URLconf only contains one element
+ (#21530).
+* Re-added missing search result count and reset link in changelist admin view
+ (#21510).
+* The current language is no longer saved to the session by ``LocaleMiddleware``
+ on every response, but rather only after a logout (#21473).
+* Fixed a crash when executing ``runserver`` on non-English systems and when the
+ formatted date in its output contained non-ASCII characters (#21358).
+* Fixed a crash in the debug view after an exception occurred on Python ≥ 3.3
+ (#21443).
+* Fixed a crash in :class:`~django.db.models.ImageField` on some platforms
+ (Homebrew and RHEL6 reported) (#21355).
+* Fixed a regression when using generic relations in ``ModelAdmin.list_filter``
+ (#21431).
diff --git a/docs/releases/1.6.2.txt b/docs/releases/1.6.2.txt
new file mode 100644
index 0000000000..cae18b86fa
--- /dev/null
+++ b/docs/releases/1.6.2.txt
@@ -0,0 +1,12 @@
+==========================
+Django 1.6.2 release notes
+==========================
+
+*Under development*
+
+This is Django 1.6.2, a bugfix release for Django 1.6.
+
+Bug fixes
+=========
+
+...
diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt
index f8cdcf1f3b..3a1568fe45 100644
--- a/docs/releases/1.7.txt
+++ b/docs/releases/1.7.txt
@@ -256,10 +256,11 @@ Minor features
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* The :ref:`static files storage classes ` may be
- subclassed to override the permissions that collected static files receive by
- setting the
+ subclassed to override the permissions that collected static files and
+ directories receive by setting the
:attr:`~django.core.files.storage.FileSystemStorage.file_permissions_mode`
- parameter. See :djadmin:`collectstatic` for example usage.
+ and :attr:`~django.core.files.storage.FileSystemStorage.directory_permissions_mode`
+ parameters. See :djadmin:`collectstatic` for example usage.
:mod:`django.contrib.syndication`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -279,7 +280,7 @@ Cache
* If you instanciate cache backends directly, be aware that they aren't
thread-safe any more, as :data:`django.core.cache.caches` now yields
- differend instances per thread.
+ different instances per thread.
Email
^^^^^
@@ -349,6 +350,15 @@ Forms
* It's now possible to opt-out from a ``Form`` field declared in a parent class
by shadowing it with a non-``Field`` value.
+* The new :meth:`~django.forms.Form.add_error()` method allows adding errors
+ to specific form fields.
+
+* The dict-like attribute :attr:`~django.forms.Form.errors` now has two new
+ methods :meth:`~django.forms.Form.errors.as_data()` and
+ :meth:`~django.forms.Form.errors.as_json()`. The former returns a ``dict``
+ that maps fields to their original errors, complete with all metadata
+ (error code and params), the latter returns the errors serialized as json.
+
Internationalization
^^^^^^^^^^^^^^^^^^^^
@@ -371,6 +381,11 @@ Internationalization
in the corresponding entry in the PO file, which makes the translation
process easier.
+* When you run :djadmin:`makemessages` from the root directory of your project,
+ any extracted strings will now be automatically distributed to the proper
+ app or project message file. See :ref:`how-to-create-language-files` for
+ details.
+
Management Commands
^^^^^^^^^^^^^^^^^^^
@@ -389,13 +404,10 @@ Management Commands
* The :djadmin:`runserver` command received several improvements:
- # On BSD systems, including OS X, the development server will reload
- immediately when a file is changed. Previously, it polled the filesystem
- for changes every second. That caused a small delay before reloads and
- reduced battery life on laptops.
-
- * On Linux, the same improvements are available when pyinotify_ is
- installed.
+ * On Linux systems, if pyinotify_ is installed, the development server will
+ reload immediately when a file is changed. Previously, it polled the
+ filesystem for changes every second. That caused a small delay before
+ reloads and reduced battery life on laptops.
.. _pyinotify: https://pypi.python.org/pypi/pyinotify
@@ -406,6 +418,9 @@ Management Commands
* All HTTP requests are logged to the console, including requests for static
files or ``favicon.ico`` that used to be filtered out.
+* Management commands can now produce syntax colored output under Windows if
+ the ANSICON third-party tool is installed and active.
+
Models
^^^^^^
@@ -435,6 +450,9 @@ Models
argument to control whether or not to perform operations in bulk
(i.e. using ``QuerySet.update()``). Defaults to ``True``.
+* It is now possible to use ``None`` as a query value for the :lookup:`iexact`
+ lookup.
+
Signals
^^^^^^^
@@ -522,6 +540,9 @@ Tests
in :class:`~django.test.Client` instantiation and are processed through
middleware.
+* :meth:`~django.test.TransactionTestCase.assertNumQueries` now prints
+ out the list of executed queries if the assertion fails.
+
Backwards incompatible changes in 1.7
=====================================
@@ -842,3 +863,26 @@ The function ``memoize`` is deprecated and should be replaced by the
Django ships a backport of this decorator for older Python versions and it's
available at ``django.utils.lru_cache.lru_cache``. The deprecated function will
be removed in Django 1.9.
+
+Geo Sitemaps
+~~~~~~~~~~~~
+
+Google has retired support for the Geo Sitemaps format. Hence Django support
+for Geo Sitemaps is deprecated and will be removed in Django 1.8.
+
+Passing callable arguments to queryset methods
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Callable arguments for querysets were an undocumented feature that was
+unreliable. It's been deprecated and will be removed in Django 1.9.
+
+Callable arguments were evaluated when a queryset was constructed rather than
+when it was evaluated, thus this feature didn't offer any benefit compared to
+evaluating arguments before passing them to queryset and created confusion that
+the arguments may have been evaluated at query time.
+
+``ADMIN_FOR`` setting
+~~~~~~~~~~~~~~~~~~~~~
+
+The ``ADMIN_FOR`` feature, part of the admindocs, has been removed. You can
+remove the setting from your configuration at your convenience.
diff --git a/docs/releases/index.txt b/docs/releases/index.txt
index 0c80cfb378..81c418f4b6 100644
--- a/docs/releases/index.txt
+++ b/docs/releases/index.txt
@@ -29,8 +29,9 @@ Final releases
.. toctree::
:maxdepth: 1
- 1.6
+ 1.6.2
1.6.1
+ 1.6
1.5 release
-----------
diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt
index e08ea5f960..7ef7285fb7 100644
--- a/docs/topics/cache.txt
+++ b/docs/topics/cache.txt
@@ -1123,10 +1123,10 @@ Controlling cache: Using other headers
Other problems with caching are the privacy of data and the question of where
data should be stored in a cascade of caches.
-A user usually faces two kinds of caches: his or her own browser cache (a
-private cache) and his or her provider's cache (a public cache). A public cache
-is used by multiple users and controlled by someone else. This poses problems
-with sensitive data--you don't want, say, your bank account number stored in a
+A user usually faces two kinds of caches: their own browser cache (a private
+cache) and their provider's cache (a public cache). A public cache is used by
+multiple users and controlled by someone else. This poses problems with
+sensitive data--you don't want, say, your bank account number stored in a
public cache. So Web applications need a way to tell caches which data is
private and which is public.
diff --git a/docs/topics/db/aggregation.txt b/docs/topics/db/aggregation.txt
index 1024d6b0c2..9e02b50c67 100644
--- a/docs/topics/db/aggregation.txt
+++ b/docs/topics/db/aggregation.txt
@@ -241,7 +241,7 @@ such alias were specified, it would be the rather long ``'book__pubdate__min'``.
This doesn't apply just to foreign keys. It also works with many-to-many
relations. For example, we can ask for every author, annotated with the total
-number of pages considering all the books he/she has (co-)authored (note how we
+number of pages considering all the books the author has (co-)authored (note how we
use ``'book'`` to specify the ``Author`` -> ``Book`` reverse many-to-many hop)::
>>> Author.objects.annotate(total_pages=Sum('book__pages'))
diff --git a/docs/topics/db/managers.txt b/docs/topics/db/managers.txt
index c97149cfd1..3377bccab6 100644
--- a/docs/topics/db/managers.txt
+++ b/docs/topics/db/managers.txt
@@ -219,7 +219,7 @@ custom ``QuerySet`` if you also implement them on the ``Manager``::
class PersonManager(models.Manager):
def get_queryset(self):
- return PersonQuerySet()
+ return PersonQuerySet(self.model, using=self._db)
def male(self):
return self.get_queryset().male()
diff --git a/docs/topics/email.txt b/docs/topics/email.txt
index d60a497fdf..058a007721 100644
--- a/docs/topics/email.txt
+++ b/docs/topics/email.txt
@@ -600,8 +600,8 @@ manually open the connection, you can control when it is closed. For example::
connection.close()
-Testing email sending
-=====================
+Configuring email for development
+=================================
There are times when you do not want Django to send emails at
all. For example, while developing a Web site, you probably don't want
@@ -609,13 +609,13 @@ to send out thousands of emails -- but you may want to validate that
emails will be sent to the right people under the right conditions,
and that those emails will contain the correct content.
-The easiest way to test your project's use of email is to use the ``console``
-email backend. This backend redirects all email to stdout, allowing you to
-inspect the content of mail.
+The easiest way to configure email for local development is to use the
+:ref:`console ` email backend. This backend
+redirects all email to stdout, allowing you to inspect the content of mail.
-The ``file`` email backend can also be useful during development -- this backend
-dumps the contents of every SMTP connection to a file that can be inspected
-at your leisure.
+The :ref:`file ` email backend can also be useful
+during development -- this backend dumps the contents of every SMTP connection
+to a file that can be inspected at your leisure.
Another approach is to use a "dumb" SMTP server that receives the emails
locally and displays them to the terminal, but does not actually send
@@ -626,7 +626,9 @@ anything. Python has a built-in way to accomplish this with a single command::
This command will start a simple SMTP server listening on port 1025 of
localhost. This server simply prints to standard output all email headers and
the email body. You then only need to set the :setting:`EMAIL_HOST` and
-:setting:`EMAIL_PORT` accordingly, and you are set.
+:setting:`EMAIL_PORT` accordingly. For a more detailed discussion of SMTP
+server options, see the Python documentation for the :mod:`smtpd` module.
-For a more detailed discussion of testing and processing of emails locally,
-see the Python documentation for the :mod:`smtpd` module.
+For information about unit-testing the sending of emails in your
+application, see the :ref:`topics-testing-email` section of :doc:`Testing
+Django applications `.
diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt
index d8d1c106f8..e98fcb0344 100644
--- a/docs/topics/forms/modelforms.txt
+++ b/docs/topics/forms/modelforms.txt
@@ -528,7 +528,7 @@ field, you could do the following::
class Meta:
model = Article
- fields = ['pub_date', 'headline', 'content', 'reporter']
+ fields = ['pub_date', 'headline', 'content', 'reporter', 'slug']
If you want to specify a field's validators, you can do so by defining
@@ -760,6 +760,30 @@ instances of the model, you can specify an empty QuerySet::
>>> AuthorFormSet(queryset=Author.objects.none())
+Changing the ``form``
+---------------------
+
+By default, when you use ``modelformset_factory``, a model form will
+be created using :func:`~django.forms.models.modelform_factory`.
+Often, it can be useful to specify a custom model form. For example,
+you can create a custom model form that has custom validation::
+
+ class AuthorForm(forms.ModelForm):
+ class Meta:
+ model = Author
+ fields = ('name', 'title')
+
+ def clean_name(self):
+ # custom validation for the name field
+ ...
+
+Then, pass your model form to the factory function::
+
+ AuthorFormSet = modelformset_factory(Author, form=AuthorForm)
+
+It is not always necessary to define a custom model form. The
+``modelformset_factory`` function has several arguments which are
+passed through to ``modelform_factory``, which are described below.
.. _controlling-fields-with-fields-and-exclude:
diff --git a/docs/topics/http/file-uploads.txt b/docs/topics/http/file-uploads.txt
index d88524ee20..94415d03c3 100644
--- a/docs/topics/http/file-uploads.txt
+++ b/docs/topics/http/file-uploads.txt
@@ -10,6 +10,12 @@ When Django handles a file upload, the file data ends up placed in
`). This document explains how files are stored on disk
and in memory, and how to customize the default behavior.
+.. warning::
+
+ There are security risks if you are accepting uploaded content from
+ untrusted users! See the security guide's topic on
+ :ref:`user-uploaded-content-security` for mitigation details.
+
Basic file uploads
==================
diff --git a/docs/topics/http/sessions.txt b/docs/topics/http/sessions.txt
index d39b8344c6..f7e8807945 100644
--- a/docs/topics/http/sessions.txt
+++ b/docs/topics/http/sessions.txt
@@ -166,7 +166,7 @@ and the :setting:`SECRET_KEY` setting.
cookie backend might open you up to `replay attacks`_. Unlike other session
backends which keep a server-side record of each session and invalidate it
when a user logs out, cookie-based sessions are not invalidated when a user
- logs out. Thus if an attacker steals a user's cookie, he or she can use that
+ logs out. Thus if an attacker steals a user's cookie, they can use that
cookie to login as that user even if the user logs out. Cookies will only
be detected as 'stale' if they are older than your
:setting:`SESSION_COOKIE_AGE`.
@@ -590,8 +590,8 @@ log in every time they open a browser.
If :setting:`SESSION_EXPIRE_AT_BROWSER_CLOSE` is set to ``True``, Django will
use browser-length cookies -- cookies that expire as soon as the user closes
-his or her browser. Use this if you want people to have to log in every time
-they open a browser.
+their browser. Use this if you want people to have to log in every time they
+open a browser.
This setting is a global default and can be overwritten at a per-session level
by explicitly calling the :meth:`~backends.base.SessionBase.set_expiry` method
diff --git a/docs/topics/http/shortcuts.txt b/docs/topics/http/shortcuts.txt
index ebe7729428..c4f413e28c 100644
--- a/docs/topics/http/shortcuts.txt
+++ b/docs/topics/http/shortcuts.txt
@@ -24,6 +24,11 @@ introduce controlled coupling for convenience's sake.
:func:`render_to_response()` with a ``context_instance`` argument that
forces the use of a :class:`~django.template.RequestContext`.
+ Django does not provide a shortcut function which returns a
+ :class:`~django.template.response.TemplateResponse` because the constructor
+ of :class:`~django.template.response.TemplateResponse` offers the same level
+ of convenience as :func:`render()`.
+
Required arguments
------------------
diff --git a/docs/topics/i18n/formatting.txt b/docs/topics/i18n/formatting.txt
index fc3f37de32..e306d4ab44 100644
--- a/docs/topics/i18n/formatting.txt
+++ b/docs/topics/i18n/formatting.txt
@@ -7,8 +7,9 @@ Format localization
Overview
========
-Django's formatting system is capable to display dates, times and numbers in templates using the format specified for the current :term:`locale `. It also handles localized input in forms.
+Django's formatting system is capable of displaying dates, times and numbers in
+templates using the format specified for the current
+:term:`locale `. It also handles localized input in forms.
When it's enabled, two users accessing the same content may see dates, times and
numbers formatted in different ways, depending on the formats for their current
diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt
index 8a484702e7..cc4c24d364 100644
--- a/docs/topics/i18n/translation.txt
+++ b/docs/topics/i18n/translation.txt
@@ -557,7 +557,7 @@ string in the message catalogs.
It's not possible to mix a template variable inside a string within ``{% trans
%}``. If your translations require strings with variables (placeholders), use
-``{% blocktrans %}`` instead.
+:ttag:`{% blocktrans %}` instead.
If you'd like to retrieve a translated string without displaying it, you can
@@ -1256,6 +1256,17 @@ is configured correctly). It creates (or updates) a message file in the
directory ``locale/LANG/LC_MESSAGES``. In the ``de`` example, the file will be
``locale/de/LC_MESSAGES/django.po``.
+.. versionchanged:: 1.7
+
+ When you run ``makemessages`` from the root directory of your project, the
+ extracted strings will be automatically distributed to the proper message
+ files. That is, a string extracted from a file of an app containing a
+ ``locale`` directory will go in a message file under that directory.
+ A string extracted from a file of an app without any ``locale`` directory
+ will either go in a message file under the directory listed first in
+ :setting:`LOCALE_PATHS` or will generate an error if :setting:`LOCALE_PATHS`
+ is empty.
+
By default :djadmin:`django-admin.py makemessages ` examines every
file that has the ``.html`` or ``.txt`` file extension. In case you want to
override that default, use the ``--extension`` or ``-e`` option to specify the
@@ -1568,8 +1579,8 @@ If all you want is to run Django with your native language all you need to do
is set :setting:`LANGUAGE_CODE` and make sure the corresponding :term:`message
files ` and their compiled versions (``.mo``) exist.
-If you want to let each individual user specify which language he or she
-prefers, then you also need to use use the ``LocaleMiddleware``.
+If you want to let each individual user specify which language they
+prefer, then you also need to use use the ``LocaleMiddleware``.
``LocaleMiddleware`` enables language selection based on data from the request.
It customizes content for each user.
@@ -1730,24 +1741,9 @@ All message file repositories are structured the same way. They are:
* ``$PYTHONPATH/django/conf/locale//LC_MESSAGES/django.(po|mo)``
To create message files, you use the :djadmin:`django-admin.py makemessages `
-tool. You only need to be in the same directory where the ``locale/`` directory
-is located. And you use :djadmin:`django-admin.py compilemessages `
+tool. And you use :djadmin:`django-admin.py compilemessages `
to produce the binary ``.mo`` files that are used by ``gettext``.
You can also run :djadmin:`django-admin.py compilemessages
--settings=path.to.settings ` to make the compiler process all
the directories in your :setting:`LOCALE_PATHS` setting.
-
-Finally, you should give some thought to the structure of your translation
-files. If your applications need to be delivered to other users and will be used
-in other projects, you might want to use app-specific translations. But using
-app-specific translations and project-specific translations could produce weird
-problems with :djadmin:`makemessages`: it will traverse all directories below
-the current path and so might put message IDs into a unified, common message
-file for the current project that are already in application message files.
-
-The easiest way out is to store applications that are not part of the project
-(and so carry their own translations) outside the project tree. That way,
-:djadmin:`django-admin.py makemessages `, when ran on a project
-level will only extract strings that are connected to your explicit project and
-not strings that are distributed independently.
diff --git a/docs/topics/install.txt b/docs/topics/install.txt
index 923ae44270..d14f40a7bb 100644
--- a/docs/topics/install.txt
+++ b/docs/topics/install.txt
@@ -86,19 +86,9 @@ from other databases, so if you are working on something substantial, it's
recommended to develop with the same database as you plan on using in
production.
-In addition to the officially supported databases, there are backends provided
-by 3rd parties that allow you to use other databases with Django:
-
-* `Sybase SQL Anywhere`_
-* `IBM DB2`_
-* `Microsoft SQL Server 2005`_
-* Firebird_
-* ODBC_
-
-The Django versions and ORM features supported by these unofficial backends
-vary considerably. Queries regarding the specific capabilities of these
-unofficial backends, along with any support queries, should be directed to the
-support channels provided by each 3rd party project.
+In addition to the officially supported databases, there are :ref:`backends
+provided by 3rd parties ` that allow you to use other
+databases with Django.
In addition to a database backend, you'll need to make sure your Python
database bindings are installed.
@@ -221,7 +211,7 @@ This is the recommended way to install Django.
.. _pip: http://www.pip-installer.org/
.. _virtualenv: http://www.virtualenv.org/
-.. _virtualenvwrapper: http://www.doughellmann.com/docs/virtualenvwrapper/
+.. _virtualenvwrapper: http://virtualenvwrapper.readthedocs.org/en/latest/
.. _standalone pip installer: http://www.pip-installer.org/en/latest/installing.html#using-the-installer
Installing an official release manually
diff --git a/docs/topics/logging.txt b/docs/topics/logging.txt
index f4c1043eb4..15c1aa9dd8 100644
--- a/docs/topics/logging.txt
+++ b/docs/topics/logging.txt
@@ -516,7 +516,7 @@ Python logging module.
By default, an instance of the email backend specified in
:setting:`EMAIL_BACKEND` will be used.
-.. _Sentry: http://pypi.python.org/pypi/sentry
+.. _Sentry: https://pypi.python.org/pypi/sentry
Filters
diff --git a/docs/topics/migrations.txt b/docs/topics/migrations.txt
index 048daaab37..ead5ecffdb 100644
--- a/docs/topics/migrations.txt
+++ b/docs/topics/migrations.txt
@@ -70,7 +70,7 @@ PostgreSQL
PostgreSQL is the most capable of all the databases here in terms of schema
support; the only caveat is that adding columns with default values will
-lock a table for a time proportional to the number of rows in it.
+cause a full rewrite of the table, for a time proportional to its size.
For this reason, it's recommended you always create new columns with
``null=True``, as this way they will be added immediately.
@@ -83,11 +83,11 @@ meaning that if a migration fails to apply you will have to manually unpick
the changes in order to try again (it's impossible to roll back to an
earlier point).
-In addition, MySQL will lock tables for almost every schema operation and
-generally takes a time proportional to the number of rows in the table to
-add or remove columns. On slower hardware this can be worse than a minute
-per million rows - adding a few columns to a table with just a few million
-rows could lock your site up for over ten minutes.
+In addition, MySQL will fully rewrite tables for almost every schema operation
+and generally takes a time proportional to the number of rows in the table to
+add or remove columns. On slower hardware this can be worse than a minute per
+million rows - adding a few columns to a table with just a few million rows
+could lock your site up for over ten minutes.
Finally, MySQL has reasonably small limits on name lengths for columns, tables
and indexes, as well as a limit on the combined size of all columns an index
diff --git a/docs/topics/performance.txt b/docs/topics/performance.txt
index 1c1ed86762..1e3bf7b9d4 100644
--- a/docs/topics/performance.txt
+++ b/docs/topics/performance.txt
@@ -417,7 +417,7 @@ With these caveats in mind, you should be aware of:
performance gains, typically for heavyweight applications.
A key aim of the PyPy project is `compatibility
-`_ with existing Python APIs and libraries.
+`_ with existing Python APIs and libraries.
Django is compatible, but you will need to check the compatibility of other
libraries you rely on.
diff --git a/docs/topics/python3.txt b/docs/topics/python3.txt
index d059bc042d..15c07ccbf0 100644
--- a/docs/topics/python3.txt
+++ b/docs/topics/python3.txt
@@ -6,7 +6,7 @@ Django 1.5 is the first version of Django to support Python 3. The same code
runs both on Python 2 (≥ 2.6.5) and Python 3 (≥ 3.2), thanks to the six_
compatibility layer.
-.. _six: http://packages.python.org/six/
+.. _six: http://pythonhosted.org/six/
This document is primarily targeted at authors of pluggable application
who want to support both Python 2 and 3. It also describes guidelines that
@@ -42,7 +42,7 @@ developers are used to dealing with such constraints.
Porting tools provided by Django are inspired by this philosophy, and it's
reflected throughout this guide.
-.. _Python's official porting guide: http://docs.python.org/py3k/howto/pyporting.html
+.. _Python's official porting guide: http://docs.python.org/3/howto/pyporting.html
.. _Pragmatic Unicode: http://nedbatchelder.com/text/unipain.html
Porting tips
@@ -246,7 +246,7 @@ consequence, the following pattern is sometimes necessary::
Be cautious if you have to `index bytestrings`_.
-.. _index bytestrings: http://docs.python.org/py3k/howto/pyporting.html#bytes-literals
+.. _index bytestrings: http://docs.python.org/3/howto/pyporting.html#bytes-literals
Exceptions
~~~~~~~~~~
diff --git a/docs/topics/security.txt b/docs/topics/security.txt
index 5200edb95f..1ae5ddf78e 100644
--- a/docs/topics/security.txt
+++ b/docs/topics/security.txt
@@ -203,6 +203,52 @@ be deployed such that untrusted users don't have access to any subdomains,
:mod:`django.contrib.sessions` also has limitations. See :ref:`the session
topic guide section on security ` for details.
+.. _user-uploaded-content-security:
+
+User-uploaded content
+=====================
+
+.. note::
+ Consider :ref:`serving static files from a cloud service or CDN
+ ` to avoid some of these issues.
+
+* If your site accepts file uploads, it is strongly advised that you limit
+ these uploads in your Web server configuration to a reasonable
+ size in order to prevent denial of service (DOS) attacks. In Apache, this
+ can be easily set using the LimitRequestBody_ directive.
+
+* If you are serving your own static files, be sure that handlers like Apache's
+ ``mod_php``, which would execute static files as code, are disabled. You don't
+ want users to be able to execute arbitrary code by uploading and requesting a
+ specially crafted file.
+
+* Django's media upload handling poses some vulnerabilities when that media is
+ served in ways that do not follow security best practices. Specifically, an
+ HTML file can be uploaded as an image if that file contains a valid PNG
+ header followed by malicious HTML. This file will pass verification of the
+ libraries that Django uses for :class:`~django.db.models.ImageField` image
+ processing (PIL or Pillow). When this file is subsequently displayed to a
+ user, it may be displayed as HTML depending on the type and configuration of
+ your web server.
+
+ No bulletproof technical solution exists at the framework level to safely
+ validate all user uploaded file content, however, there are some other steps
+ you can take to mitigate these attacks:
+
+ 1. One class of attacks can be prevented by always serving user uploaded
+ content from a distinct Top Level Domain (TLD). This prevents any
+ exploit blocked by `same-origin policy`_ protections such as cross site
+ scripting. For example, if your site runs on ``example.com``, you would
+ want to serve uploaded content (the :setting:`MEDIA_URL` setting) from
+ something like ``usercontent-example.com``. It's *not* sufficient to
+ serve content from a subdomain like ``usercontent.example.com``.
+
+ 2. Beyond this, applications may choose to define a whitelist of allowable
+ file extensions for user uploaded files and configure the web server
+ to only serve such files.
+
+.. _same-origin policy: http://en.wikipedia.org/wiki/Same-origin_policy
+
.. _additional-security-topics:
Additional security topics
@@ -219,10 +265,6 @@ security protection of the Web server, operating system and other components.
* Django does not throttle requests to authenticate users. To protect against
brute-force attacks against the authentication system, you may consider
deploying a Django plugin or Web server module to throttle these requests.
-* If your site accepts file uploads, it is strongly advised that you limit
- these uploads in your Web server configuration to a reasonable
- size in order to prevent denial of service (DOS) attacks. In Apache, this
- can be easily set using the LimitRequestBody_ directive.
* Keep your :setting:`SECRET_KEY` a secret.
* It is a good idea to limit the accessibility of your caching system and
database using a firewall.
diff --git a/docs/topics/templates.txt b/docs/topics/templates.txt
index b1389b85f6..53ee292e37 100644
--- a/docs/topics/templates.txt
+++ b/docs/topics/templates.txt
@@ -30,7 +30,7 @@ or CheetahTemplate_, you should feel right at home with Django's templates.
` to the template language as needed).
.. _`The Django template language: For Python programmers`: ../templates_python/
-.. _Smarty: http://smarty.php.net/
+.. _Smarty: http://www.smarty.net/
.. _CheetahTemplate: http://www.cheetahtemplate.org/
Templates
diff --git a/docs/topics/testing/advanced.txt b/docs/topics/testing/advanced.txt
index 90111d05ff..cbf574a29c 100644
--- a/docs/topics/testing/advanced.txt
+++ b/docs/topics/testing/advanced.txt
@@ -377,21 +377,9 @@ Methods
Run the test suite.
- ``test_labels`` is a list of strings describing the tests to be run. A test
- label can take one of four forms:
-
- * ``path.to.test_module.TestCase.test_method`` -- Run a single test method
- in a test case.
- * ``path.to.test_module.TestCase`` -- Run all the test methods in a test
- case.
- * ``path.to.module`` -- Search for and run all tests in the named Python
- package or module.
- * ``path/to/directory`` -- Search for and run all tests below the named
- directory.
-
- If ``test_labels`` has a value of ``None``, the test runner will search for
- tests in all files below the current directory whose names match its
- ``pattern`` (see above).
+ ``test_labels`` allows you to specify which tests to run and supports
+ several formats (see :meth:`DiscoverRunner.build_suite` for a list of
+ supported formats).
``extra_tests`` is a list of extra ``TestCase`` instances to add to the
suite that is executed by the test runner. These extra tests are run
@@ -410,15 +398,20 @@ Methods
Constructs a test suite that matches the test labels provided.
``test_labels`` is a list of strings describing the tests to be run. A test
- label can take one of three forms:
+ label can take one of four forms:
- * ``app.TestCase.test_method`` -- Run a single test method in a test
+ * ``path.to.test_module.TestCase.test_method`` -- Run a single test method
+ in a test case.
+ * ``path.to.test_module.TestCase`` -- Run all the test methods in a test
case.
- * ``app.TestCase`` -- Run all the test methods in a test case.
- * ``app`` -- Search for and run all tests in the named application.
+ * ``path.to.module`` -- Search for and run all tests in the named Python
+ package or module.
+ * ``path/to/directory`` -- Search for and run all tests below the named
+ directory.
- If ``test_labels`` has a value of ``None``, the test runner should run
- search for tests in all the applications in :setting:`INSTALLED_APPS`.
+ If ``test_labels`` has a value of ``None``, the test runner will search for
+ tests in all files below the current directory whose names match its
+ ``pattern`` (see above).
``extra_tests`` is a list of extra ``TestCase`` instances to add to the
suite that is executed by the test runner. These extra tests are run
@@ -549,4 +542,4 @@ For more options like annotated HTML listings detailing missed lines, see the
`coverage.py`_ docs.
.. _coverage.py: http://nedbatchelder.com/code/coverage/
-.. _install coverage.py: http://pypi.python.org/pypi/coverage
+.. _install coverage.py: https://pypi.python.org/pypi/coverage
diff --git a/docs/topics/testing/overview.txt b/docs/topics/testing/overview.txt
index a0bed1b2ee..f8464638c2 100644
--- a/docs/topics/testing/overview.txt
+++ b/docs/topics/testing/overview.txt
@@ -31,7 +31,7 @@ module defines tests using a class-based approach.
Since Django no longer supports Python versions older than 2.7,
``django.utils.unittest`` is deprecated. Simply use ``unittest``.
-.. _unittest2: http://pypi.python.org/pypi/unittest2
+.. _unittest2: https://pypi.python.org/pypi/unittest2
Here is an example which subclasses from :class:`django.test.TestCase`,
which is a subclass of :class:`unittest.TestCase` that runs each test inside a
@@ -1053,7 +1053,7 @@ example above is just a tiny fraction of what the Selenium client can do; check
out the `full reference`_ for more details.
.. _Selenium: http://seleniumhq.org/
-.. _selenium package: http://pypi.python.org/pypi/selenium
+.. _selenium package: https://pypi.python.org/pypi/selenium
.. _full reference: http://selenium-python.readthedocs.org/en/latest/api.html
.. _Firefox: http://www.mozilla.com/firefox/
diff --git a/extras/csrf_migration_helper.py b/extras/csrf_migration_helper.py
index 38dee93735..9e7b04b85b 100755
--- a/extras/csrf_migration_helper.py
+++ b/extras/csrf_migration_helper.py
@@ -145,11 +145,11 @@ def get_template_dirs():
from django.conf import settings
dirs = set()
if ('django.template.loaders.filesystem.load_template_source' in settings.TEMPLATE_LOADERS
- or 'django.template.loaders.filesystem.Loader' in settings.TEMPLATE_LOADERS):
+ or 'django.template.loaders.filesystem.Loader' in settings.TEMPLATE_LOADERS):
dirs.update(map(unicode, settings.TEMPLATE_DIRS))
if ('django.template.loaders.app_directories.load_template_source' in settings.TEMPLATE_LOADERS
- or 'django.template.loaders.app_directories.Loader' in settings.TEMPLATE_LOADERS):
+ or 'django.template.loaders.app_directories.Loader' in settings.TEMPLATE_LOADERS):
from django.template.loaders.app_directories import app_template_dirs
dirs.update(app_template_dirs)
return dirs
diff --git a/scripts/manage_translations.py b/scripts/manage_translations.py
index f256f7a1e7..83d5b87c87 100644
--- a/scripts/manage_translations.py
+++ b/scripts/manage_translations.py
@@ -28,13 +28,16 @@ from django.core.management import call_command
HAVE_JS = ['admin']
-def _get_locale_dirs(include_core=True):
+def _get_locale_dirs(resources, include_core=True):
"""
Return a tuple (contrib name, absolute path) for all locale directories,
optionally including the django core catalog.
+ If resources list is not None, filter directories matching resources content.
"""
contrib_dir = os.path.join(os.getcwd(), 'django', 'contrib')
dirs = []
+
+ # Collect all locale directories
for contrib_name in os.listdir(contrib_dir):
path = os.path.join(contrib_dir, contrib_name, 'locale')
if os.path.isdir(path):
@@ -43,6 +46,15 @@ def _get_locale_dirs(include_core=True):
dirs.append(("%s-js" % contrib_name, path))
if include_core:
dirs.insert(0, ('core', os.path.join(os.getcwd(), 'django', 'conf', 'locale')))
+
+ # Filter by resources, if any
+ if resources is not None:
+ res_names = [d[0] for d in dirs]
+ dirs = [ld for ld in dirs if ld[0] in resources]
+ if len(resources) > len(dirs):
+ print("You have specified some unknown resources. "
+ "Available resource names are: %s" % (', '.join(res_names),))
+ exit(1)
return dirs
@@ -72,7 +84,7 @@ def update_catalogs(resources=None, languages=None):
Update the en/LC_MESSAGES/django.po (main and contrib) files with
new/updated translatable strings.
"""
- contrib_dirs = _get_locale_dirs(include_core=False)
+ contrib_dirs = _get_locale_dirs(resources, include_core=False)
os.chdir(os.path.join(os.getcwd(), 'django'))
print("Updating main en catalog")
@@ -81,8 +93,6 @@ def update_catalogs(resources=None, languages=None):
# Contrib catalogs
for name, dir_ in contrib_dirs:
- if resources and not name in resources:
- continue
os.chdir(os.path.join(dir_, '..'))
print("Updating en catalog in %s" % dir_)
if name.endswith('-js'):
@@ -99,11 +109,9 @@ def lang_stats(resources=None, languages=None):
If resources is provided, it should be a list of translation resource to
limit the output (e.g. ['core', 'gis']).
"""
- locale_dirs = _get_locale_dirs()
+ locale_dirs = _get_locale_dirs(resources)
for name, dir_ in locale_dirs:
- if resources and not name in resources:
- continue
print("\nShowing translations stats for '%s':" % name)
langs = sorted([d for d in os.listdir(dir_) if not d.startswith('_')])
for lang in langs:
@@ -126,17 +134,14 @@ def fetch(resources=None, languages=None):
"""
Fetch translations from Transifex, wrap long lines, generate mo files.
"""
- locale_dirs = _get_locale_dirs()
+ locale_dirs = _get_locale_dirs(resources)
errors = []
for name, dir_ in locale_dirs:
- if resources and not name in resources:
- continue
-
# Transifex pull
if languages is None:
- call('tx pull -r %(res)s -a -f' % {'res': _tx_resource_for_name(name)}, shell=True)
- languages = sorted([d for d in os.listdir(dir_) if not d.startswith('_')])
+ call('tx pull -r %(res)s -a -f --minimum-perc=5' % {'res': _tx_resource_for_name(name)}, shell=True)
+ languages = sorted([d for d in os.listdir(dir_) if not d.startswith('_') and d != 'en'])
else:
for lang in languages:
call('tx pull -r %(res)s -f -l %(lang)s' % {
@@ -146,6 +151,10 @@ def fetch(resources=None, languages=None):
for lang in languages:
po_path = '%(path)s/%(lang)s/LC_MESSAGES/django%(ext)s.po' % {
'path': dir_, 'lang': lang, 'ext': 'js' if name.endswith('-js') else ''}
+ if not os.path.exists(po_path):
+ print("No %(lang)s translation for resource %(name)s" % {
+ 'lang': lang, 'name': name})
+ continue
call('msgcat -o %s %s' % (po_path, po_path), shell=True)
res = call('msgfmt -c -o %s.mo %s' % (po_path[:-3], po_path), shell=True)
if res != 0:
diff --git a/setup.cfg b/setup.cfg
index d45eff4c36..ac1e029e90 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -3,8 +3,8 @@ doc_files = docs extras AUTHORS INSTALL LICENSE README.rst
install-script = scripts/rpm-install.sh
[flake8]
-exclude=./django/utils/dictconfig.py,./django/contrib/comments/*,./django/utils/unittest.py,./django/utils/lru_cache.py,./tests/comment_tests/*,./django/test/_doctest.py,./django/utils/six.py,./django/conf/app_template/*
-ignore=E124,E125,E127,E128,E501,W601
+exclude=.git,./django/utils/dictconfig.py,./django/contrib/comments/*,./django/utils/unittest.py,./django/utils/lru_cache.py,./tests/comment_tests/*,./django/test/_doctest.py,./django/utils/six.py,./django/conf/app_template/*
+ignore=E128,E501,W601
[metadata]
license-file = LICENSE
diff --git a/tests/admin_docs/tests.py b/tests/admin_docs/tests.py
index 0d4bcbd998..047bf920a2 100644
--- a/tests/admin_docs/tests.py
+++ b/tests/admin_docs/tests.py
@@ -1,5 +1,7 @@
import unittest
+from django.conf import settings
+from django.contrib.sites.models import Site
from django.contrib.admindocs import utils
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
@@ -7,6 +9,33 @@ from django.test import TestCase
from django.test.utils import override_settings
+class MiscTests(TestCase):
+ urls = 'admin_docs.urls'
+
+ def setUp(self):
+ self._old_installed = Site._meta.app_config.installed
+ User.objects.create_superuser('super', None, 'secret')
+ self.client.login(username='super', password='secret')
+
+ def tearDown(self):
+ Site._meta.app_config.installed = self._old_installed
+
+ @override_settings(
+ SITE_ID=None,
+ INSTALLED_APPS=[app for app in settings.INSTALLED_APPS
+ if app != 'django.contrib.sites'],
+ )
+ def test_no_sites_framework(self):
+ """
+ Without the sites framework, should not access SITE_ID or Site
+ objects. Deleting settings is fine here as UserSettingsHolder is used.
+ """
+ Site._meta.app_config.installed = False
+ Site.objects.all().delete()
+ del settings.SITE_ID
+ self.client.get('/admindocs/views/') # should not raise
+
+
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
@unittest.skipUnless(utils.docutils_is_available, "no docutils installed.")
class AdminDocViewTests(TestCase):
@@ -46,6 +75,8 @@ class AdminDocViewTests(TestCase):
self.assertContains(response,
'',
html=True)
+ self.assertContains(response, 'Views by namespace test')
+ self.assertContains(response, 'Name: test:func
.')
def test_view_detail(self):
response = self.client.get(
diff --git a/tests/admin_docs/urls.py b/tests/admin_docs/urls.py
index 2dcd0315be..48e7898d09 100644
--- a/tests/admin_docs/urls.py
+++ b/tests/admin_docs/urls.py
@@ -1,11 +1,16 @@
-from django.conf.urls import include, patterns
+from django.conf.urls import include, patterns, url
from django.contrib import admin
from . import views
+ns_patterns = patterns('',
+ url(r'^xview/func/$', views.xview_dec(views.xview), name='func'),
+)
+
urlpatterns = patterns('',
(r'^admin/', include(admin.site.urls)),
(r'^admindocs/', include('django.contrib.admindocs.urls')),
+ (r'^', include(ns_patterns, namespace='test')),
(r'^xview/func/$', views.xview_dec(views.xview)),
(r'^xview/class/$', views.xview_dec(views.XViewClass.as_view())),
)
diff --git a/tests/admin_filters/tests.py b/tests/admin_filters/tests.py
index 42012d201e..7381447051 100644
--- a/tests/admin_filters/tests.py
+++ b/tests/admin_filters/tests.py
@@ -243,9 +243,13 @@ class ListFiltersTests(TestCase):
self.assertEqual(force_text(filterspec.title), 'date registered')
choice = select_by(filterspec.choices(changelist), "display", "Today")
self.assertEqual(choice['selected'], True)
- self.assertEqual(choice['query_string'], '?date_registered__gte=%s'
- '&date_registered__lt=%s'
- % (self.today, self.tomorrow))
+ self.assertEqual(
+ choice['query_string'],
+ '?date_registered__gte=%s&date_registered__lt=%s' % (
+ self.today,
+ self.tomorrow,
+ )
+ )
request = self.request_factory.get('/', {'date_registered__gte': self.today.replace(day=1),
'date_registered__lt': self.next_month})
@@ -264,9 +268,13 @@ class ListFiltersTests(TestCase):
self.assertEqual(force_text(filterspec.title), 'date registered')
choice = select_by(filterspec.choices(changelist), "display", "This month")
self.assertEqual(choice['selected'], True)
- self.assertEqual(choice['query_string'], '?date_registered__gte=%s'
- '&date_registered__lt=%s'
- % (self.today.replace(day=1), self.next_month))
+ self.assertEqual(
+ choice['query_string'],
+ '?date_registered__gte=%s&date_registered__lt=%s' % (
+ self.today.replace(day=1),
+ self.next_month,
+ )
+ )
request = self.request_factory.get('/', {'date_registered__gte': self.today.replace(month=1, day=1),
'date_registered__lt': self.next_year})
@@ -285,12 +293,18 @@ class ListFiltersTests(TestCase):
self.assertEqual(force_text(filterspec.title), 'date registered')
choice = select_by(filterspec.choices(changelist), "display", "This year")
self.assertEqual(choice['selected'], True)
- self.assertEqual(choice['query_string'], '?date_registered__gte=%s'
- '&date_registered__lt=%s'
- % (self.today.replace(month=1, day=1), self.next_year))
+ self.assertEqual(
+ choice['query_string'],
+ '?date_registered__gte=%s&date_registered__lt=%s' % (
+ self.today.replace(month=1, day=1),
+ self.next_year,
+ )
+ )
- request = self.request_factory.get('/', {'date_registered__gte': str(self.one_week_ago),
- 'date_registered__lt': str(self.tomorrow)})
+ request = self.request_factory.get('/', {
+ 'date_registered__gte': str(self.one_week_ago),
+ 'date_registered__lt': str(self.tomorrow),
+ })
changelist = self.get_changelist(request, Book, modeladmin)
# Make sure the correct queryset is returned
@@ -302,9 +316,13 @@ class ListFiltersTests(TestCase):
self.assertEqual(force_text(filterspec.title), 'date registered')
choice = select_by(filterspec.choices(changelist), "display", "Past 7 days")
self.assertEqual(choice['selected'], True)
- self.assertEqual(choice['query_string'], '?date_registered__gte=%s'
- '&date_registered__lt=%s'
- % (str(self.one_week_ago), str(self.tomorrow)))
+ self.assertEqual(
+ choice['query_string'],
+ '?date_registered__gte=%s&date_registered__lt=%s' % (
+ str(self.one_week_ago),
+ str(self.tomorrow),
+ )
+ )
@override_settings(USE_TZ=True)
def test_datefieldlistfilter_with_time_zone_support(self):
diff --git a/tests/admin_scripts/custom_templates/app_template/models.py b/tests/admin_scripts/custom_templates/app_template/models.py
deleted file mode 100644
index fcd0b215d8..0000000000
--- a/tests/admin_scripts/custom_templates/app_template/models.py
+++ /dev/null
@@ -1 +0,0 @@
-# whatever
diff --git a/tests/admin_scripts/tests.py b/tests/admin_scripts/tests.py
index 73a91b6e7b..8f693bfd34 100644
--- a/tests/admin_scripts/tests.py
+++ b/tests/admin_scripts/tests.py
@@ -379,14 +379,14 @@ class DjangoAdminMinimalSettings(AdminScriptTestCase):
args = ['sqlall', '--settings=test_project.settings', 'admin_scripts']
out, err = self.run_django_admin(args)
self.assertNoOutput(out)
- self.assertOutput(err, 'App with label admin_scripts could not be found')
+ self.assertOutput(err, "No app with label 'admin_scripts'.")
def test_builtin_with_environment(self):
"minimal: django-admin builtin commands fail if settings are provided in the environment"
args = ['sqlall', 'admin_scripts']
out, err = self.run_django_admin(args, 'test_project.settings')
self.assertNoOutput(out)
- self.assertOutput(err, 'App with label admin_scripts could not be found')
+ self.assertOutput(err, "No app with label 'admin_scripts'.")
def test_builtin_with_bad_settings(self):
"minimal: django-admin builtin commands fail if settings file (from argument) doesn't exist"
@@ -815,21 +815,21 @@ class ManageMinimalSettings(AdminScriptTestCase):
args = ['sqlall', 'admin_scripts']
out, err = self.run_manage(args)
self.assertNoOutput(out)
- self.assertOutput(err, 'App with label admin_scripts could not be found')
+ self.assertOutput(err, "No app with label 'admin_scripts'.")
def test_builtin_with_settings(self):
"minimal: manage.py builtin commands fail if settings are provided as argument"
args = ['sqlall', '--settings=test_project.settings', 'admin_scripts']
out, err = self.run_manage(args)
self.assertNoOutput(out)
- self.assertOutput(err, 'App with label admin_scripts could not be found')
+ self.assertOutput(err, "No app with label 'admin_scripts'.")
def test_builtin_with_environment(self):
"minimal: manage.py builtin commands fail if settings are provided in the environment"
args = ['sqlall', 'admin_scripts']
out, err = self.run_manage(args, 'test_project.settings')
self.assertNoOutput(out)
- self.assertOutput(err, 'App with label admin_scripts could not be found')
+ self.assertOutput(err, "No app with label 'admin_scripts'.")
def test_builtin_with_bad_settings(self):
"minimal: manage.py builtin commands fail if settings file (from argument) doesn't exist"
@@ -964,7 +964,7 @@ class ManageMultipleSettings(AdminScriptTestCase):
args = ['sqlall', 'admin_scripts']
out, err = self.run_manage(args)
self.assertNoOutput(out)
- self.assertOutput(err, 'App with label admin_scripts could not be found.')
+ self.assertOutput(err, "No app with label 'admin_scripts'.")
def test_builtin_with_settings(self):
"multiple: manage.py builtin commands succeed if settings are provided as argument"
@@ -1442,13 +1442,13 @@ class CommandTypes(AdminScriptTestCase):
"User AppCommands can execute when a single app name is provided"
args = ['app_command', 'NOT_AN_APP']
out, err = self.run_manage(args)
- self.assertOutput(err, "App with label NOT_AN_APP could not be found")
+ self.assertOutput(err, "No app with label 'NOT_AN_APP'.")
def test_app_command_some_invalid_appnames(self):
"User AppCommands can execute when some of the provided app names are invalid"
args = ['app_command', 'auth', 'NOT_AN_APP']
out, err = self.run_manage(args)
- self.assertOutput(err, "App with label NOT_AN_APP could not be found")
+ self.assertOutput(err, "No app with label 'NOT_AN_APP'.")
def test_label_command(self):
"User LabelCommands can execute when a label is provided"
diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py
index 2cc0618cc3..c813ee8437 100644
--- a/tests/admin_views/tests.py
+++ b/tests/admin_views/tests.py
@@ -2275,6 +2275,15 @@ class AdminSearchTest(TestCase):
self.assertContains(response, "\n1 pluggable search person\n")
self.assertContains(response, "Amy")
+ def test_reset_link(self):
+ """
+ Test presence of reset link in search bar ("1 result (_x total_)").
+ """
+ response = self.client.get('/test_admin/admin/admin_views/person/?q=Gui')
+ self.assertContains(response,
+ """1 result (3 total)""",
+ html=True)
+
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class AdminInheritedInlinesTest(TestCase):
@@ -2716,17 +2725,17 @@ class AdminCustomQuerysetTest(TestCase):
resp = self.client.get('/test_admin/admin/admin_views/person/')
self.assertEqual(resp.context['selection_note'], '0 of 2 selected')
self.assertEqual(resp.context['selection_note_all'], 'All 2 selected')
- with self.assertNumQueries(4):
+ # here one more count(*) query will run, because filters were applied
+ with self.assertNumQueries(5):
extra = {'q': 'not_in_name'}
resp = self.client.get('/test_admin/admin/admin_views/person/', extra)
self.assertEqual(resp.context['selection_note'], '0 of 0 selected')
self.assertEqual(resp.context['selection_note_all'], 'All 0 selected')
- with self.assertNumQueries(4):
+ with self.assertNumQueries(5):
extra = {'q': 'person'}
resp = self.client.get('/test_admin/admin/admin_views/person/', extra)
self.assertEqual(resp.context['selection_note'], '0 of 2 selected')
self.assertEqual(resp.context['selection_note_all'], 'All 2 selected')
- # here one more count(*) query will run, because filters were applied
with self.assertNumQueries(5):
extra = {'gender__exact': '1'}
resp = self.client.get('/test_admin/admin/admin_views/person/', extra)
@@ -4634,7 +4643,7 @@ class AdminViewOnSiteTests(TestCase):
# actual regression test
for error_set in response.context['inline_admin_formset'].formset.errors:
self.assertEqual(['Children must share a family name with their parents in this contrived test case'],
- error_set.get('__all__'))
+ error_set.get('__all__'))
def test_change_view_form_and_formsets_run_validation(self):
"""
@@ -4664,7 +4673,7 @@ class AdminViewOnSiteTests(TestCase):
# actual regression test
for error_set in response.context['inline_admin_formset'].formset.errors:
self.assertEqual(['Children must share a family name with their parents in this contrived test case'],
- error_set.get('__all__'))
+ error_set.get('__all__'))
def test_validate(self):
"Ensure that the view_on_site value is either a boolean or a callable"
diff --git a/tests/app_cache/models.py b/tests/app_cache/models.py
index 1b4d33c2f9..10e9e6f1de 100644
--- a/tests/app_cache/models.py
+++ b/tests/app_cache/models.py
@@ -1,9 +1,9 @@
+from django.core.apps.cache import AppCache
from django.db import models
-from django.db.models.loading import BaseAppCache
# We're testing app cache presence on load, so this is handy.
-new_app_cache = BaseAppCache()
+new_app_cache = AppCache()
class TotallyNormal(models.Model):
diff --git a/tests/app_cache/tests.py b/tests/app_cache/tests.py
index b72b862de3..a531a22e8b 100644
--- a/tests/app_cache/tests.py
+++ b/tests/app_cache/tests.py
@@ -1,21 +1,21 @@
from __future__ import absolute_import
-from django.test import TestCase
-from django.db.models.loading import cache, BaseAppCache
+
+from django.core.apps import app_cache
+from django.core.apps.cache import AppCache
from django.db import models
+from django.test import TestCase
+
from .models import TotallyNormal, SoAlternative, new_app_cache
class AppCacheTests(TestCase):
- """
- Tests the AppCache borg and non-borg versions
- """
def test_models_py(self):
"""
Tests that the models in the models.py file were loaded correctly.
"""
- self.assertEqual(cache.get_model("app_cache", "TotallyNormal"), TotallyNormal)
- self.assertEqual(cache.get_model("app_cache", "SoAlternative"), None)
+ self.assertEqual(app_cache.get_model("app_cache", "TotallyNormal"), TotallyNormal)
+ self.assertEqual(app_cache.get_model("app_cache", "SoAlternative"), None)
self.assertEqual(new_app_cache.get_model("app_cache", "TotallyNormal"), None)
self.assertEqual(new_app_cache.get_model("app_cache", "SoAlternative"), SoAlternative)
@@ -24,10 +24,10 @@ class AppCacheTests(TestCase):
"""
Makes a new model at runtime and ensures it goes into the right place.
"""
- old_models = cache.get_models(cache.get_app("app_cache"))
+ old_models = app_cache.get_models(app_cache.get_app_config("app_cache").models_module)
# Construct a new model in a new app cache
body = {}
- new_app_cache = BaseAppCache()
+ new_app_cache = AppCache()
meta_contents = {
'app_label': "app_cache",
'app_cache': new_app_cache,
@@ -39,6 +39,13 @@ class AppCacheTests(TestCase):
# Make sure it appeared in the right place!
self.assertEqual(
old_models,
- cache.get_models(cache.get_app("app_cache")),
+ app_cache.get_models(app_cache.get_app_config("app_cache").models_module),
)
self.assertEqual(new_app_cache.get_model("app_cache", "SouthPonies"), temp_model)
+
+ def test_singleton_master(self):
+ """
+ Ensures that only one master app cache can exist.
+ """
+ with self.assertRaises(RuntimeError):
+ AppCache(master=True)
diff --git a/tests/app_loading/tests.py b/tests/app_loading/tests.py
index 20ec064d69..b32f510a24 100644
--- a/tests/app_loading/tests.py
+++ b/tests/app_loading/tests.py
@@ -1,11 +1,11 @@
from __future__ import unicode_literals
-import copy
import os
import sys
from unittest import TestCase
-from django.db.models.loading import cache, load_app, get_model, get_models, AppCache
+from django.core.apps import app_cache
+from django.core.apps.cache import AppCache
from django.test.utils import override_settings
from django.utils._os import upath
@@ -16,53 +16,52 @@ class EggLoadingTest(TestCase):
self.old_path = sys.path[:]
self.egg_dir = '%s/eggs' % os.path.dirname(upath(__file__))
- # This test adds dummy applications to the app cache. These
- # need to be removed in order to prevent bad interactions
- # with the flush operation in other tests.
- self.old_app_models = copy.deepcopy(cache.app_models)
- self.old_app_store = copy.deepcopy(cache.app_store)
+ # The models need to be removed after the test in order to prevent bad
+ # interactions with the flush operation in other tests.
+ self._old_models = app_cache.app_configs['app_loading'].models.copy()
def tearDown(self):
+ app_cache.app_configs['app_loading'].models = self._old_models
+ app_cache._get_models_cache = {}
+
sys.path = self.old_path
- cache.app_models = self.old_app_models
- cache.app_store = self.old_app_store
def test_egg1(self):
"""Models module can be loaded from an app in an egg"""
egg_name = '%s/modelapp.egg' % self.egg_dir
sys.path.append(egg_name)
- models = load_app('app_with_models')
+ models = app_cache.load_app('app_with_models')
self.assertFalse(models is None)
def test_egg2(self):
"""Loading an app from an egg that has no models returns no models (and no error)"""
egg_name = '%s/nomodelapp.egg' % self.egg_dir
sys.path.append(egg_name)
- models = load_app('app_no_models')
+ models = app_cache.load_app('app_no_models')
self.assertTrue(models is None)
def test_egg3(self):
"""Models module can be loaded from an app located under an egg's top-level package"""
egg_name = '%s/omelet.egg' % self.egg_dir
sys.path.append(egg_name)
- models = load_app('omelet.app_with_models')
+ models = app_cache.load_app('omelet.app_with_models')
self.assertFalse(models is None)
def test_egg4(self):
"""Loading an app with no models from under the top-level egg package generates no error"""
egg_name = '%s/omelet.egg' % self.egg_dir
sys.path.append(egg_name)
- models = load_app('omelet.app_no_models')
+ models = app_cache.load_app('omelet.app_no_models')
self.assertTrue(models is None)
def test_egg5(self):
"""Loading an app from an egg that has an import error in its models module raises that error"""
egg_name = '%s/brokenapp.egg' % self.egg_dir
sys.path.append(egg_name)
- self.assertRaises(ImportError, load_app, 'broken_app')
+ self.assertRaises(ImportError, app_cache.load_app, 'broken_app')
raised = None
try:
- load_app('broken_app')
+ app_cache.load_app('broken_app')
except ImportError as e:
raised = e
@@ -76,17 +75,14 @@ class EggLoadingTest(TestCase):
Test that repeated app loading doesn't succeed in case there is an
error. Refs #17667.
"""
- # AppCache is a Borg, so we can instantiate one and change its
- # loaded to False to force the following code to actually try to
- # populate the cache.
- a = AppCache()
- a.loaded = False
- try:
- with override_settings(INSTALLED_APPS=('notexists',)):
- self.assertRaises(ImportError, get_model, 'notexists', 'nomodel', seed_cache=True)
- self.assertRaises(ImportError, get_model, 'notexists', 'nomodel', seed_cache=True)
- finally:
- a.loaded = True
+ app_cache = AppCache()
+ # Pretend we're the master app cache to test populate().
+ app_cache.master = True
+ with override_settings(INSTALLED_APPS=('notexists',)):
+ with self.assertRaises(ImportError):
+ app_cache.get_model('notexists', 'nomodel', seed_cache=True)
+ with self.assertRaises(ImportError):
+ app_cache.get_model('notexists', 'nomodel', seed_cache=True)
class GetModelsTest(TestCase):
@@ -96,26 +92,26 @@ class GetModelsTest(TestCase):
def test_get_model_only_returns_installed_models(self):
self.assertEqual(
- get_model("not_installed", "NotInstalledModel"), None)
+ app_cache.get_model("not_installed", "NotInstalledModel"), None)
def test_get_model_with_not_installed(self):
self.assertEqual(
- get_model(
+ app_cache.get_model(
"not_installed", "NotInstalledModel", only_installed=False),
self.not_installed_module.NotInstalledModel)
def test_get_models_only_returns_installed_models(self):
self.assertFalse(
"NotInstalledModel" in
- [m.__name__ for m in get_models()])
+ [m.__name__ for m in app_cache.get_models()])
def test_get_models_with_app_label_only_returns_installed_models(self):
- self.assertEqual(get_models(self.not_installed_module), [])
+ self.assertEqual(app_cache.get_models(self.not_installed_module), [])
def test_get_models_with_not_installed(self):
self.assertTrue(
"NotInstalledModel" in [
- m.__name__ for m in get_models(only_installed=False)])
+ m.__name__ for m in app_cache.get_models(only_installed=False)])
class NotInstalledModelsTest(TestCase):
diff --git a/tests/builtin_server/models.py b/tests/builtin_server/models.py
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/tests/commands_sql/tests.py b/tests/commands_sql/tests.py
index 3a0b5527f4..3088ea4d48 100644
--- a/tests/commands_sql/tests.py
+++ b/tests/commands_sql/tests.py
@@ -1,9 +1,10 @@
from __future__ import unicode_literals
+from django.core.apps import app_cache
from django.core.management.color import no_style
from django.core.management.sql import (sql_create, sql_delete, sql_indexes,
sql_destroy_indexes, sql_all)
-from django.db import connections, DEFAULT_DB_ALIAS, models, router
+from django.db import connections, DEFAULT_DB_ALIAS, router
from django.test import TestCase
from django.utils import six
@@ -16,7 +17,7 @@ class SQLCommandsTestCase(TestCase):
return len([o for o in output if o.startswith(cmd)])
def test_sql_create(self):
- app = models.get_app('commands_sql')
+ app = app_cache.get_app_config('commands_sql').models_module
output = sql_create(app, no_style(), connections[DEFAULT_DB_ALIAS])
create_tables = [o for o in output if o.startswith('CREATE TABLE')]
self.assertEqual(len(create_tables), 3)
@@ -25,7 +26,7 @@ class SQLCommandsTestCase(TestCase):
six.assertRegex(self, sql, r'^create table .commands_sql_book.*')
def test_sql_delete(self):
- app = models.get_app('commands_sql')
+ app = app_cache.get_app_config('commands_sql').models_module
output = sql_delete(app, no_style(), connections[DEFAULT_DB_ALIAS])
drop_tables = [o for o in output if o.startswith('DROP TABLE')]
self.assertEqual(len(drop_tables), 3)
@@ -34,19 +35,19 @@ class SQLCommandsTestCase(TestCase):
six.assertRegex(self, sql, r'^drop table .commands_sql_comment.*')
def test_sql_indexes(self):
- app = models.get_app('commands_sql')
+ app = app_cache.get_app_config('commands_sql').models_module
output = sql_indexes(app, no_style(), connections[DEFAULT_DB_ALIAS])
# PostgreSQL creates one additional index for CharField
self.assertIn(self.count_ddl(output, 'CREATE INDEX'), [3, 4])
def test_sql_destroy_indexes(self):
- app = models.get_app('commands_sql')
+ app = app_cache.get_app_config('commands_sql').models_module
output = sql_destroy_indexes(app, no_style(), connections[DEFAULT_DB_ALIAS])
# PostgreSQL creates one additional index for CharField
self.assertIn(self.count_ddl(output, 'DROP INDEX'), [3, 4])
def test_sql_all(self):
- app = models.get_app('commands_sql')
+ app = app_cache.get_app_config('commands_sql').models_module
output = sql_all(app, no_style(), connections[DEFAULT_DB_ALIAS])
self.assertEqual(self.count_ddl(output, 'CREATE TABLE'), 3)
@@ -68,7 +69,7 @@ class SQLCommandsRouterTestCase(TestCase):
router.routers = self._old_routers
def test_router_honored(self):
- app = models.get_app('commands_sql')
+ app = app_cache.get_app_config('commands_sql').models_module
for sql_command in (sql_all, sql_create, sql_delete, sql_indexes, sql_destroy_indexes):
output = sql_command(app, no_style(), connections[DEFAULT_DB_ALIAS])
self.assertEqual(len(output), 0,
diff --git a/tests/conditional_processing/models.py b/tests/conditional_processing/models.py
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/tests/contenttypes_tests/tests.py b/tests/contenttypes_tests/tests.py
index 9d1e1f77ea..9cc254e665 100644
--- a/tests/contenttypes_tests/tests.py
+++ b/tests/contenttypes_tests/tests.py
@@ -1,8 +1,8 @@
from __future__ import unicode_literals
from django.contrib.contenttypes.models import ContentType
+from django.core.apps.cache import AppCache
from django.db import models
-from django.db.models.loading import BaseAppCache
from django.test import TestCase
from .models import Author, Article
@@ -61,7 +61,7 @@ class ContentTypesViewsTests(TestCase):
class Meta:
verbose_name = 'a model created on the fly'
app_label = 'my_great_app'
- app_cache = BaseAppCache()
+ app_cache = AppCache()
ct = ContentType.objects.get_for_model(ModelCreatedOnTheFly)
self.assertEqual(ct.app_label, 'my_great_app')
diff --git a/tests/context_processors/models.py b/tests/context_processors/models.py
deleted file mode 100644
index cde172db68..0000000000
--- a/tests/context_processors/models.py
+++ /dev/null
@@ -1 +0,0 @@
-# Models file for tests to run.
diff --git a/tests/createsuperuser/models.py b/tests/createsuperuser/models.py
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/tests/createsuperuser/tests.py b/tests/createsuperuser/tests.py
index f64e4d9915..822f05d01b 100644
--- a/tests/createsuperuser/tests.py
+++ b/tests/createsuperuser/tests.py
@@ -38,7 +38,8 @@ class MultiDBCreatesuperuserTestCase(TestCase):
" createsuperuser command should operate on specified DB"
new_io = StringIO()
- call_command("createsuperuser",
+ call_command(
+ "createsuperuser",
interactive=False,
username="joe",
email="joe@somewhere.org",
diff --git a/tests/csrf_tests/models.py b/tests/csrf_tests/models.py
deleted file mode 100644
index 71abcc5198..0000000000
--- a/tests/csrf_tests/models.py
+++ /dev/null
@@ -1 +0,0 @@
-# models.py file for tests to run.
diff --git a/tests/custom_columns/tests.py b/tests/custom_columns/tests.py
index 155279297f..aae3712e53 100644
--- a/tests/custom_columns/tests.py
+++ b/tests/custom_columns/tests.py
@@ -42,7 +42,8 @@ class CustomColumnsTests(TestCase):
)
def test_field_error(self):
- self.assertRaises(FieldError,
+ self.assertRaises(
+ FieldError,
lambda: Author.objects.filter(firstname__exact="John")
)
diff --git a/tests/custom_pk/tests.py b/tests/custom_pk/tests.py
index 22369747a9..09a3ee5888 100644
--- a/tests/custom_pk/tests.py
+++ b/tests/custom_pk/tests.py
@@ -34,7 +34,8 @@ class CustomPKTests(TestCase):
self.assertEqual(Employee.objects.get(pk=123), dan)
self.assertEqual(Employee.objects.get(pk=456), fran)
- self.assertRaises(Employee.DoesNotExist,
+ self.assertRaises(
+ Employee.DoesNotExist,
lambda: Employee.objects.get(pk=42)
)
@@ -153,6 +154,13 @@ class CustomPKTests(TestCase):
with transaction.atomic():
Employee.objects.create(employee_code=123, first_name="Fred", last_name="Jones")
+ def test_zero_non_autoincrement_pk(self):
+ Employee.objects.create(
+ employee_code=0, first_name="Frank", last_name="Jones"
+ )
+ employee = Employee.objects.get(pk=0)
+ self.assertEqual(employee.employee_code, 0)
+
def test_custom_field_pk(self):
# Regression for #10785 -- Custom fields can be used for primary keys.
new_bar = Bar.objects.create()
diff --git a/tests/db_typecasts/models.py b/tests/db_typecasts/models.py
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/tests/decorators/models.py b/tests/decorators/models.py
deleted file mode 100644
index 22e41b9828..0000000000
--- a/tests/decorators/models.py
+++ /dev/null
@@ -1 +0,0 @@
-# A models.py so that tests run.
diff --git a/tests/defaultfilters/models.py b/tests/defaultfilters/models.py
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/tests/defaultfilters/tests.py b/tests/defaultfilters/tests.py
index 92972e1edb..2dc6d9cf0c 100644
--- a/tests/defaultfilters/tests.py
+++ b/tests/defaultfilters/tests.py
@@ -59,7 +59,7 @@ class DefaultFiltersTests(TestCase):
self.assertEqual(floatformat(0, 7), '0.0000000')
self.assertEqual(floatformat(0, 10), '0.0000000000')
self.assertEqual(floatformat(0.000000000000000000015, 20),
- '0.00000000000000000002')
+ '0.00000000000000000002')
pos_inf = float(1e30000)
self.assertEqual(floatformat(pos_inf), six.text_type(pos_inf))
@@ -103,10 +103,10 @@ class DefaultFiltersTests(TestCase):
def test_addslashes(self):
self.assertEqual(addslashes('"double quotes" and \'single quotes\''),
- '\\"double quotes\\" and \\\'single quotes\\\'')
+ '\\"double quotes\\" and \\\'single quotes\\\'')
self.assertEqual(addslashes(r'\ : backslashes, too'),
- '\\\\ : backslashes, too')
+ '\\\\ : backslashes, too')
def test_capfirst(self):
self.assertEqual(capfirst('hello world'), 'Hello world')
@@ -126,14 +126,14 @@ class DefaultFiltersTests(TestCase):
def test_fix_ampersands(self):
self.assertEqual(fix_ampersands_filter('Jack & Jill & Jeroboam'),
- 'Jack & Jill & Jeroboam')
+ 'Jack & Jill & Jeroboam')
def test_linenumbers(self):
self.assertEqual(linenumbers('line 1\nline 2'),
- '1. line 1\n2. line 2')
+ '1. line 1\n2. line 2')
self.assertEqual(linenumbers('\n'.join(['x'] * 10)),
- '01. x\n02. x\n03. x\n04. x\n05. x\n06. x\n07. '
- 'x\n08. x\n09. x\n10. x')
+ '01. x\n02. x\n03. x\n04. x\n05. x\n06. x\n07. '
+ 'x\n08. x\n09. x\n10. x')
def test_lower(self):
self.assertEqual(lower('TEST'), 'test')
@@ -151,7 +151,7 @@ class DefaultFiltersTests(TestCase):
'jack-jill-like-numbers-123-and-4-and-silly-characters')
self.assertEqual(slugify("Un \xe9l\xe9phant \xe0 l'or\xe9e du bois"),
- 'un-elephant-a-loree-du-bois')
+ 'un-elephant-a-loree-du-bois')
def test_stringformat(self):
self.assertEqual(stringformat(1, '03d'), '001')
@@ -159,7 +159,7 @@ class DefaultFiltersTests(TestCase):
def test_title(self):
self.assertEqual(title('a nice title, isn\'t it?'),
- "A Nice Title, Isn't It?")
+ "A Nice Title, Isn't It?")
self.assertEqual(title('discoth\xe8que'), 'Discoth\xe8que')
def test_truncatewords(self):
@@ -203,14 +203,14 @@ class DefaultFiltersTests(TestCase):
def test_urlencode(self):
self.assertEqual(urlencode('fran\xe7ois & jill'),
- 'fran%C3%A7ois%20%26%20jill')
+ 'fran%C3%A7ois%20%26%20jill')
self.assertEqual(urlencode(1), '1')
def test_iriencode(self):
self.assertEqual(iriencode('S\xf8r-Tr\xf8ndelag'),
- 'S%C3%B8r-Tr%C3%B8ndelag')
+ 'S%C3%B8r-Tr%C3%B8ndelag')
self.assertEqual(iriencode(urlencode('fran\xe7ois & jill')),
- 'fran%C3%A7ois%20%26%20jill')
+ 'fran%C3%A7ois%20%26%20jill')
def test_urlizetrunc(self):
self.assertEqual(urlizetrunc('http://short.com/', 20), ' here')
@@ -402,19 +402,19 @@ class DefaultFiltersTests(TestCase):
def test_linebreaks(self):
self.assertEqual(linebreaks_filter('line 1'), 'line 1
')
self.assertEqual(linebreaks_filter('line 1\nline 2'),
- 'line 1
line 2
')
+ 'line 1
line 2
')
self.assertEqual(linebreaks_filter('line 1\rline 2'),
- 'line 1
line 2
')
+ 'line 1
line 2
')
self.assertEqual(linebreaks_filter('line 1\r\nline 2'),
- 'line 1
line 2
')
+ 'line 1
line 2
')
def test_linebreaksbr(self):
self.assertEqual(linebreaksbr('line 1\nline 2'),
- 'line 1
line 2')
+ 'line 1
line 2')
self.assertEqual(linebreaksbr('line 1\rline 2'),
- 'line 1
line 2')
+ 'line 1
line 2')
self.assertEqual(linebreaksbr('line 1\r\nline 2'),
- 'line 1
line 2')
+ 'line 1
line 2')
def test_removetags(self):
self.assertEqual(removetags('some html with