mirror of https://github.com/django/django.git
Merge branch 'master' of https://github.com/django/django
This commit is contained in:
commit
5c645ec81a
1
AUTHORS
1
AUTHORS
|
@ -397,6 +397,7 @@ answer newbie questions, and generally made Django that much better:
|
||||||
Yann Malet
|
Yann Malet
|
||||||
Frantisek Malina <vizualbod@vizualbod.com>
|
Frantisek Malina <vizualbod@vizualbod.com>
|
||||||
Mike Malone <mjmalone@gmail.com>
|
Mike Malone <mjmalone@gmail.com>
|
||||||
|
Curtis Maloney (FunkyBob) <curtis@tinbrain.net>
|
||||||
Martin Maney <http://www.chipy.org/Martin_Maney>
|
Martin Maney <http://www.chipy.org/Martin_Maney>
|
||||||
Michael Manfre <mmanfre@gmail.com>
|
Michael Manfre <mmanfre@gmail.com>
|
||||||
Javier Mansilla <javimansilla@gmail.com>
|
Javier Mansilla <javimansilla@gmail.com>
|
||||||
|
|
|
@ -7,35 +7,8 @@ from django.contrib.admin.sites import AdminSite, site
|
||||||
from django.contrib.admin.filters import (ListFilter, SimpleListFilter,
|
from django.contrib.admin.filters import (ListFilter, SimpleListFilter,
|
||||||
FieldListFilter, BooleanFieldListFilter, RelatedFieldListFilter,
|
FieldListFilter, BooleanFieldListFilter, RelatedFieldListFilter,
|
||||||
ChoicesFieldListFilter, DateFieldListFilter, AllValuesFieldListFilter)
|
ChoicesFieldListFilter, DateFieldListFilter, AllValuesFieldListFilter)
|
||||||
|
from django.utils.module_loading import autodiscover_modules
|
||||||
|
|
||||||
|
|
||||||
def autodiscover():
|
def autodiscover():
|
||||||
"""
|
autodiscover_modules('admin', register_to=site)
|
||||||
Auto-discover INSTALLED_APPS admin.py modules and fail silently when
|
|
||||||
not present. This forces an import on them to register any admin bits they
|
|
||||||
may want.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import copy
|
|
||||||
from importlib import import_module
|
|
||||||
from django.conf import settings
|
|
||||||
from django.utils.module_loading import module_has_submodule
|
|
||||||
|
|
||||||
for app in settings.INSTALLED_APPS:
|
|
||||||
mod = import_module(app)
|
|
||||||
# Attempt to import the app's admin module.
|
|
||||||
try:
|
|
||||||
before_import_registry = copy.copy(site._registry)
|
|
||||||
import_module('%s.admin' % app)
|
|
||||||
except:
|
|
||||||
# Reset the model registry to the state before the last import as
|
|
||||||
# this import will have to reoccur on the next request and this
|
|
||||||
# could raise NotRegistered and AlreadyRegistered exceptions
|
|
||||||
# (see #8245).
|
|
||||||
site._registry = before_import_registry
|
|
||||||
|
|
||||||
# Decide whether to bubble up this error. If the app just
|
|
||||||
# doesn't have an admin module, we can ignore the error
|
|
||||||
# attempting to import it, otherwise we want it to bubble up.
|
|
||||||
if module_has_submodule(mod, 'admin'):
|
|
||||||
raise
|
|
||||||
|
|
|
@ -33,26 +33,26 @@ class ListFilter(object):
|
||||||
"""
|
"""
|
||||||
Returns True if some choices would be output for this filter.
|
Returns True if some choices would be output for this filter.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of ListFilter must provide a has_output() method')
|
||||||
|
|
||||||
def choices(self, cl):
|
def choices(self, cl):
|
||||||
"""
|
"""
|
||||||
Returns choices ready to be output in the template.
|
Returns choices ready to be output in the template.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of ListFilter must provide a choices() method')
|
||||||
|
|
||||||
def queryset(self, request, queryset):
|
def queryset(self, request, queryset):
|
||||||
"""
|
"""
|
||||||
Returns the filtered queryset.
|
Returns the filtered queryset.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of ListFilter must provide a queryset() method')
|
||||||
|
|
||||||
def expected_parameters(self):
|
def expected_parameters(self):
|
||||||
"""
|
"""
|
||||||
Returns the list of parameter names that are expected from the
|
Returns the list of parameter names that are expected from the
|
||||||
request's query string and that will be used by this filter.
|
request's query string and that will be used by this filter.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of ListFilter must provide an expected_parameters() method')
|
||||||
|
|
||||||
|
|
||||||
class SimpleListFilter(ListFilter):
|
class SimpleListFilter(ListFilter):
|
||||||
|
@ -89,7 +89,9 @@ class SimpleListFilter(ListFilter):
|
||||||
"""
|
"""
|
||||||
Must be overridden to return a list of tuples (value, verbose value)
|
Must be overridden to return a list of tuples (value, verbose value)
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError(
|
||||||
|
'The SimpleListFilter.lookups() method must be overridden to '
|
||||||
|
'return a list of tuples (value, verbose value)')
|
||||||
|
|
||||||
def expected_parameters(self):
|
def expected_parameters(self):
|
||||||
return [self.parameter_name]
|
return [self.parameter_name]
|
||||||
|
|
|
@ -6,7 +6,7 @@ from django.contrib.auth import logout as auth_logout, REDIRECT_FIELD_NAME
|
||||||
from django.contrib.contenttypes import views as contenttype_views
|
from django.contrib.contenttypes import views as contenttype_views
|
||||||
from django.views.decorators.csrf import csrf_protect
|
from django.views.decorators.csrf import csrf_protect
|
||||||
from django.db.models.base import ModelBase
|
from django.db.models.base import ModelBase
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
|
||||||
from django.core.urlresolvers import reverse, NoReverseMatch
|
from django.core.urlresolvers import reverse, NoReverseMatch
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
@ -232,14 +232,25 @@ class AdminSite(object):
|
||||||
url(r'^password_change/done/$', wrap(self.password_change_done, cacheable=True), name='password_change_done'),
|
url(r'^password_change/done/$', wrap(self.password_change_done, cacheable=True), name='password_change_done'),
|
||||||
url(r'^jsi18n/$', wrap(self.i18n_javascript, cacheable=True), name='jsi18n'),
|
url(r'^jsi18n/$', wrap(self.i18n_javascript, cacheable=True), name='jsi18n'),
|
||||||
url(r'^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$', wrap(contenttype_views.shortcut), name='view_on_site'),
|
url(r'^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$', wrap(contenttype_views.shortcut), name='view_on_site'),
|
||||||
url(r'^(?P<app_label>\w+)/$', wrap(self.app_index), name='app_list'),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add in each model's views.
|
# Add in each model's views, and create a list of valid URLS for the
|
||||||
|
# app_index
|
||||||
|
valid_app_labels = []
|
||||||
for model, model_admin in six.iteritems(self._registry):
|
for model, model_admin in six.iteritems(self._registry):
|
||||||
urlpatterns += patterns('',
|
urlpatterns += patterns('',
|
||||||
url(r'^%s/%s/' % (model._meta.app_label, model._meta.model_name), include(model_admin.urls))
|
url(r'^%s/%s/' % (model._meta.app_label, model._meta.model_name), include(model_admin.urls))
|
||||||
)
|
)
|
||||||
|
if model._meta.app_label not in valid_app_labels:
|
||||||
|
valid_app_labels.append(model._meta.app_label)
|
||||||
|
|
||||||
|
# If there were ModelAdmins registered, we should have a list of app
|
||||||
|
# labels for which we need to allow access to the app_index view,
|
||||||
|
if valid_app_labels:
|
||||||
|
regex = r'^(?P<app_label>' + '|'.join(valid_app_labels) + ')/$'
|
||||||
|
urlpatterns += patterns('',
|
||||||
|
url(regex, wrap(self.app_index), name='app_list'),
|
||||||
|
)
|
||||||
return urlpatterns
|
return urlpatterns
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -399,44 +410,45 @@ class AdminSite(object):
|
||||||
def app_index(self, request, app_label, extra_context=None):
|
def app_index(self, request, app_label, extra_context=None):
|
||||||
user = request.user
|
user = request.user
|
||||||
has_module_perms = user.has_module_perms(app_label)
|
has_module_perms = user.has_module_perms(app_label)
|
||||||
|
if not has_module_perms:
|
||||||
|
raise PermissionDenied
|
||||||
app_dict = {}
|
app_dict = {}
|
||||||
for model, model_admin in self._registry.items():
|
for model, model_admin in self._registry.items():
|
||||||
if app_label == model._meta.app_label:
|
if app_label == model._meta.app_label:
|
||||||
if has_module_perms:
|
perms = model_admin.get_model_perms(request)
|
||||||
perms = model_admin.get_model_perms(request)
|
|
||||||
|
|
||||||
# Check whether user has any perm for this module.
|
# Check whether user has any perm for this module.
|
||||||
# If so, add the module to the model_list.
|
# If so, add the module to the model_list.
|
||||||
if True in perms.values():
|
if True in perms.values():
|
||||||
info = (app_label, model._meta.model_name)
|
info = (app_label, model._meta.model_name)
|
||||||
model_dict = {
|
model_dict = {
|
||||||
'name': capfirst(model._meta.verbose_name_plural),
|
'name': capfirst(model._meta.verbose_name_plural),
|
||||||
'object_name': model._meta.object_name,
|
'object_name': model._meta.object_name,
|
||||||
'perms': perms,
|
'perms': perms,
|
||||||
|
}
|
||||||
|
if perms.get('change'):
|
||||||
|
try:
|
||||||
|
model_dict['admin_url'] = reverse('admin:%s_%s_changelist' % info, current_app=self.name)
|
||||||
|
except NoReverseMatch:
|
||||||
|
pass
|
||||||
|
if perms.get('add'):
|
||||||
|
try:
|
||||||
|
model_dict['add_url'] = reverse('admin:%s_%s_add' % info, current_app=self.name)
|
||||||
|
except NoReverseMatch:
|
||||||
|
pass
|
||||||
|
if app_dict:
|
||||||
|
app_dict['models'].append(model_dict),
|
||||||
|
else:
|
||||||
|
# First time around, now that we know there's
|
||||||
|
# something to display, add in the necessary meta
|
||||||
|
# information.
|
||||||
|
app_dict = {
|
||||||
|
'name': app_label.title(),
|
||||||
|
'app_label': app_label,
|
||||||
|
'app_url': '',
|
||||||
|
'has_module_perms': has_module_perms,
|
||||||
|
'models': [model_dict],
|
||||||
}
|
}
|
||||||
if perms.get('change', False):
|
|
||||||
try:
|
|
||||||
model_dict['admin_url'] = reverse('admin:%s_%s_changelist' % info, current_app=self.name)
|
|
||||||
except NoReverseMatch:
|
|
||||||
pass
|
|
||||||
if perms.get('add', False):
|
|
||||||
try:
|
|
||||||
model_dict['add_url'] = reverse('admin:%s_%s_add' % info, current_app=self.name)
|
|
||||||
except NoReverseMatch:
|
|
||||||
pass
|
|
||||||
if app_dict:
|
|
||||||
app_dict['models'].append(model_dict),
|
|
||||||
else:
|
|
||||||
# First time around, now that we know there's
|
|
||||||
# something to display, add in the necessary meta
|
|
||||||
# information.
|
|
||||||
app_dict = {
|
|
||||||
'name': app_label.title(),
|
|
||||||
'app_label': app_label,
|
|
||||||
'app_url': '',
|
|
||||||
'has_module_perms': has_module_perms,
|
|
||||||
'models': [model_dict],
|
|
||||||
}
|
|
||||||
if not app_dict:
|
if not app_dict:
|
||||||
raise Http404('The requested admin page does not exist.')
|
raise Http404('The requested admin page does not exist.')
|
||||||
# Sort the models alphabetically within each app.
|
# Sort the models alphabetically within each app.
|
||||||
|
|
|
@ -293,8 +293,9 @@ var DateTimeShortcuts = {
|
||||||
var date_parts = inp.value.split('-');
|
var date_parts = inp.value.split('-');
|
||||||
var year = date_parts[0];
|
var year = date_parts[0];
|
||||||
var month = parseFloat(date_parts[1]);
|
var month = parseFloat(date_parts[1]);
|
||||||
|
var selected = new Date(inp.value);
|
||||||
if (year.match(/\d\d\d\d/) && month >= 1 && month <= 12) {
|
if (year.match(/\d\d\d\d/) && month >= 1 && month <= 12) {
|
||||||
DateTimeShortcuts.calendars[num].drawDate(month, year);
|
DateTimeShortcuts.calendars[num].drawDate(month, year, selected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,13 +27,29 @@ var CalendarNamespace = {
|
||||||
}
|
}
|
||||||
return days;
|
return days;
|
||||||
},
|
},
|
||||||
draw: function(month, year, div_id, callback) { // month = 1-12, year = 1-9999
|
draw: function(month, year, div_id, callback, selected) { // month = 1-12, year = 1-9999
|
||||||
var today = new Date();
|
var today = new Date();
|
||||||
var todayDay = today.getDate();
|
var todayDay = today.getDate();
|
||||||
var todayMonth = today.getMonth()+1;
|
var todayMonth = today.getMonth()+1;
|
||||||
var todayYear = today.getFullYear();
|
var todayYear = today.getFullYear();
|
||||||
var todayClass = '';
|
var todayClass = '';
|
||||||
|
|
||||||
|
// Use UTC functions here because the date field does not contain time
|
||||||
|
// and using the UTC function variants prevent the local time offset
|
||||||
|
// from altering the date, specifically the day field. For example:
|
||||||
|
//
|
||||||
|
// ```
|
||||||
|
// var x = new Date('2013-10-02');
|
||||||
|
// var day = x.getDate();
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// The day variable above will be 1 instead of 2 in, say, US Pacific time
|
||||||
|
// zone.
|
||||||
|
var isSelectedMonth = false;
|
||||||
|
if (typeof selected != 'undefined') {
|
||||||
|
isSelectedMonth = (selected.getUTCFullYear() == year && (selected.getUTCMonth()+1) == month);
|
||||||
|
}
|
||||||
|
|
||||||
month = parseInt(month);
|
month = parseInt(month);
|
||||||
year = parseInt(year);
|
year = parseInt(year);
|
||||||
var calDiv = document.getElementById(div_id);
|
var calDiv = document.getElementById(div_id);
|
||||||
|
@ -55,7 +71,7 @@ var CalendarNamespace = {
|
||||||
tableRow = quickElement('tr', tableBody);
|
tableRow = quickElement('tr', tableBody);
|
||||||
for (var i = 0; i < startingPos; i++) {
|
for (var i = 0; i < startingPos; i++) {
|
||||||
var _cell = quickElement('td', tableRow, ' ');
|
var _cell = quickElement('td', tableRow, ' ');
|
||||||
_cell.style.backgroundColor = '#f3f3f3';
|
_cell.className = "nonday";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw days of month
|
// Draw days of month
|
||||||
|
@ -69,6 +85,13 @@ var CalendarNamespace = {
|
||||||
} else {
|
} else {
|
||||||
todayClass='';
|
todayClass='';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// use UTC function; see above for explanation.
|
||||||
|
if (isSelectedMonth && currentDay == selected.getUTCDate()) {
|
||||||
|
if (todayClass != '') todayClass += " ";
|
||||||
|
todayClass += "selected";
|
||||||
|
}
|
||||||
|
|
||||||
var cell = quickElement('td', tableRow, '', 'class', todayClass);
|
var cell = quickElement('td', tableRow, '', 'class', todayClass);
|
||||||
|
|
||||||
quickElement('a', cell, currentDay, 'href', 'javascript:void(' + callback + '('+year+','+month+','+currentDay+'));');
|
quickElement('a', cell, currentDay, 'href', 'javascript:void(' + callback + '('+year+','+month+','+currentDay+'));');
|
||||||
|
@ -78,7 +101,7 @@ var CalendarNamespace = {
|
||||||
// Draw blanks after end of month (optional, but makes for valid code)
|
// Draw blanks after end of month (optional, but makes for valid code)
|
||||||
while (tableRow.childNodes.length < 7) {
|
while (tableRow.childNodes.length < 7) {
|
||||||
var _cell = quickElement('td', tableRow, ' ');
|
var _cell = quickElement('td', tableRow, ' ');
|
||||||
_cell.style.backgroundColor = '#f3f3f3';
|
_cell.className = "nonday";
|
||||||
}
|
}
|
||||||
|
|
||||||
calDiv.appendChild(calTable);
|
calDiv.appendChild(calTable);
|
||||||
|
@ -86,7 +109,7 @@ var CalendarNamespace = {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calendar -- A calendar instance
|
// Calendar -- A calendar instance
|
||||||
function Calendar(div_id, callback) {
|
function Calendar(div_id, callback, selected) {
|
||||||
// div_id (string) is the ID of the element in which the calendar will
|
// div_id (string) is the ID of the element in which the calendar will
|
||||||
// be displayed
|
// be displayed
|
||||||
// callback (string) is the name of a JavaScript function that will be
|
// callback (string) is the name of a JavaScript function that will be
|
||||||
|
@ -97,14 +120,22 @@ function Calendar(div_id, callback) {
|
||||||
this.today = new Date();
|
this.today = new Date();
|
||||||
this.currentMonth = this.today.getMonth() + 1;
|
this.currentMonth = this.today.getMonth() + 1;
|
||||||
this.currentYear = this.today.getFullYear();
|
this.currentYear = this.today.getFullYear();
|
||||||
|
if (typeof selected != 'undefined') {
|
||||||
|
this.selected = selected;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Calendar.prototype = {
|
Calendar.prototype = {
|
||||||
drawCurrent: function() {
|
drawCurrent: function() {
|
||||||
CalendarNamespace.draw(this.currentMonth, this.currentYear, this.div_id, this.callback);
|
CalendarNamespace.draw(this.currentMonth, this.currentYear, this.div_id, this.callback, this.selected);
|
||||||
},
|
},
|
||||||
drawDate: function(month, year) {
|
drawDate: function(month, year, selected) {
|
||||||
this.currentMonth = month;
|
this.currentMonth = month;
|
||||||
this.currentYear = year;
|
this.currentYear = year;
|
||||||
|
|
||||||
|
if(selected) {
|
||||||
|
this.selected = selected;
|
||||||
|
}
|
||||||
|
|
||||||
this.drawCurrent();
|
this.drawCurrent();
|
||||||
},
|
},
|
||||||
drawPreviousMonth: function() {
|
drawPreviousMonth: function() {
|
||||||
|
|
|
@ -26,8 +26,10 @@
|
||||||
</div>
|
</div>
|
||||||
{% if user.is_active and user.is_staff %}
|
{% if user.is_active and user.is_staff %}
|
||||||
<div id="user-tools">
|
<div id="user-tools">
|
||||||
{% trans 'Welcome,' %}
|
{% block welcome-msg %}
|
||||||
<strong>{% firstof user.get_short_name user.get_username %}</strong>.
|
{% trans 'Welcome,' %}
|
||||||
|
<strong>{% firstof user.get_short_name user.get_username %}</strong>.
|
||||||
|
{% endblock %}
|
||||||
{% block userlinks %}
|
{% block userlinks %}
|
||||||
{% url 'django-admindocs-docroot' as docsroot %}
|
{% url 'django-admindocs-docroot' as docsroot %}
|
||||||
{% if docsroot %}
|
{% if docsroot %}
|
||||||
|
|
|
@ -192,7 +192,7 @@ class BasePasswordHasher(object):
|
||||||
"""
|
"""
|
||||||
Checks if the given password is correct
|
Checks if the given password is correct
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError('subclasses of BasePasswordHasher must provide a verify() method')
|
||||||
|
|
||||||
def encode(self, password, salt):
|
def encode(self, password, salt):
|
||||||
"""
|
"""
|
||||||
|
@ -201,7 +201,7 @@ class BasePasswordHasher(object):
|
||||||
The result is normally formatted as "algorithm$salt$hash" and
|
The result is normally formatted as "algorithm$salt$hash" and
|
||||||
must be fewer than 128 characters.
|
must be fewer than 128 characters.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError('subclasses of BasePasswordHasher must provide an encode() method')
|
||||||
|
|
||||||
def safe_summary(self, encoded):
|
def safe_summary(self, encoded):
|
||||||
"""
|
"""
|
||||||
|
@ -210,7 +210,7 @@ class BasePasswordHasher(object):
|
||||||
The result is a dictionary and will be used where the password field
|
The result is a dictionary and will be used where the password field
|
||||||
must be displayed to construct a safe representation of the password.
|
must be displayed to construct a safe representation of the password.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError('subclasses of BasePasswordHasher must provide a safe_summary() method')
|
||||||
|
|
||||||
|
|
||||||
class PBKDF2PasswordHasher(BasePasswordHasher):
|
class PBKDF2PasswordHasher(BasePasswordHasher):
|
||||||
|
|
|
@ -245,10 +245,10 @@ class AbstractBaseUser(models.Model):
|
||||||
return is_password_usable(self.password)
|
return is_password_usable(self.password)
|
||||||
|
|
||||||
def get_full_name(self):
|
def get_full_name(self):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError('subclasses of AbstractBaseUser must provide a get_full_name() method')
|
||||||
|
|
||||||
def get_short_name(self):
|
def get_short_name(self):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError('subclasses of AbstractBaseUser must provide a get_short_name() method.')
|
||||||
|
|
||||||
|
|
||||||
# A few helper functions for common logic between User and AnonymousUser.
|
# A few helper functions for common logic between User and AnonymousUser.
|
||||||
|
@ -441,16 +441,16 @@ class AnonymousUser(object):
|
||||||
return 1 # instances always return the same hash value
|
return 1 # instances always return the same hash value
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
raise NotImplementedError
|
raise NotImplementedError("Django doesn't provide a DB representation for AnonymousUser.")
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
raise NotImplementedError
|
raise NotImplementedError("Django doesn't provide a DB representation for AnonymousUser.")
|
||||||
|
|
||||||
def set_password(self, raw_password):
|
def set_password(self, raw_password):
|
||||||
raise NotImplementedError
|
raise NotImplementedError("Django doesn't provide a DB representation for AnonymousUser.")
|
||||||
|
|
||||||
def check_password(self, raw_password):
|
def check_password(self, raw_password):
|
||||||
raise NotImplementedError
|
raise NotImplementedError("Django doesn't provide a DB representation for AnonymousUser.")
|
||||||
|
|
||||||
def _get_groups(self):
|
def _get_groups(self):
|
||||||
return self._groups
|
return self._groups
|
||||||
|
|
|
@ -112,7 +112,7 @@ class BaseCommentNode(six.with_metaclass(RenameBaseCommentNodeMethods, template.
|
||||||
|
|
||||||
def get_context_value_from_queryset(self, context, qs):
|
def get_context_value_from_queryset(self, context, qs):
|
||||||
"""Subclasses should override this."""
|
"""Subclasses should override this."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of BaseCommentNode must provide a get_context_value_from_queryset() method')
|
||||||
|
|
||||||
class CommentListNode(BaseCommentNode):
|
class CommentListNode(BaseCommentNode):
|
||||||
"""Insert a list of comments into the context."""
|
"""Insert a list of comments into the context."""
|
||||||
|
@ -338,4 +338,3 @@ def get_comment_permalink(comment, anchor_pattern=None):
|
||||||
if anchor_pattern:
|
if anchor_pattern:
|
||||||
return comment.get_absolute_url(anchor_pattern)
|
return comment.get_absolute_url(anchor_pattern)
|
||||||
return comment.get_absolute_url()
|
return comment.get_absolute_url()
|
||||||
|
|
||||||
|
|
|
@ -101,7 +101,7 @@ class BaseSpatialOperations(object):
|
||||||
Returns the database column type for the geometry field on
|
Returns the database column type for the geometry field on
|
||||||
the spatial backend.
|
the spatial backend.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of BaseSpatialOperations must provide a geo_db_type() method')
|
||||||
|
|
||||||
def get_distance(self, f, value, lookup_type):
|
def get_distance(self, f, value, lookup_type):
|
||||||
"""
|
"""
|
||||||
|
@ -117,7 +117,7 @@ class BaseSpatialOperations(object):
|
||||||
stored procedure call to the transformation function of the spatial
|
stored procedure call to the transformation function of the spatial
|
||||||
backend.
|
backend.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of BaseSpatialOperations must provide a geo_db_placeholder() method')
|
||||||
|
|
||||||
def get_expression_column(self, evaluator):
|
def get_expression_column(self, evaluator):
|
||||||
"""
|
"""
|
||||||
|
@ -134,14 +134,14 @@ class BaseSpatialOperations(object):
|
||||||
raise NotImplementedError('Aggregate support not implemented for this spatial backend.')
|
raise NotImplementedError('Aggregate support not implemented for this spatial backend.')
|
||||||
|
|
||||||
def spatial_lookup_sql(self, lvalue, lookup_type, value, field):
|
def spatial_lookup_sql(self, lvalue, lookup_type, value, field):
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of BaseSpatialOperations must a provide spatial_lookup_sql() method')
|
||||||
|
|
||||||
# Routines for getting the OGC-compliant models.
|
# Routines for getting the OGC-compliant models.
|
||||||
def geometry_columns(self):
|
def geometry_columns(self):
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of BaseSpatialOperations must a provide geometry_columns() method')
|
||||||
|
|
||||||
def spatial_ref_sys(self):
|
def spatial_ref_sys(self):
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of BaseSpatialOperations must a provide spatial_ref_sys() method')
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class SpatialRefSysMixin(object):
|
class SpatialRefSysMixin(object):
|
||||||
|
|
|
@ -25,9 +25,10 @@ class SpatiaLiteIntrospection(DatabaseIntrospection):
|
||||||
cursor = self.connection.cursor()
|
cursor = self.connection.cursor()
|
||||||
try:
|
try:
|
||||||
# Querying the `geometry_columns` table to get additional metadata.
|
# Querying the `geometry_columns` table to get additional metadata.
|
||||||
cursor.execute('SELECT "coord_dimension", "srid", "type" '
|
type_col = 'type' if self.connection.ops.spatial_version < (4, 0, 0) else 'geometry_type'
|
||||||
'FROM "geometry_columns" '
|
cursor.execute('SELECT coord_dimension, srid, %s '
|
||||||
'WHERE "f_table_name"=%s AND "f_geometry_column"=%s',
|
'FROM geometry_columns '
|
||||||
|
'WHERE f_table_name=%%s AND f_geometry_column=%%s' % type_col,
|
||||||
(table_name, geo_col))
|
(table_name, geo_col))
|
||||||
row = cursor.fetchone()
|
row = cursor.fetchone()
|
||||||
if not row:
|
if not row:
|
||||||
|
|
|
@ -9,5 +9,6 @@ class AllOGRFields(models.Model):
|
||||||
f_datetime = models.DateTimeField()
|
f_datetime = models.DateTimeField()
|
||||||
f_time = models.TimeField()
|
f_time = models.TimeField()
|
||||||
geom = models.PolygonField()
|
geom = models.PolygonField()
|
||||||
|
point = models.PointField()
|
||||||
|
|
||||||
objects = models.GeoManager()
|
objects = models.GeoManager()
|
||||||
|
|
|
@ -3,11 +3,13 @@ from __future__ import unicode_literals
|
||||||
import os
|
import os
|
||||||
from unittest import skipUnless
|
from unittest import skipUnless
|
||||||
|
|
||||||
|
from django.core.management import call_command
|
||||||
from django.db import connections
|
from django.db import connections
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.contrib.gis.gdal import HAS_GDAL
|
from django.contrib.gis.gdal import HAS_GDAL
|
||||||
from django.contrib.gis.geometry.test_data import TEST_DATA
|
from django.contrib.gis.geometry.test_data import TEST_DATA
|
||||||
from django.contrib.gis.tests.utils import HAS_SPATIAL_DB
|
from django.contrib.gis.tests.utils import HAS_SPATIAL_DB
|
||||||
|
from django.utils.six import StringIO
|
||||||
|
|
||||||
if HAS_GDAL:
|
if HAS_GDAL:
|
||||||
from django.contrib.gis.gdal import Driver
|
from django.contrib.gis.gdal import Driver
|
||||||
|
@ -16,6 +18,22 @@ if HAS_GDAL:
|
||||||
from .models import AllOGRFields
|
from .models import AllOGRFields
|
||||||
|
|
||||||
|
|
||||||
|
@skipUnless(HAS_GDAL and HAS_SPATIAL_DB, "GDAL and spatial db are required.")
|
||||||
|
class InspectDbTests(TestCase):
|
||||||
|
def test_geom_columns(self):
|
||||||
|
"""
|
||||||
|
Test the geo-enabled inspectdb command.
|
||||||
|
"""
|
||||||
|
out = StringIO()
|
||||||
|
call_command('inspectdb',
|
||||||
|
table_name_filter=lambda tn:tn.startswith('inspectapp_'),
|
||||||
|
stdout=out)
|
||||||
|
output = out.getvalue()
|
||||||
|
self.assertIn('geom = models.PolygonField()', output)
|
||||||
|
self.assertIn('point = models.PointField()', output)
|
||||||
|
self.assertIn('objects = models.GeoManager()', output)
|
||||||
|
|
||||||
|
|
||||||
@skipUnless(HAS_GDAL and HAS_SPATIAL_DB, "GDAL and spatial db are required.")
|
@skipUnless(HAS_GDAL and HAS_SPATIAL_DB, "GDAL and spatial db are required.")
|
||||||
class OGRInspectTest(TestCase):
|
class OGRInspectTest(TestCase):
|
||||||
maxDiff = 1024
|
maxDiff = 1024
|
||||||
|
|
|
@ -14,10 +14,9 @@ from django.template import Template, Context, defaultfilters
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test.utils import override_settings
|
from django.test.utils import override_settings
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from django.utils.timezone import utc
|
from django.utils.timezone import utc, get_fixed_timezone
|
||||||
from django.utils import translation
|
from django.utils import translation
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.utils import tzinfo
|
|
||||||
|
|
||||||
from i18n import TransRealMixin
|
from i18n import TransRealMixin
|
||||||
|
|
||||||
|
@ -153,8 +152,8 @@ class HumanizeTests(TransRealMixin, TestCase):
|
||||||
|
|
||||||
def test_naturalday_tz(self):
|
def test_naturalday_tz(self):
|
||||||
today = datetime.date.today()
|
today = datetime.date.today()
|
||||||
tz_one = tzinfo.FixedOffset(datetime.timedelta(hours=-12))
|
tz_one = get_fixed_timezone(-720)
|
||||||
tz_two = tzinfo.FixedOffset(datetime.timedelta(hours=12))
|
tz_two = get_fixed_timezone(720)
|
||||||
|
|
||||||
# Can be today or yesterday
|
# Can be today or yesterday
|
||||||
date_one = datetime.datetime(today.year, today.month, today.day, tzinfo=tz_one)
|
date_one = datetime.datetime(today.year, today.month, today.day, tzinfo=tz_one)
|
||||||
|
|
|
@ -105,7 +105,7 @@ class BaseStorage(object):
|
||||||
just containing no messages) then ``None`` should be returned in
|
just containing no messages) then ``None`` should be returned in
|
||||||
place of ``messages``.
|
place of ``messages``.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError('subclasses of BaseStorage must provide a _get() method')
|
||||||
|
|
||||||
def _store(self, messages, response, *args, **kwargs):
|
def _store(self, messages, response, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
@ -116,7 +116,7 @@ class BaseStorage(object):
|
||||||
|
|
||||||
**This method must be implemented by a subclass.**
|
**This method must be implemented by a subclass.**
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError('subclasses of BaseStorage must provide a _store() method')
|
||||||
|
|
||||||
def _prepare_messages(self, messages):
|
def _prepare_messages(self, messages):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -284,7 +284,7 @@ class SessionBase(object):
|
||||||
"""
|
"""
|
||||||
Returns True if the given session_key already exists.
|
Returns True if the given session_key already exists.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of SessionBase must provide an exists() method')
|
||||||
|
|
||||||
def create(self):
|
def create(self):
|
||||||
"""
|
"""
|
||||||
|
@ -292,7 +292,7 @@ class SessionBase(object):
|
||||||
a unique key and will have saved the result once (with empty data)
|
a unique key and will have saved the result once (with empty data)
|
||||||
before the method returns.
|
before the method returns.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of SessionBase must provide a create() method')
|
||||||
|
|
||||||
def save(self, must_create=False):
|
def save(self, must_create=False):
|
||||||
"""
|
"""
|
||||||
|
@ -300,20 +300,20 @@ class SessionBase(object):
|
||||||
is created (otherwise a CreateError exception is raised). Otherwise,
|
is created (otherwise a CreateError exception is raised). Otherwise,
|
||||||
save() can update an existing object with the same key.
|
save() can update an existing object with the same key.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of SessionBase must provide a save() method')
|
||||||
|
|
||||||
def delete(self, session_key=None):
|
def delete(self, session_key=None):
|
||||||
"""
|
"""
|
||||||
Deletes the session data under this key. If the key is None, the
|
Deletes the session data under this key. If the key is None, the
|
||||||
current session key value is used.
|
current session key value is used.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of SessionBase must provide a delete() method')
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
"""
|
"""
|
||||||
Loads the session data and returns a dictionary.
|
Loads the session data and returns a dictionary.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of SessionBase must provide a load() method')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def clear_expired(cls):
|
def clear_expired(cls):
|
||||||
|
@ -324,4 +324,4 @@ class SessionBase(object):
|
||||||
NotImplementedError. If it isn't necessary, because the backend has
|
NotImplementedError. If it isn't necessary, because the backend has
|
||||||
a built-in expiration mechanism, it should be a no-op.
|
a built-in expiration mechanism, it should be a no-op.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('This backend does not support clear_expired().')
|
||||||
|
|
|
@ -28,7 +28,7 @@ class BaseFinder(object):
|
||||||
the first found file path will be returned; if set
|
the first found file path will be returned; if set
|
||||||
to ``True`` a list of all found files paths is returned.
|
to ``True`` a list of all found files paths is returned.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError('subclasses of BaseFinder must provide a find() method')
|
||||||
|
|
||||||
def list(self, ignore_patterns):
|
def list(self, ignore_patterns):
|
||||||
"""
|
"""
|
||||||
|
@ -36,7 +36,7 @@ class BaseFinder(object):
|
||||||
a two item iterable consisting of the relative path and storage
|
a two item iterable consisting of the relative path and storage
|
||||||
instance.
|
instance.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError('subclasses of BaseFinder must provide a list() method')
|
||||||
|
|
||||||
|
|
||||||
class FileSystemFinder(BaseFinder):
|
class FileSystemFinder(BaseFinder):
|
||||||
|
|
|
@ -7,12 +7,12 @@ from django.contrib.sites.models import get_current_site
|
||||||
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
|
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
|
||||||
from django.http import HttpResponse, Http404
|
from django.http import HttpResponse, Http404
|
||||||
from django.template import loader, TemplateDoesNotExist, RequestContext
|
from django.template import loader, TemplateDoesNotExist, RequestContext
|
||||||
from django.utils import feedgenerator, tzinfo
|
from django.utils import feedgenerator
|
||||||
from django.utils.encoding import force_text, iri_to_uri, smart_text
|
from django.utils.encoding import force_text, iri_to_uri, smart_text
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from django.utils.http import http_date
|
from django.utils.http import http_date
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
from django.utils.timezone import is_naive
|
from django.utils.timezone import get_default_timezone, is_naive, make_aware
|
||||||
|
|
||||||
|
|
||||||
def add_domain(domain, url, secure=False):
|
def add_domain(domain, url, secure=False):
|
||||||
|
@ -186,15 +186,15 @@ class Feed(object):
|
||||||
else:
|
else:
|
||||||
author_email = author_link = None
|
author_email = author_link = None
|
||||||
|
|
||||||
|
tz = get_default_timezone()
|
||||||
|
|
||||||
pubdate = self.__get_dynamic_attr('item_pubdate', item)
|
pubdate = self.__get_dynamic_attr('item_pubdate', item)
|
||||||
if pubdate and is_naive(pubdate):
|
if pubdate and is_naive(pubdate):
|
||||||
ltz = tzinfo.LocalTimezone(pubdate)
|
pubdate = make_aware(pubdate, tz)
|
||||||
pubdate = pubdate.replace(tzinfo=ltz)
|
|
||||||
|
|
||||||
updateddate = self.__get_dynamic_attr('item_updateddate', item)
|
updateddate = self.__get_dynamic_attr('item_updateddate', item)
|
||||||
if updateddate and is_naive(updateddate):
|
if updateddate and is_naive(updateddate):
|
||||||
ltz = tzinfo.LocalTimezone(updateddate)
|
updateddate = make_aware(updateddate, tz)
|
||||||
updateddate = updateddate.replace(tzinfo=ltz)
|
|
||||||
|
|
||||||
feed.add_item(
|
feed.add_item(
|
||||||
title = title,
|
title = title,
|
||||||
|
|
|
@ -96,27 +96,27 @@ class BaseCache(object):
|
||||||
|
|
||||||
Returns True if the value was stored, False otherwise.
|
Returns True if the value was stored, False otherwise.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of BaseCache must provide an add() method')
|
||||||
|
|
||||||
def get(self, key, default=None, version=None):
|
def get(self, key, default=None, version=None):
|
||||||
"""
|
"""
|
||||||
Fetch a given key from the cache. If the key does not exist, return
|
Fetch a given key from the cache. If the key does not exist, return
|
||||||
default, which itself defaults to None.
|
default, which itself defaults to None.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of BaseCache must provide a get() method')
|
||||||
|
|
||||||
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
||||||
"""
|
"""
|
||||||
Set a value in the cache. If timeout is given, that timeout will be
|
Set a value in the cache. If timeout is given, that timeout will be
|
||||||
used for the key; otherwise the default cache timeout will be used.
|
used for the key; otherwise the default cache timeout will be used.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of BaseCache must provide a set() method')
|
||||||
|
|
||||||
def delete(self, key, version=None):
|
def delete(self, key, version=None):
|
||||||
"""
|
"""
|
||||||
Delete a key from the cache, failing silently.
|
Delete a key from the cache, failing silently.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of BaseCache must provide a delete() method')
|
||||||
|
|
||||||
def get_many(self, keys, version=None):
|
def get_many(self, keys, version=None):
|
||||||
"""
|
"""
|
||||||
|
@ -190,7 +190,7 @@ class BaseCache(object):
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
"""Remove *all* values from the cache at once."""
|
"""Remove *all* values from the cache at once."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of BaseCache must provide a clear() method')
|
||||||
|
|
||||||
def validate_key(self, key):
|
def validate_key(self, key):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -92,55 +92,55 @@ class Storage(object):
|
||||||
"""
|
"""
|
||||||
Deletes the specified file from the storage system.
|
Deletes the specified file from the storage system.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError('subclasses of Storage must provide a delete() method')
|
||||||
|
|
||||||
def exists(self, name):
|
def exists(self, name):
|
||||||
"""
|
"""
|
||||||
Returns True if a file referened by the given name already exists in the
|
Returns True if a file referened by the given name already exists in the
|
||||||
storage system, or False if the name is available for a new file.
|
storage system, or False if the name is available for a new file.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError('subclasses of Storage must provide a exists() method')
|
||||||
|
|
||||||
def listdir(self, path):
|
def listdir(self, path):
|
||||||
"""
|
"""
|
||||||
Lists the contents of the specified path, returning a 2-tuple of lists;
|
Lists the contents of the specified path, returning a 2-tuple of lists;
|
||||||
the first item being directories, the second item being files.
|
the first item being directories, the second item being files.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError('subclasses of Storage must provide a listdir() method')
|
||||||
|
|
||||||
def size(self, name):
|
def size(self, name):
|
||||||
"""
|
"""
|
||||||
Returns the total size, in bytes, of the file specified by name.
|
Returns the total size, in bytes, of the file specified by name.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError('subclasses of Storage must provide a size() method')
|
||||||
|
|
||||||
def url(self, name):
|
def url(self, name):
|
||||||
"""
|
"""
|
||||||
Returns an absolute URL where the file's contents can be accessed
|
Returns an absolute URL where the file's contents can be accessed
|
||||||
directly by a Web browser.
|
directly by a Web browser.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError('subclasses of Storage must provide a url() method')
|
||||||
|
|
||||||
def accessed_time(self, name):
|
def accessed_time(self, name):
|
||||||
"""
|
"""
|
||||||
Returns the last accessed time (as datetime object) of the file
|
Returns the last accessed time (as datetime object) of the file
|
||||||
specified by name.
|
specified by name.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError('subclasses of Storage must provide an accessed_time() method')
|
||||||
|
|
||||||
def created_time(self, name):
|
def created_time(self, name):
|
||||||
"""
|
"""
|
||||||
Returns the creation time (as datetime object) of the file
|
Returns the creation time (as datetime object) of the file
|
||||||
specified by name.
|
specified by name.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError('subclasses of Storage must provide a created_time() method')
|
||||||
|
|
||||||
def modified_time(self, name):
|
def modified_time(self, name):
|
||||||
"""
|
"""
|
||||||
Returns the last modified time (as datetime object) of the file
|
Returns the last modified time (as datetime object) of the file
|
||||||
specified by name.
|
specified by name.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError('subclasses of Storage must provide a modified_time() method')
|
||||||
|
|
||||||
class FileSystemStorage(Storage):
|
class FileSystemStorage(Storage):
|
||||||
"""
|
"""
|
||||||
|
@ -215,6 +215,7 @@ class FileSystemStorage(Storage):
|
||||||
_file = os.fdopen(fd, mode)
|
_file = os.fdopen(fd, mode)
|
||||||
_file.write(chunk)
|
_file.write(chunk)
|
||||||
finally:
|
finally:
|
||||||
|
content.close()
|
||||||
locks.unlock(fd)
|
locks.unlock(fd)
|
||||||
if _file is not None:
|
if _file is not None:
|
||||||
_file.close()
|
_file.close()
|
||||||
|
|
|
@ -46,6 +46,7 @@ class UploadedFile(File):
|
||||||
# File names longer than 255 characters can cause problems on older OSes.
|
# File names longer than 255 characters can cause problems on older OSes.
|
||||||
if len(name) > 255:
|
if len(name) > 255:
|
||||||
name, ext = os.path.splitext(name)
|
name, ext = os.path.splitext(name)
|
||||||
|
ext = ext[:255]
|
||||||
name = name[:255 - len(ext)] + ext
|
name = name[:255 - len(ext)] + ext
|
||||||
|
|
||||||
self._name = name
|
self._name = name
|
||||||
|
|
|
@ -104,7 +104,7 @@ class FileUploadHandler(object):
|
||||||
Receive data from the streamed upload parser. ``start`` is the position
|
Receive data from the streamed upload parser. ``start`` is the position
|
||||||
in the file of the chunk.
|
in the file of the chunk.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError('subclasses of FileUploadHandler must provide a recieve_data_chunk() method')
|
||||||
|
|
||||||
def file_complete(self, file_size):
|
def file_complete(self, file_size):
|
||||||
"""
|
"""
|
||||||
|
@ -113,7 +113,7 @@ class FileUploadHandler(object):
|
||||||
|
|
||||||
Subclasses should return a valid ``UploadedFile`` object.
|
Subclasses should return a valid ``UploadedFile`` object.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError('subclasses of FileUploadHandler must provide a file_complete() method')
|
||||||
|
|
||||||
def upload_complete(self):
|
def upload_complete(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -36,4 +36,4 @@ class BaseEmailBackend(object):
|
||||||
Sends one or more EmailMessage objects and returns the number of email
|
Sends one or more EmailMessage objects and returns the number of email
|
||||||
messages sent.
|
messages sent.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of BaseEmailBackend must override send_messages() method')
|
||||||
|
|
|
@ -325,7 +325,7 @@ class BaseCommand(object):
|
||||||
this method.
|
this method.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError('subclasses of BaseCommand must provide a handle() method')
|
||||||
|
|
||||||
|
|
||||||
class AppCommand(BaseCommand):
|
class AppCommand(BaseCommand):
|
||||||
|
@ -361,7 +361,7 @@ class AppCommand(BaseCommand):
|
||||||
the command line.
|
the command line.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError('subclasses of AppCommand must provide a handle_app() method')
|
||||||
|
|
||||||
|
|
||||||
class LabelCommand(BaseCommand):
|
class LabelCommand(BaseCommand):
|
||||||
|
@ -397,7 +397,7 @@ class LabelCommand(BaseCommand):
|
||||||
string as given on the command line.
|
string as given on the command line.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError('subclasses of LabelCommand must provide a handle_label() method')
|
||||||
|
|
||||||
|
|
||||||
class NoArgsCommand(BaseCommand):
|
class NoArgsCommand(BaseCommand):
|
||||||
|
@ -423,4 +423,4 @@ class NoArgsCommand(BaseCommand):
|
||||||
Perform this command's actions.
|
Perform this command's actions.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError('subclasses of NoArgsCommand must provide a handle_noargs() method')
|
||||||
|
|
|
@ -104,8 +104,11 @@ class Command(NoArgsCommand):
|
||||||
|
|
||||||
# Don't output 'id = meta.AutoField(primary_key=True)', because
|
# Don't output 'id = meta.AutoField(primary_key=True)', because
|
||||||
# that's assumed if it doesn't exist.
|
# that's assumed if it doesn't exist.
|
||||||
if att_name == 'id' and field_type == 'AutoField(' and extra_params == {'primary_key': True}:
|
if att_name == 'id' and extra_params == {'primary_key': True}:
|
||||||
continue
|
if field_type == 'AutoField(':
|
||||||
|
continue
|
||||||
|
elif field_type == 'IntegerField(' and not connection.features.can_introspect_autofield:
|
||||||
|
comment_notes.append('AutoField?')
|
||||||
|
|
||||||
# Add 'null' and 'blank', if the 'null_ok' flag was present in the
|
# Add 'null' and 'blank', if the 'null_ok' flag was present in the
|
||||||
# table description.
|
# table description.
|
||||||
|
@ -117,7 +120,12 @@ class Command(NoArgsCommand):
|
||||||
if not field_type in ('TextField(', 'CharField('):
|
if not field_type in ('TextField(', 'CharField('):
|
||||||
extra_params['null'] = True
|
extra_params['null'] = True
|
||||||
|
|
||||||
field_desc = '%s = models.%s' % (att_name, field_type)
|
field_desc = '%s = %s%s' % (
|
||||||
|
att_name,
|
||||||
|
# Custom fields will have a dotted path
|
||||||
|
'' if '.' in field_type else 'models.',
|
||||||
|
field_type,
|
||||||
|
)
|
||||||
if extra_params:
|
if extra_params:
|
||||||
if not field_desc.endswith('('):
|
if not field_desc.endswith('('):
|
||||||
field_desc += ', '
|
field_desc += ', '
|
||||||
|
|
|
@ -65,7 +65,7 @@ class Serializer(object):
|
||||||
"""
|
"""
|
||||||
Called when serializing of the queryset starts.
|
Called when serializing of the queryset starts.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of Serializer must provide a start_serialization() method')
|
||||||
|
|
||||||
def end_serialization(self):
|
def end_serialization(self):
|
||||||
"""
|
"""
|
||||||
|
@ -77,7 +77,7 @@ class Serializer(object):
|
||||||
"""
|
"""
|
||||||
Called when serializing of an object starts.
|
Called when serializing of an object starts.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of Serializer must provide a start_object() method')
|
||||||
|
|
||||||
def end_object(self, obj):
|
def end_object(self, obj):
|
||||||
"""
|
"""
|
||||||
|
@ -89,19 +89,19 @@ class Serializer(object):
|
||||||
"""
|
"""
|
||||||
Called to handle each individual (non-relational) field on an object.
|
Called to handle each individual (non-relational) field on an object.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of Serializer must provide an handle_field() method')
|
||||||
|
|
||||||
def handle_fk_field(self, obj, field):
|
def handle_fk_field(self, obj, field):
|
||||||
"""
|
"""
|
||||||
Called to handle a ForeignKey field.
|
Called to handle a ForeignKey field.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of Serializer must provide an handle_fk_field() method')
|
||||||
|
|
||||||
def handle_m2m_field(self, obj, field):
|
def handle_m2m_field(self, obj, field):
|
||||||
"""
|
"""
|
||||||
Called to handle a ManyToManyField.
|
Called to handle a ManyToManyField.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of Serializer must provide an handle_m2m_field() method')
|
||||||
|
|
||||||
def getvalue(self):
|
def getvalue(self):
|
||||||
"""
|
"""
|
||||||
|
@ -135,7 +135,7 @@ class Deserializer(six.Iterator):
|
||||||
|
|
||||||
def __next__(self):
|
def __next__(self):
|
||||||
"""Iteration iterface -- return the next item in the stream"""
|
"""Iteration iterface -- return the next item in the stream"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of Deserializer must provide a __next__() method')
|
||||||
|
|
||||||
class DeserializedObject(object):
|
class DeserializedObject(object):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -84,19 +84,19 @@ class BaseDatabaseWrapper(object):
|
||||||
|
|
||||||
def get_connection_params(self):
|
def get_connection_params(self):
|
||||||
"""Returns a dict of parameters suitable for get_new_connection."""
|
"""Returns a dict of parameters suitable for get_new_connection."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of BaseDatabaseWrapper may require a get_connection_params() method')
|
||||||
|
|
||||||
def get_new_connection(self, conn_params):
|
def get_new_connection(self, conn_params):
|
||||||
"""Opens a connection to the database."""
|
"""Opens a connection to the database."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of BaseDatabaseWrapper may require a get_new_connection() method')
|
||||||
|
|
||||||
def init_connection_state(self):
|
def init_connection_state(self):
|
||||||
"""Initializes the database connection settings."""
|
"""Initializes the database connection settings."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of BaseDatabaseWrapper may require an init_connection_state() method')
|
||||||
|
|
||||||
def create_cursor(self):
|
def create_cursor(self):
|
||||||
"""Creates a cursor. Assumes that a connection is established."""
|
"""Creates a cursor. Assumes that a connection is established."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of BaseDatabaseWrapper may require a create_cursor() method')
|
||||||
|
|
||||||
##### Backend-specific methods for creating connections #####
|
##### Backend-specific methods for creating connections #####
|
||||||
|
|
||||||
|
@ -262,7 +262,7 @@ class BaseDatabaseWrapper(object):
|
||||||
"""
|
"""
|
||||||
Backend-specific implementation to enable or disable autocommit.
|
Backend-specific implementation to enable or disable autocommit.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of BaseDatabaseWrapper may require a _set_autocommit() method')
|
||||||
|
|
||||||
##### Generic transaction management methods #####
|
##### Generic transaction management methods #####
|
||||||
|
|
||||||
|
@ -440,7 +440,7 @@ class BaseDatabaseWrapper(object):
|
||||||
Tests if the database connection is usable.
|
Tests if the database connection is usable.
|
||||||
This function may assume that self.connection is not None.
|
This function may assume that self.connection is not None.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of BaseDatabaseWrapper may require an is_usable() method')
|
||||||
|
|
||||||
def close_if_unusable_or_obsolete(self):
|
def close_if_unusable_or_obsolete(self):
|
||||||
"""
|
"""
|
||||||
|
@ -519,11 +519,11 @@ class BaseDatabaseWrapper(object):
|
||||||
"""
|
"""
|
||||||
Only required when autocommits_when_autocommit_is_off = True.
|
Only required when autocommits_when_autocommit_is_off = True.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of BaseDatabaseWrapper may require a _start_transaction_under_autocommit() method')
|
||||||
|
|
||||||
def schema_editor(self, *args, **kwargs):
|
def schema_editor(self, *args, **kwargs):
|
||||||
"Returns a new instance of this backend's SchemaEditor"
|
"Returns a new instance of this backend's SchemaEditor"
|
||||||
raise NotImplementedError()
|
raise NotImplementedError('subclasses of BaseDatabaseWrapper may require a schema_editor() method')
|
||||||
|
|
||||||
|
|
||||||
class BaseDatabaseFeatures(object):
|
class BaseDatabaseFeatures(object):
|
||||||
|
@ -627,6 +627,9 @@ class BaseDatabaseFeatures(object):
|
||||||
# which can't do it for MyISAM tables
|
# which can't do it for MyISAM tables
|
||||||
can_introspect_foreign_keys = True
|
can_introspect_foreign_keys = True
|
||||||
|
|
||||||
|
# Can the backend introspect an AutoField, instead of an IntegerField?
|
||||||
|
can_introspect_autofield = False
|
||||||
|
|
||||||
# Support for the DISTINCT ON clause
|
# Support for the DISTINCT ON clause
|
||||||
can_distinct_on_fields = False
|
can_distinct_on_fields = False
|
||||||
|
|
||||||
|
@ -741,13 +744,13 @@ class BaseDatabaseOperations(object):
|
||||||
Given a lookup_type of 'year', 'month' or 'day', returns the SQL that
|
Given a lookup_type of 'year', 'month' or 'day', returns the SQL that
|
||||||
extracts a value from the given date field field_name.
|
extracts a value from the given date field field_name.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError('subclasses of BaseDatabaseOperations may require a date_extract_sql() method')
|
||||||
|
|
||||||
def date_interval_sql(self, sql, connector, timedelta):
|
def date_interval_sql(self, sql, connector, timedelta):
|
||||||
"""
|
"""
|
||||||
Implements the date interval functionality for expressions
|
Implements the date interval functionality for expressions
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError('subclasses of BaseDatabaseOperations may require a date_interval_sql() method')
|
||||||
|
|
||||||
def date_trunc_sql(self, lookup_type, field_name):
|
def date_trunc_sql(self, lookup_type, field_name):
|
||||||
"""
|
"""
|
||||||
|
@ -755,7 +758,7 @@ class BaseDatabaseOperations(object):
|
||||||
truncates the given date field field_name to a date object with only
|
truncates the given date field field_name to a date object with only
|
||||||
the given specificity.
|
the given specificity.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError('subclasses of BaseDatabaseOperations may require a datetrunc_sql() method')
|
||||||
|
|
||||||
def datetime_cast_sql(self):
|
def datetime_cast_sql(self):
|
||||||
"""
|
"""
|
||||||
|
@ -772,7 +775,7 @@ class BaseDatabaseOperations(object):
|
||||||
'second', returns the SQL that extracts a value from the given
|
'second', returns the SQL that extracts a value from the given
|
||||||
datetime field field_name, and a tuple of parameters.
|
datetime field field_name, and a tuple of parameters.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError('subclasses of BaseDatabaseOperations may require a datetime_extract_sql() method')
|
||||||
|
|
||||||
def datetime_trunc_sql(self, lookup_type, field_name, tzname):
|
def datetime_trunc_sql(self, lookup_type, field_name, tzname):
|
||||||
"""
|
"""
|
||||||
|
@ -781,7 +784,7 @@ class BaseDatabaseOperations(object):
|
||||||
field_name to a datetime object with only the given specificity, and
|
field_name to a datetime object with only the given specificity, and
|
||||||
a tuple of parameters.
|
a tuple of parameters.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError('subclasses of BaseDatabaseOperations may require a datetime_trunk_sql() method')
|
||||||
|
|
||||||
def deferrable_sql(self):
|
def deferrable_sql(self):
|
||||||
"""
|
"""
|
||||||
|
@ -916,7 +919,7 @@ class BaseDatabaseOperations(object):
|
||||||
Returns the value to use for the LIMIT when we are wanting "LIMIT
|
Returns the value to use for the LIMIT when we are wanting "LIMIT
|
||||||
infinity". Returns None if the limit clause can be omitted in this case.
|
infinity". Returns None if the limit clause can be omitted in this case.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of BaseDatabaseOperations may require a no_limit_value() method')
|
||||||
|
|
||||||
def pk_default_value(self):
|
def pk_default_value(self):
|
||||||
"""
|
"""
|
||||||
|
@ -956,7 +959,7 @@ class BaseDatabaseOperations(object):
|
||||||
Returns a quoted version of the given table, index or column name. Does
|
Returns a quoted version of the given table, index or column name. Does
|
||||||
not quote the given name if it's already been quoted.
|
not quote the given name if it's already been quoted.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError('subclasses of BaseDatabaseOperations may require a quote_name() method')
|
||||||
|
|
||||||
def quote_parameter(self, value):
|
def quote_parameter(self, value):
|
||||||
"""
|
"""
|
||||||
|
@ -982,7 +985,7 @@ class BaseDatabaseOperations(object):
|
||||||
If the feature is not supported (or part of it is not supported), a
|
If the feature is not supported (or part of it is not supported), a
|
||||||
NotImplementedError exception can be raised.
|
NotImplementedError exception can be raised.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of BaseDatabaseOperations may require a regex_lookup() method')
|
||||||
|
|
||||||
def savepoint_create_sql(self, sid):
|
def savepoint_create_sql(self, sid):
|
||||||
"""
|
"""
|
||||||
|
@ -1028,7 +1031,7 @@ class BaseDatabaseOperations(object):
|
||||||
to tables with foreign keys pointing the tables being truncated.
|
to tables with foreign keys pointing the tables being truncated.
|
||||||
PostgreSQL requires a cascade even if these tables are empty.
|
PostgreSQL requires a cascade even if these tables are empty.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError('subclasses of BaseDatabaseOperations must provide a sql_flush() method')
|
||||||
|
|
||||||
def sequence_reset_by_name_sql(self, style, sequences):
|
def sequence_reset_by_name_sql(self, style, sequences):
|
||||||
"""
|
"""
|
||||||
|
@ -1245,7 +1248,7 @@ class BaseDatabaseIntrospection(object):
|
||||||
Returns an unsorted list of names of all tables that exist in the
|
Returns an unsorted list of names of all tables that exist in the
|
||||||
database.
|
database.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of BaseDatabaseIntrospection may require a get_table_list() method')
|
||||||
|
|
||||||
def django_table_names(self, only_existing=False):
|
def django_table_names(self, only_existing=False):
|
||||||
"""
|
"""
|
||||||
|
@ -1322,7 +1325,7 @@ class BaseDatabaseIntrospection(object):
|
||||||
Backends can override this to return a list of (column_name, referenced_table_name,
|
Backends can override this to return a list of (column_name, referenced_table_name,
|
||||||
referenced_column_name) for all key columns in given table.
|
referenced_column_name) for all key columns in given table.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of BaseDatabaseIntrospection may require a get_key_columns() method')
|
||||||
|
|
||||||
def get_primary_key_column(self, cursor, table_name):
|
def get_primary_key_column(self, cursor, table_name):
|
||||||
"""
|
"""
|
||||||
|
@ -1342,7 +1345,7 @@ class BaseDatabaseIntrospection(object):
|
||||||
|
|
||||||
Only single-column indexes are introspected.
|
Only single-column indexes are introspected.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of BaseDatabaseIntrospection may require a get_indexes() method')
|
||||||
|
|
||||||
def get_constraints(self, cursor, table_name):
|
def get_constraints(self, cursor, table_name):
|
||||||
"""
|
"""
|
||||||
|
@ -1361,7 +1364,7 @@ class BaseDatabaseIntrospection(object):
|
||||||
Some backends may return special constraint names that don't exist
|
Some backends may return special constraint names that don't exist
|
||||||
if they don't name constraints of a certain type (e.g. SQLite)
|
if they don't name constraints of a certain type (e.g. SQLite)
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of BaseDatabaseIntrospection may require a get_constraints() method')
|
||||||
|
|
||||||
|
|
||||||
class BaseDatabaseClient(object):
|
class BaseDatabaseClient(object):
|
||||||
|
@ -1378,7 +1381,7 @@ class BaseDatabaseClient(object):
|
||||||
self.connection = connection
|
self.connection = connection
|
||||||
|
|
||||||
def runshell(self):
|
def runshell(self):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError('subclasses of BaseDatabaseClient must provide a runshell() method')
|
||||||
|
|
||||||
|
|
||||||
class BaseDatabaseValidation(object):
|
class BaseDatabaseValidation(object):
|
||||||
|
|
|
@ -181,6 +181,7 @@ class DatabaseCreation(BaseDatabaseCreation):
|
||||||
IDENTIFIED BY %(password)s
|
IDENTIFIED BY %(password)s
|
||||||
DEFAULT TABLESPACE %(tblspace)s
|
DEFAULT TABLESPACE %(tblspace)s
|
||||||
TEMPORARY TABLESPACE %(tblspace_temp)s
|
TEMPORARY TABLESPACE %(tblspace_temp)s
|
||||||
|
QUOTA UNLIMITED ON %(tblspace)s
|
||||||
""",
|
""",
|
||||||
"""GRANT CONNECT, RESOURCE TO %(user)s""",
|
"""GRANT CONNECT, RESOURCE TO %(user)s""",
|
||||||
]
|
]
|
||||||
|
|
|
@ -148,7 +148,7 @@ class BaseDatabaseSchemaEditor(object):
|
||||||
"""
|
"""
|
||||||
Only used for backends which have requires_literal_defaults feature
|
Only used for backends which have requires_literal_defaults feature
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError('subclasses of BaseDatabaseSchemaEditor for backends which have requires_literal_defaults must provide a prepare_default() method')
|
||||||
|
|
||||||
def effective_default(self, field):
|
def effective_default(self, field):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -38,14 +38,14 @@ class Operation(object):
|
||||||
Takes the state from the previous migration, and mutates it
|
Takes the state from the previous migration, and mutates it
|
||||||
so that it matches what this migration would perform.
|
so that it matches what this migration would perform.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError('subclasses of Operation must provide a state_forwards() method')
|
||||||
|
|
||||||
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
||||||
"""
|
"""
|
||||||
Performs the mutation on the database schema in the normal
|
Performs the mutation on the database schema in the normal
|
||||||
(forwards) direction.
|
(forwards) direction.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError('subclasses of Operation must provide a database_forwards() method')
|
||||||
|
|
||||||
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
||||||
"""
|
"""
|
||||||
|
@ -53,7 +53,7 @@ class Operation(object):
|
||||||
direction - e.g. if this were CreateModel, it would in fact
|
direction - e.g. if this were CreateModel, it would in fact
|
||||||
drop the model's table.
|
drop the model's table.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError('subclasses of Operation must provide a database_backwards() method')
|
||||||
|
|
||||||
def describe(self):
|
def describe(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -73,6 +73,26 @@ class MigrationWriter(object):
|
||||||
raise ImportError("Cannot open migrations module %s for app %s" % (migrations_module_name, self.migration.app_label))
|
raise ImportError("Cannot open migrations module %s for app %s" % (migrations_module_name, self.migration.app_label))
|
||||||
return os.path.join(basedir, self.filename)
|
return os.path.join(basedir, self.filename)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def serialize_deconstructed(cls, path, args, kwargs):
|
||||||
|
module, name = path.rsplit(".", 1)
|
||||||
|
if module == "django.db.models":
|
||||||
|
imports = set(["from django.db import models"])
|
||||||
|
name = "models.%s" % name
|
||||||
|
else:
|
||||||
|
imports = set(["import %s" % module])
|
||||||
|
name = path
|
||||||
|
arg_strings = []
|
||||||
|
for arg in args:
|
||||||
|
arg_string, arg_imports = cls.serialize(arg)
|
||||||
|
arg_strings.append(arg_string)
|
||||||
|
imports.update(arg_imports)
|
||||||
|
for kw, arg in kwargs.items():
|
||||||
|
arg_string, arg_imports = cls.serialize(arg)
|
||||||
|
imports.update(arg_imports)
|
||||||
|
arg_strings.append("%s=%s" % (kw, arg_string))
|
||||||
|
return "%s(%s)" % (name, ", ".join(arg_strings)), imports
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def serialize(cls, value):
|
def serialize(cls, value):
|
||||||
"""
|
"""
|
||||||
|
@ -119,23 +139,7 @@ class MigrationWriter(object):
|
||||||
# Django fields
|
# Django fields
|
||||||
elif isinstance(value, models.Field):
|
elif isinstance(value, models.Field):
|
||||||
attr_name, path, args, kwargs = value.deconstruct()
|
attr_name, path, args, kwargs = value.deconstruct()
|
||||||
module, name = path.rsplit(".", 1)
|
return cls.serialize_deconstructed(path, args, kwargs)
|
||||||
if module == "django.db.models":
|
|
||||||
imports = set(["from django.db import models"])
|
|
||||||
name = "models.%s" % name
|
|
||||||
else:
|
|
||||||
imports = set(["import %s" % module])
|
|
||||||
name = path
|
|
||||||
arg_strings = []
|
|
||||||
for arg in args:
|
|
||||||
arg_string, arg_imports = cls.serialize(arg)
|
|
||||||
arg_strings.append(arg_string)
|
|
||||||
imports.update(arg_imports)
|
|
||||||
for kw, arg in kwargs.items():
|
|
||||||
arg_string, arg_imports = cls.serialize(arg)
|
|
||||||
imports.update(arg_imports)
|
|
||||||
arg_strings.append("%s=%s" % (kw, arg_string))
|
|
||||||
return "%s(%s)" % (name, ", ".join(arg_strings)), imports
|
|
||||||
# Functions
|
# Functions
|
||||||
elif isinstance(value, (types.FunctionType, types.BuiltinFunctionType)):
|
elif isinstance(value, (types.FunctionType, types.BuiltinFunctionType)):
|
||||||
# Special-cases, as these don't have im_class
|
# Special-cases, as these don't have im_class
|
||||||
|
@ -152,6 +156,8 @@ class MigrationWriter(object):
|
||||||
klass = value.im_class
|
klass = value.im_class
|
||||||
module = klass.__module__
|
module = klass.__module__
|
||||||
return "%s.%s.%s" % (module, klass.__name__, value.__name__), set(["import %s" % module])
|
return "%s.%s.%s" % (module, klass.__name__, value.__name__), set(["import %s" % module])
|
||||||
|
elif hasattr(value, 'deconstruct'):
|
||||||
|
return cls.serialize_deconstructed(*value.deconstruct())
|
||||||
elif value.__name__ == '<lambda>':
|
elif value.__name__ == '<lambda>':
|
||||||
raise ValueError("Cannot serialize function: lambda")
|
raise ValueError("Cannot serialize function: lambda")
|
||||||
elif value.__module__ is None:
|
elif value.__module__ is None:
|
||||||
|
|
|
@ -35,10 +35,12 @@ def SET(value):
|
||||||
else:
|
else:
|
||||||
def set_on_delete(collector, field, sub_objs, using):
|
def set_on_delete(collector, field, sub_objs, using):
|
||||||
collector.add_field_update(field, value, sub_objs)
|
collector.add_field_update(field, value, sub_objs)
|
||||||
|
set_on_delete.deconstruct = lambda: ('django.db.models.SET', (value,), {})
|
||||||
return set_on_delete
|
return set_on_delete
|
||||||
|
|
||||||
|
|
||||||
SET_NULL = SET(None)
|
def SET_NULL(collector, field, sub_objs, using):
|
||||||
|
collector.add_field_update(field, None, sub_objs)
|
||||||
|
|
||||||
|
|
||||||
def SET_DEFAULT(collector, field, sub_objs, using):
|
def SET_DEFAULT(collector, field, sub_objs, using):
|
||||||
|
|
|
@ -391,7 +391,7 @@ class SQLCompiler(object):
|
||||||
if not distinct or elt in select_aliases:
|
if not distinct or elt in select_aliases:
|
||||||
result.append('%s %s' % (elt, order))
|
result.append('%s %s' % (elt, order))
|
||||||
group_by.append((elt, []))
|
group_by.append((elt, []))
|
||||||
elif get_order_dir(field)[0] not in self.query.extra:
|
elif not self.query._extra or get_order_dir(field)[0] not in self.query._extra:
|
||||||
# 'col' is of the form 'field' or 'field1__field2' or
|
# 'col' is of the form 'field' or 'field1__field2' or
|
||||||
# '-field1__field2__field', etc.
|
# '-field1__field2__field', etc.
|
||||||
for table, cols, order in self.find_ordering_name(field,
|
for table, cols, order in self.find_ordering_name(field,
|
||||||
|
@ -987,7 +987,7 @@ class SQLUpdateCompiler(SQLCompiler):
|
||||||
# We need to use a sub-select in the where clause to filter on things
|
# We need to use a sub-select in the where clause to filter on things
|
||||||
# from other tables.
|
# from other tables.
|
||||||
query = self.query.clone(klass=Query)
|
query = self.query.clone(klass=Query)
|
||||||
query.extra = {}
|
query._extra = {}
|
||||||
query.select = []
|
query.select = []
|
||||||
query.add_fields([query.get_meta().pk.name])
|
query.add_fields([query.get_meta().pk.name])
|
||||||
# Recheck the count - it is possible that fiddling with the select
|
# Recheck the count - it is possible that fiddling with the select
|
||||||
|
|
|
@ -143,7 +143,10 @@ class Query(object):
|
||||||
self.select_related = False
|
self.select_related = False
|
||||||
|
|
||||||
# SQL aggregate-related attributes
|
# SQL aggregate-related attributes
|
||||||
self.aggregates = OrderedDict() # Maps alias -> SQL aggregate function
|
# The _aggregates will be an OrderedDict when used. Due to the cost
|
||||||
|
# of creating OrderedDict this attribute is created lazily (in
|
||||||
|
# self.aggregates property).
|
||||||
|
self._aggregates = None # Maps alias -> SQL aggregate function
|
||||||
self.aggregate_select_mask = None
|
self.aggregate_select_mask = None
|
||||||
self._aggregate_select_cache = None
|
self._aggregate_select_cache = None
|
||||||
|
|
||||||
|
@ -153,7 +156,9 @@ class Query(object):
|
||||||
|
|
||||||
# These are for extensions. The contents are more or less appended
|
# These are for extensions. The contents are more or less appended
|
||||||
# verbatim to the appropriate clause.
|
# verbatim to the appropriate clause.
|
||||||
self.extra = OrderedDict() # Maps col_alias -> (col_sql, params).
|
# The _extra attribute is an OrderedDict, lazily created similarly to
|
||||||
|
# .aggregates
|
||||||
|
self._extra = None # Maps col_alias -> (col_sql, params).
|
||||||
self.extra_select_mask = None
|
self.extra_select_mask = None
|
||||||
self._extra_select_cache = None
|
self._extra_select_cache = None
|
||||||
|
|
||||||
|
@ -165,6 +170,18 @@ class Query(object):
|
||||||
# load.
|
# load.
|
||||||
self.deferred_loading = (set(), True)
|
self.deferred_loading = (set(), True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def extra(self):
|
||||||
|
if self._extra is None:
|
||||||
|
self._extra = OrderedDict()
|
||||||
|
return self._extra
|
||||||
|
|
||||||
|
@property
|
||||||
|
def aggregates(self):
|
||||||
|
if self._aggregates is None:
|
||||||
|
self._aggregates = OrderedDict()
|
||||||
|
return self._aggregates
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""
|
"""
|
||||||
Returns the query as a string of SQL with the parameter values
|
Returns the query as a string of SQL with the parameter values
|
||||||
|
@ -245,7 +262,7 @@ class Query(object):
|
||||||
obj.select_for_update_nowait = self.select_for_update_nowait
|
obj.select_for_update_nowait = self.select_for_update_nowait
|
||||||
obj.select_related = self.select_related
|
obj.select_related = self.select_related
|
||||||
obj.related_select_cols = []
|
obj.related_select_cols = []
|
||||||
obj.aggregates = self.aggregates.copy()
|
obj._aggregates = self._aggregates.copy() if self._aggregates is not None else None
|
||||||
if self.aggregate_select_mask is None:
|
if self.aggregate_select_mask is None:
|
||||||
obj.aggregate_select_mask = None
|
obj.aggregate_select_mask = None
|
||||||
else:
|
else:
|
||||||
|
@ -257,7 +274,7 @@ class Query(object):
|
||||||
# used.
|
# used.
|
||||||
obj._aggregate_select_cache = None
|
obj._aggregate_select_cache = None
|
||||||
obj.max_depth = self.max_depth
|
obj.max_depth = self.max_depth
|
||||||
obj.extra = self.extra.copy()
|
obj._extra = self._extra.copy() if self._extra is not None else None
|
||||||
if self.extra_select_mask is None:
|
if self.extra_select_mask is None:
|
||||||
obj.extra_select_mask = None
|
obj.extra_select_mask = None
|
||||||
else:
|
else:
|
||||||
|
@ -344,7 +361,7 @@ class Query(object):
|
||||||
# and move them to the outer AggregateQuery.
|
# and move them to the outer AggregateQuery.
|
||||||
for alias, aggregate in self.aggregate_select.items():
|
for alias, aggregate in self.aggregate_select.items():
|
||||||
if aggregate.is_summary:
|
if aggregate.is_summary:
|
||||||
query.aggregate_select[alias] = aggregate.relabeled_clone(relabels)
|
query.aggregates[alias] = aggregate.relabeled_clone(relabels)
|
||||||
del obj.aggregate_select[alias]
|
del obj.aggregate_select[alias]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -358,7 +375,7 @@ class Query(object):
|
||||||
query = self
|
query = self
|
||||||
self.select = []
|
self.select = []
|
||||||
self.default_cols = False
|
self.default_cols = False
|
||||||
self.extra = {}
|
self._extra = {}
|
||||||
self.remove_inherited_models()
|
self.remove_inherited_models()
|
||||||
|
|
||||||
query.clear_ordering(True)
|
query.clear_ordering(True)
|
||||||
|
@ -527,7 +544,7 @@ class Query(object):
|
||||||
# It would be nice to be able to handle this, but the queries don't
|
# It would be nice to be able to handle this, but the queries don't
|
||||||
# really make sense (or return consistent value sets). Not worth
|
# really make sense (or return consistent value sets). Not worth
|
||||||
# the extra complexity when you can write a real query instead.
|
# the extra complexity when you can write a real query instead.
|
||||||
if self.extra and rhs.extra:
|
if self._extra and rhs._extra:
|
||||||
raise ValueError("When merging querysets using 'or', you "
|
raise ValueError("When merging querysets using 'or', you "
|
||||||
"cannot have extra(select=...) on both sides.")
|
"cannot have extra(select=...) on both sides.")
|
||||||
self.extra.update(rhs.extra)
|
self.extra.update(rhs.extra)
|
||||||
|
@ -756,8 +773,9 @@ class Query(object):
|
||||||
self.group_by = [relabel_column(col) for col in self.group_by]
|
self.group_by = [relabel_column(col) for col in self.group_by]
|
||||||
self.select = [SelectInfo(relabel_column(s.col), s.field)
|
self.select = [SelectInfo(relabel_column(s.col), s.field)
|
||||||
for s in self.select]
|
for s in self.select]
|
||||||
self.aggregates = OrderedDict(
|
if self._aggregates:
|
||||||
(key, relabel_column(col)) for key, col in self.aggregates.items())
|
self._aggregates = OrderedDict(
|
||||||
|
(key, relabel_column(col)) for key, col in self._aggregates.items())
|
||||||
|
|
||||||
# 2. Rename the alias in the internal table/alias datastructures.
|
# 2. Rename the alias in the internal table/alias datastructures.
|
||||||
for ident, aliases in self.join_map.items():
|
for ident, aliases in self.join_map.items():
|
||||||
|
@ -967,7 +985,7 @@ class Query(object):
|
||||||
"""
|
"""
|
||||||
opts = model._meta
|
opts = model._meta
|
||||||
field_list = aggregate.lookup.split(LOOKUP_SEP)
|
field_list = aggregate.lookup.split(LOOKUP_SEP)
|
||||||
if len(field_list) == 1 and aggregate.lookup in self.aggregates:
|
if len(field_list) == 1 and self._aggregates and aggregate.lookup in self.aggregates:
|
||||||
# Aggregate is over an annotation
|
# Aggregate is over an annotation
|
||||||
field_name = field_list[0]
|
field_name = field_list[0]
|
||||||
col = field_name
|
col = field_name
|
||||||
|
@ -1049,7 +1067,7 @@ class Query(object):
|
||||||
lookup_parts = lookup.split(LOOKUP_SEP)
|
lookup_parts = lookup.split(LOOKUP_SEP)
|
||||||
num_parts = len(lookup_parts)
|
num_parts = len(lookup_parts)
|
||||||
if (len(lookup_parts) > 1 and lookup_parts[-1] in self.query_terms
|
if (len(lookup_parts) > 1 and lookup_parts[-1] in self.query_terms
|
||||||
and lookup not in self.aggregates):
|
and (not self._aggregates or lookup not in self._aggregates)):
|
||||||
# Traverse the lookup query to distinguish related fields from
|
# Traverse the lookup query to distinguish related fields from
|
||||||
# lookup types.
|
# lookup types.
|
||||||
lookup_model = self.model
|
lookup_model = self.model
|
||||||
|
@ -1108,10 +1126,11 @@ class Query(object):
|
||||||
value, lookup_type = self.prepare_lookup_value(value, lookup_type, can_reuse)
|
value, lookup_type = self.prepare_lookup_value(value, lookup_type, can_reuse)
|
||||||
|
|
||||||
clause = self.where_class()
|
clause = self.where_class()
|
||||||
for alias, aggregate in self.aggregates.items():
|
if self._aggregates:
|
||||||
if alias in (parts[0], LOOKUP_SEP.join(parts)):
|
for alias, aggregate in self.aggregates.items():
|
||||||
clause.add((aggregate, lookup_type, value), AND)
|
if alias in (parts[0], LOOKUP_SEP.join(parts)):
|
||||||
return clause
|
clause.add((aggregate, lookup_type, value), AND)
|
||||||
|
return clause
|
||||||
|
|
||||||
opts = self.get_meta()
|
opts = self.get_meta()
|
||||||
alias = self.get_initial_alias()
|
alias = self.get_initial_alias()
|
||||||
|
@ -1170,6 +1189,8 @@ class Query(object):
|
||||||
Returns whether or not all elements of this q_object need to be put
|
Returns whether or not all elements of this q_object need to be put
|
||||||
together in the HAVING clause.
|
together in the HAVING clause.
|
||||||
"""
|
"""
|
||||||
|
if not self._aggregates:
|
||||||
|
return False
|
||||||
if not isinstance(obj, Node):
|
if not isinstance(obj, Node):
|
||||||
return (refs_aggregate(obj[0].split(LOOKUP_SEP), self.aggregates)
|
return (refs_aggregate(obj[0].split(LOOKUP_SEP), self.aggregates)
|
||||||
or (hasattr(obj[1], 'contains_aggregate')
|
or (hasattr(obj[1], 'contains_aggregate')
|
||||||
|
@ -1632,7 +1653,7 @@ class Query(object):
|
||||||
|
|
||||||
# Set only aggregate to be the count column.
|
# Set only aggregate to be the count column.
|
||||||
# Clear out the select cache to reflect the new unmasked aggregates.
|
# Clear out the select cache to reflect the new unmasked aggregates.
|
||||||
self.aggregates = {None: count}
|
self._aggregates = {None: count}
|
||||||
self.set_aggregate_mask(None)
|
self.set_aggregate_mask(None)
|
||||||
self.group_by = None
|
self.group_by = None
|
||||||
|
|
||||||
|
@ -1781,7 +1802,8 @@ class Query(object):
|
||||||
self.extra_select_mask = set(names)
|
self.extra_select_mask = set(names)
|
||||||
self._extra_select_cache = None
|
self._extra_select_cache = None
|
||||||
|
|
||||||
def _aggregate_select(self):
|
@property
|
||||||
|
def aggregate_select(self):
|
||||||
"""The OrderedDict of aggregate columns that are not masked, and should
|
"""The OrderedDict of aggregate columns that are not masked, and should
|
||||||
be used in the SELECT clause.
|
be used in the SELECT clause.
|
||||||
|
|
||||||
|
@ -1789,6 +1811,8 @@ class Query(object):
|
||||||
"""
|
"""
|
||||||
if self._aggregate_select_cache is not None:
|
if self._aggregate_select_cache is not None:
|
||||||
return self._aggregate_select_cache
|
return self._aggregate_select_cache
|
||||||
|
elif not self._aggregates:
|
||||||
|
return {}
|
||||||
elif self.aggregate_select_mask is not None:
|
elif self.aggregate_select_mask is not None:
|
||||||
self._aggregate_select_cache = OrderedDict(
|
self._aggregate_select_cache = OrderedDict(
|
||||||
(k, v) for k, v in self.aggregates.items()
|
(k, v) for k, v in self.aggregates.items()
|
||||||
|
@ -1797,11 +1821,13 @@ class Query(object):
|
||||||
return self._aggregate_select_cache
|
return self._aggregate_select_cache
|
||||||
else:
|
else:
|
||||||
return self.aggregates
|
return self.aggregates
|
||||||
aggregate_select = property(_aggregate_select)
|
|
||||||
|
|
||||||
def _extra_select(self):
|
@property
|
||||||
|
def extra_select(self):
|
||||||
if self._extra_select_cache is not None:
|
if self._extra_select_cache is not None:
|
||||||
return self._extra_select_cache
|
return self._extra_select_cache
|
||||||
|
if not self._extra:
|
||||||
|
return {}
|
||||||
elif self.extra_select_mask is not None:
|
elif self.extra_select_mask is not None:
|
||||||
self._extra_select_cache = OrderedDict(
|
self._extra_select_cache = OrderedDict(
|
||||||
(k, v) for k, v in self.extra.items()
|
(k, v) for k, v in self.extra.items()
|
||||||
|
@ -1810,7 +1836,6 @@ class Query(object):
|
||||||
return self._extra_select_cache
|
return self._extra_select_cache
|
||||||
else:
|
else:
|
||||||
return self.extra
|
return self.extra
|
||||||
extra_select = property(_extra_select)
|
|
||||||
|
|
||||||
def trim_start(self, names_with_path):
|
def trim_start(self, names_with_path):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -977,6 +977,11 @@ class MultiValueField(Field):
|
||||||
f.required = False
|
f.required = False
|
||||||
self.fields = fields
|
self.fields = fields
|
||||||
|
|
||||||
|
def __deepcopy__(self, memo):
|
||||||
|
result = super(MultiValueField, self).__deepcopy__(memo)
|
||||||
|
result.fields = tuple([x.__deepcopy__(memo) for x in self.fields])
|
||||||
|
return result
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -185,7 +185,8 @@ class BaseForm(object):
|
||||||
'label': force_text(label),
|
'label': force_text(label),
|
||||||
'field': six.text_type(bf),
|
'field': six.text_type(bf),
|
||||||
'help_text': help_text,
|
'help_text': help_text,
|
||||||
'html_class_attr': html_class_attr
|
'html_class_attr': html_class_attr,
|
||||||
|
'field_name': bf.html_name,
|
||||||
})
|
})
|
||||||
|
|
||||||
if top_errors:
|
if top_errors:
|
||||||
|
|
|
@ -23,11 +23,18 @@ def flatatt(attrs):
|
||||||
|
|
||||||
The result is passed through 'mark_safe'.
|
The result is passed through 'mark_safe'.
|
||||||
"""
|
"""
|
||||||
if [v for v in attrs.values() if v is True or v is False]:
|
for attr_name, value in attrs.items():
|
||||||
warnings.warn(
|
if type(value) is bool:
|
||||||
'The meaning of boolean values for widget attributes will change in Django 1.8',
|
warnings.warn(
|
||||||
DeprecationWarning
|
"In Django 1.8, widget attribute %(attr_name)s=%(bool_value)s "
|
||||||
)
|
"will %(action)s. To preserve current behavior, use the "
|
||||||
|
"string '%(bool_value)s' instead of the boolean value." % {
|
||||||
|
'attr_name': attr_name,
|
||||||
|
'action': "be rendered as '%s'" % attr_name if value else "not be rendered",
|
||||||
|
'bool_value': value,
|
||||||
|
},
|
||||||
|
DeprecationWarning
|
||||||
|
)
|
||||||
return format_html_join('', ' {0}="{1}"', sorted(attrs.items()))
|
return format_html_join('', ' {0}="{1}"', sorted(attrs.items()))
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
|
|
|
@ -190,7 +190,7 @@ class Widget(six.with_metaclass(MediaDefiningClass)):
|
||||||
The 'value' given is not guaranteed to be valid input, so subclass
|
The 'value' given is not guaranteed to be valid input, so subclass
|
||||||
implementations should program defensively.
|
implementations should program defensively.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of Widget must provide a render() method')
|
||||||
|
|
||||||
def build_attrs(self, extra_attrs=None, **kwargs):
|
def build_attrs(self, extra_attrs=None, **kwargs):
|
||||||
"Helper function for building an attribute dictionary."
|
"Helper function for building an attribute dictionary."
|
||||||
|
|
|
@ -64,6 +64,8 @@ else:
|
||||||
M.set(key, real_value, coded_value)
|
M.set(key, real_value, coded_value)
|
||||||
dict.__setitem__(self, key, M)
|
dict.__setitem__(self, key, M)
|
||||||
except http_cookies.CookieError:
|
except http_cookies.CookieError:
|
||||||
|
if not hasattr(self, 'bad_cookies'):
|
||||||
|
self.bad_cookies = set()
|
||||||
self.bad_cookies.add(key)
|
self.bad_cookies.add(key)
|
||||||
dict.__setitem__(self, key, http_cookies.Morsel())
|
dict.__setitem__(self, key, http_cookies.Morsel())
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ from __future__ import unicode_literals
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import time
|
import time
|
||||||
|
import sys
|
||||||
from email.header import Header
|
from email.header import Header
|
||||||
try:
|
try:
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
@ -160,7 +161,7 @@ class HttpResponseBase(six.Iterator):
|
||||||
except UnicodeError as e:
|
except UnicodeError as e:
|
||||||
if mime_encode:
|
if mime_encode:
|
||||||
# Wrapping in str() is a workaround for #12422 under Python 2.
|
# Wrapping in str() is a workaround for #12422 under Python 2.
|
||||||
value = str(Header(value, 'utf-8').encode())
|
value = str(Header(value, 'utf-8', maxlinelen=sys.maxsize).encode())
|
||||||
else:
|
else:
|
||||||
e.reason += ', HTTP response headers must be in %s format' % charset
|
e.reason += ', HTTP response headers must be in %s format' % charset
|
||||||
raise
|
raise
|
||||||
|
|
|
@ -99,7 +99,7 @@ class Origin(object):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
def reload(self):
|
def reload(self):
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of Origin must provide a reload() method')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
@ -385,7 +385,7 @@ class TokenParser(object):
|
||||||
"""
|
"""
|
||||||
Overload this method to do the actual parsing and return the result.
|
Overload this method to do the actual parsing and return the result.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError('subclasses of Tokenparser must provide a top() method')
|
||||||
|
|
||||||
def more(self):
|
def more(self):
|
||||||
"""
|
"""
|
||||||
|
@ -622,34 +622,17 @@ class FilterExpression(object):
|
||||||
|
|
||||||
def args_check(name, func, provided):
|
def args_check(name, func, provided):
|
||||||
provided = list(provided)
|
provided = list(provided)
|
||||||
plen = len(provided)
|
# First argument, filter input, is implied.
|
||||||
|
plen = len(provided) + 1
|
||||||
# Check to see if a decorator is providing the real function.
|
# Check to see if a decorator is providing the real function.
|
||||||
func = getattr(func, '_decorated_function', func)
|
func = getattr(func, '_decorated_function', func)
|
||||||
args, varargs, varkw, defaults = getargspec(func)
|
args, varargs, varkw, defaults = getargspec(func)
|
||||||
# First argument is filter input.
|
alen = len(args)
|
||||||
args.pop(0)
|
dlen = len(defaults or [])
|
||||||
if defaults:
|
# Not enough OR Too many
|
||||||
nondefs = args[:-len(defaults)]
|
if plen < (alen - dlen) or plen > alen:
|
||||||
else:
|
|
||||||
nondefs = args
|
|
||||||
# Args without defaults must be provided.
|
|
||||||
try:
|
|
||||||
for arg in nondefs:
|
|
||||||
provided.pop(0)
|
|
||||||
except IndexError:
|
|
||||||
# Not enough
|
|
||||||
raise TemplateSyntaxError("%s requires %d arguments, %d provided" %
|
raise TemplateSyntaxError("%s requires %d arguments, %d provided" %
|
||||||
(name, len(nondefs), plen))
|
(name, alen - dlen, plen))
|
||||||
|
|
||||||
# Defaults can be overridden.
|
|
||||||
defaults = list(defaults) if defaults else []
|
|
||||||
try:
|
|
||||||
for parg in provided:
|
|
||||||
defaults.pop(0)
|
|
||||||
except IndexError:
|
|
||||||
# Too many.
|
|
||||||
raise TemplateSyntaxError("%s requires %d arguments, %d provided" %
|
|
||||||
(name, len(nondefs), plen))
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
args_check = staticmethod(args_check)
|
args_check = staticmethod(args_check)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
"""Default tags used by the template system, available to all templates."""
|
"""Default tags used by the template system, available to all templates."""
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
import re
|
import re
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
@ -328,6 +329,7 @@ class RegroupNode(Node):
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
def include_is_allowed(filepath):
|
def include_is_allowed(filepath):
|
||||||
|
filepath = os.path.abspath(filepath)
|
||||||
for root in settings.ALLOWED_INCLUDE_ROOTS:
|
for root in settings.ALLOWED_INCLUDE_ROOTS:
|
||||||
if filepath.startswith(root):
|
if filepath.startswith(root):
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -61,7 +61,7 @@ class BaseLoader(object):
|
||||||
name.
|
name.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of BaseLoader must provide a load_template_source() method')
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -7,4 +7,4 @@ from django.test.testcases import (TestCase, TransactionTestCase,
|
||||||
SimpleTestCase, LiveServerTestCase, skipIfDBFeature,
|
SimpleTestCase, LiveServerTestCase, skipIfDBFeature,
|
||||||
skipUnlessDBFeature
|
skipUnlessDBFeature
|
||||||
)
|
)
|
||||||
from django.test.utils import Approximate
|
from django.test.utils import override_settings
|
||||||
|
|
|
@ -14,6 +14,8 @@ class DiscoverRunner(object):
|
||||||
A Django test runner that uses unittest2 test discovery.
|
A Django test runner that uses unittest2 test discovery.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
test_suite = TestSuite
|
||||||
|
test_runner = unittest.TextTestRunner
|
||||||
test_loader = defaultTestLoader
|
test_loader = defaultTestLoader
|
||||||
reorder_by = (TestCase, )
|
reorder_by = (TestCase, )
|
||||||
option_list = (
|
option_list = (
|
||||||
|
@ -42,7 +44,7 @@ class DiscoverRunner(object):
|
||||||
unittest.installHandler()
|
unittest.installHandler()
|
||||||
|
|
||||||
def build_suite(self, test_labels=None, extra_tests=None, **kwargs):
|
def build_suite(self, test_labels=None, extra_tests=None, **kwargs):
|
||||||
suite = TestSuite()
|
suite = self.test_suite()
|
||||||
test_labels = test_labels or ['.']
|
test_labels = test_labels or ['.']
|
||||||
extra_tests = extra_tests or []
|
extra_tests = extra_tests or []
|
||||||
|
|
||||||
|
@ -107,7 +109,7 @@ class DiscoverRunner(object):
|
||||||
return setup_databases(self.verbosity, self.interactive, **kwargs)
|
return setup_databases(self.verbosity, self.interactive, **kwargs)
|
||||||
|
|
||||||
def run_suite(self, suite, **kwargs):
|
def run_suite(self, suite, **kwargs):
|
||||||
return unittest.TextTestRunner(
|
return self.test_runner(
|
||||||
verbosity=self.verbosity,
|
verbosity=self.verbosity,
|
||||||
failfast=self.failfast,
|
failfast=self.failfast,
|
||||||
).run(suite)
|
).run(suite)
|
||||||
|
@ -201,7 +203,8 @@ def reorder_suite(suite, classes):
|
||||||
classes[1], etc. Tests with no match in classes are placed last.
|
classes[1], etc. Tests with no match in classes are placed last.
|
||||||
"""
|
"""
|
||||||
class_count = len(classes)
|
class_count = len(classes)
|
||||||
bins = [unittest.TestSuite() for i in range(class_count+1)]
|
suite_class = type(suite)
|
||||||
|
bins = [suite_class() for i in range(class_count+1)]
|
||||||
partition_suite(suite, classes, bins)
|
partition_suite(suite, classes, bins)
|
||||||
for i in range(class_count):
|
for i in range(class_count):
|
||||||
bins[0].addTests(bins[i+1])
|
bins[0].addTests(bins[i+1])
|
||||||
|
@ -218,8 +221,9 @@ def partition_suite(suite, classes, bins):
|
||||||
Tests of type classes[i] are added to bins[i],
|
Tests of type classes[i] are added to bins[i],
|
||||||
tests with no match found in classes are place in bins[-1]
|
tests with no match found in classes are place in bins[-1]
|
||||||
"""
|
"""
|
||||||
|
suite_class = type(suite)
|
||||||
for test in suite:
|
for test in suite:
|
||||||
if isinstance(test, unittest.TestSuite):
|
if isinstance(test, suite_class):
|
||||||
partition_suite(test, classes, bins)
|
partition_suite(test, classes, bins)
|
||||||
else:
|
else:
|
||||||
for i in range(len(classes)):
|
for i in range(len(classes)):
|
||||||
|
|
|
@ -225,12 +225,14 @@ class SimpleTestCase(unittest.TestCase):
|
||||||
return override_settings(**kwargs)
|
return override_settings(**kwargs)
|
||||||
|
|
||||||
def assertRedirects(self, response, expected_url, status_code=302,
|
def assertRedirects(self, response, expected_url, status_code=302,
|
||||||
target_status_code=200, host=None, msg_prefix=''):
|
target_status_code=200, host=None, msg_prefix='',
|
||||||
|
fetch_redirect_response=True):
|
||||||
"""Asserts that a response redirected to a specific URL, and that the
|
"""Asserts that a response redirected to a specific URL, and that the
|
||||||
redirect URL can be loaded.
|
redirect URL can be loaded.
|
||||||
|
|
||||||
Note that assertRedirects won't work for external links since it uses
|
Note that assertRedirects won't work for external links since it uses
|
||||||
TestClient to do a request.
|
TestClient to do a request (use fetch_redirect_response=False to check
|
||||||
|
such links without fetching thtem).
|
||||||
"""
|
"""
|
||||||
if msg_prefix:
|
if msg_prefix:
|
||||||
msg_prefix += ": "
|
msg_prefix += ": "
|
||||||
|
@ -264,14 +266,15 @@ class SimpleTestCase(unittest.TestCase):
|
||||||
url = response.url
|
url = response.url
|
||||||
scheme, netloc, path, query, fragment = urlsplit(url)
|
scheme, netloc, path, query, fragment = urlsplit(url)
|
||||||
|
|
||||||
redirect_response = response.client.get(path, QueryDict(query))
|
if fetch_redirect_response:
|
||||||
|
redirect_response = response.client.get(path, QueryDict(query))
|
||||||
|
|
||||||
# Get the redirection page, using the same client that was used
|
# Get the redirection page, using the same client that was used
|
||||||
# to obtain the original response.
|
# to obtain the original response.
|
||||||
self.assertEqual(redirect_response.status_code, target_status_code,
|
self.assertEqual(redirect_response.status_code, target_status_code,
|
||||||
msg_prefix + "Couldn't retrieve redirection page '%s':"
|
msg_prefix + "Couldn't retrieve redirection page '%s':"
|
||||||
" response code was %d (expected %d)" %
|
" response code was %d (expected %d)" %
|
||||||
(path, redirect_response.status_code, target_status_code))
|
(path, redirect_response.status_code, target_status_code))
|
||||||
|
|
||||||
e_scheme, e_netloc, e_path, e_query, e_fragment = urlsplit(
|
e_scheme, e_netloc, e_path, e_query, e_fragment = urlsplit(
|
||||||
expected_url)
|
expected_url)
|
||||||
|
@ -696,6 +699,9 @@ class TransactionTestCase(SimpleTestCase):
|
||||||
# Subclasses can enable only a subset of apps for faster tests
|
# Subclasses can enable only a subset of apps for faster tests
|
||||||
available_apps = None
|
available_apps = None
|
||||||
|
|
||||||
|
# Subclasses can define fixtures which will be automatically installed.
|
||||||
|
fixtures = None
|
||||||
|
|
||||||
def _pre_setup(self):
|
def _pre_setup(self):
|
||||||
"""Performs any pre-test setup. This includes:
|
"""Performs any pre-test setup. This includes:
|
||||||
|
|
||||||
|
@ -743,7 +749,7 @@ class TransactionTestCase(SimpleTestCase):
|
||||||
if self.reset_sequences:
|
if self.reset_sequences:
|
||||||
self._reset_sequences(db_name)
|
self._reset_sequences(db_name)
|
||||||
|
|
||||||
if hasattr(self, 'fixtures'):
|
if self.fixtures:
|
||||||
# We have to use this slightly awkward syntax due to the fact
|
# We have to use this slightly awkward syntax due to the fact
|
||||||
# that we're using *args and **kwargs together.
|
# that we're using *args and **kwargs together.
|
||||||
call_command('loaddata', *self.fixtures,
|
call_command('loaddata', *self.fixtures,
|
||||||
|
@ -835,7 +841,7 @@ class TestCase(TransactionTestCase):
|
||||||
disable_transaction_methods()
|
disable_transaction_methods()
|
||||||
|
|
||||||
for db_name in self._databases_names(include_mirrors=False):
|
for db_name in self._databases_names(include_mirrors=False):
|
||||||
if hasattr(self, 'fixtures'):
|
if self.fixtures:
|
||||||
try:
|
try:
|
||||||
call_command('loaddata', *self.fixtures,
|
call_command('loaddata', *self.fixtures,
|
||||||
**{
|
**{
|
||||||
|
|
|
@ -126,10 +126,10 @@ class BaseArchive(object):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def extract(self):
|
def extract(self):
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of BaseArchive must provide an extract() method')
|
||||||
|
|
||||||
def list(self):
|
def list(self):
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of BaseArchive must provide a list() method')
|
||||||
|
|
||||||
|
|
||||||
class TarArchive(BaseArchive):
|
class TarArchive(BaseArchive):
|
||||||
|
|
|
@ -18,11 +18,10 @@ import calendar
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from django.utils.dates import MONTHS, MONTHS_3, MONTHS_ALT, MONTHS_AP, WEEKDAYS, WEEKDAYS_ABBR
|
from django.utils.dates import MONTHS, MONTHS_3, MONTHS_ALT, MONTHS_AP, WEEKDAYS, WEEKDAYS_ABBR
|
||||||
from django.utils.tzinfo import LocalTimezone
|
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
from django.utils.timezone import is_aware, is_naive
|
from django.utils.timezone import get_default_timezone, is_aware, is_naive
|
||||||
|
|
||||||
re_formatchars = re.compile(r'(?<!\\)([aAbBcdDeEfFgGhHiIjlLmMnNoOPrsStTUuwWyYzZ])')
|
re_formatchars = re.compile(r'(?<!\\)([aAbBcdDeEfFgGhHiIjlLmMnNoOPrsStTUuwWyYzZ])')
|
||||||
re_escaped = re.compile(r'\\(.)')
|
re_escaped = re.compile(r'\\(.)')
|
||||||
|
@ -48,7 +47,7 @@ class TimeFormat(Formatter):
|
||||||
# or time objects (against established django policy).
|
# or time objects (against established django policy).
|
||||||
if isinstance(obj, datetime.datetime):
|
if isinstance(obj, datetime.datetime):
|
||||||
if is_naive(obj):
|
if is_naive(obj):
|
||||||
self.timezone = LocalTimezone(obj)
|
self.timezone = get_default_timezone()
|
||||||
else:
|
else:
|
||||||
self.timezone = obj.tzinfo
|
self.timezone = obj.tzinfo
|
||||||
|
|
||||||
|
@ -66,7 +65,7 @@ class TimeFormat(Formatter):
|
||||||
|
|
||||||
def B(self):
|
def B(self):
|
||||||
"Swatch Internet time"
|
"Swatch Internet time"
|
||||||
raise NotImplementedError
|
raise NotImplementedError('may be implemented in a future release')
|
||||||
|
|
||||||
def e(self):
|
def e(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -8,8 +8,8 @@
|
||||||
import datetime
|
import datetime
|
||||||
import re
|
import re
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
from django.utils.timezone import utc
|
from django.utils.timezone import utc, get_fixed_timezone
|
||||||
from django.utils.tzinfo import FixedOffset
|
|
||||||
|
|
||||||
date_re = re.compile(
|
date_re = re.compile(
|
||||||
r'(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})$'
|
r'(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})$'
|
||||||
|
@ -27,6 +27,7 @@ datetime_re = re.compile(
|
||||||
r'(?P<tzinfo>Z|[+-]\d{2}:?\d{2})?$'
|
r'(?P<tzinfo>Z|[+-]\d{2}:?\d{2})?$'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def parse_date(value):
|
def parse_date(value):
|
||||||
"""Parses a string and return a datetime.date.
|
"""Parses a string and return a datetime.date.
|
||||||
|
|
||||||
|
@ -59,7 +60,7 @@ def parse_datetime(value):
|
||||||
"""Parses a string and return a datetime.datetime.
|
"""Parses a string and return a datetime.datetime.
|
||||||
|
|
||||||
This function supports time zone offsets. When the input contains one,
|
This function supports time zone offsets. When the input contains one,
|
||||||
the output uses an instance of FixedOffset as tzinfo.
|
the output uses a timezone with a fixed offset from UTC.
|
||||||
|
|
||||||
Raises ValueError if the input is well formatted but not a valid datetime.
|
Raises ValueError if the input is well formatted but not a valid datetime.
|
||||||
Returns None if the input isn't well formatted.
|
Returns None if the input isn't well formatted.
|
||||||
|
@ -76,7 +77,7 @@ def parse_datetime(value):
|
||||||
offset = 60 * int(tzinfo[1:3]) + int(tzinfo[-2:])
|
offset = 60 * int(tzinfo[1:3]) + int(tzinfo[-2:])
|
||||||
if tzinfo[0] == '-':
|
if tzinfo[0] == '-':
|
||||||
offset = -offset
|
offset = -offset
|
||||||
tzinfo = FixedOffset(offset)
|
tzinfo = get_fixed_timezone(offset)
|
||||||
kw = dict((k, int(v)) for k, v in six.iteritems(kw) if v is not None)
|
kw = dict((k, int(v)) for k, v in six.iteritems(kw) if v is not None)
|
||||||
kw['tzinfo'] = tzinfo
|
kw['tzinfo'] = tzinfo
|
||||||
return datetime.datetime(**kw)
|
return datetime.datetime(**kw)
|
||||||
|
|
|
@ -177,7 +177,7 @@ class SyndicationFeed(object):
|
||||||
Outputs the feed in the given encoding to outfile, which is a file-like
|
Outputs the feed in the given encoding to outfile, which is a file-like
|
||||||
object. Subclasses should override this.
|
object. Subclasses should override this.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of SyndicationFeed must provide a write() method')
|
||||||
|
|
||||||
def writeString(self, encoding):
|
def writeString(self, encoding):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -257,7 +257,7 @@ class LazyObject(object):
|
||||||
"""
|
"""
|
||||||
Must be implemented by subclasses to initialise the wrapped object.
|
Must be implemented by subclasses to initialise the wrapped object.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError('subclasses of LazyObject must provide a _setup() method')
|
||||||
|
|
||||||
# Introspection support
|
# Introspection support
|
||||||
__dir__ = new_method_proxy(dir)
|
__dir__ = new_method_proxy(dir)
|
||||||
|
|
|
@ -84,24 +84,22 @@ class AdminEmailHandler(logging.Handler):
|
||||||
record.getMessage()
|
record.getMessage()
|
||||||
)
|
)
|
||||||
filter = get_exception_reporter_filter(request)
|
filter = get_exception_reporter_filter(request)
|
||||||
request_repr = filter.get_request_repr(request)
|
request_repr = '\n{0}'.format(filter.get_request_repr(request))
|
||||||
except Exception:
|
except Exception:
|
||||||
subject = '%s: %s' % (
|
subject = '%s: %s' % (
|
||||||
record.levelname,
|
record.levelname,
|
||||||
record.getMessage()
|
record.getMessage()
|
||||||
)
|
)
|
||||||
request = None
|
request = None
|
||||||
request_repr = "Request repr() unavailable."
|
request_repr = "unavailable"
|
||||||
subject = self.format_subject(subject)
|
subject = self.format_subject(subject)
|
||||||
|
|
||||||
if record.exc_info:
|
if record.exc_info:
|
||||||
exc_info = record.exc_info
|
exc_info = record.exc_info
|
||||||
stack_trace = '\n'.join(traceback.format_exception(*record.exc_info))
|
|
||||||
else:
|
else:
|
||||||
exc_info = (None, record.getMessage(), None)
|
exc_info = (None, record.getMessage(), None)
|
||||||
stack_trace = 'No stack trace available'
|
|
||||||
|
|
||||||
message = "%s\n\n%s" % (stack_trace, request_repr)
|
message = "%s\n\nRequest repr(): %s" % (self.format(record), request_repr)
|
||||||
reporter = ExceptionReporter(request, is_email=True, *exc_info)
|
reporter = ExceptionReporter(request, is_email=True, *exc_info)
|
||||||
html_message = reporter.get_traceback_html() if self.include_html else None
|
html_message = reporter.get_traceback_html() if self.include_html else None
|
||||||
mail.mail_admins(subject, message, fail_silently=True,
|
mail.mail_admins(subject, message, fail_silently=True,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from __future__ import absolute_import # Avoid importing `importlib` from this package.
|
from __future__ import absolute_import # Avoid importing `importlib` from this package.
|
||||||
|
|
||||||
|
import copy
|
||||||
import imp
|
import imp
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
import os
|
import os
|
||||||
|
@ -34,6 +35,43 @@ def import_by_path(dotted_path, error_prefix=''):
|
||||||
return attr
|
return attr
|
||||||
|
|
||||||
|
|
||||||
|
def autodiscover_modules(*args, **kwargs):
|
||||||
|
"""
|
||||||
|
Auto-discover INSTALLED_APPS modules and fail silently when
|
||||||
|
not present. This forces an import on them to register any admin bits they
|
||||||
|
may want.
|
||||||
|
|
||||||
|
You may provide a register_to keyword parameter as a way to access a
|
||||||
|
registry. This register_to object must have a _registry instance variable
|
||||||
|
to access it.
|
||||||
|
"""
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
register_to = kwargs.get('register_to')
|
||||||
|
for app in settings.INSTALLED_APPS:
|
||||||
|
mod = import_module(app)
|
||||||
|
# Attempt to import the app's module.
|
||||||
|
try:
|
||||||
|
if register_to:
|
||||||
|
before_import_registry = copy.copy(register_to._registry)
|
||||||
|
|
||||||
|
for module_to_search in args:
|
||||||
|
import_module('%s.%s' % (app, module_to_search))
|
||||||
|
except:
|
||||||
|
# Reset the model registry to the state before the last import as
|
||||||
|
# this import will have to reoccur on the next request and this
|
||||||
|
# could raise NotRegistered and AlreadyRegistered exceptions
|
||||||
|
# (see #8245).
|
||||||
|
if register_to:
|
||||||
|
register_to._registry = before_import_registry
|
||||||
|
|
||||||
|
# Decide whether to bubble up this error. If the app just
|
||||||
|
# doesn't have an admin module, we can ignore the error
|
||||||
|
# attempting to import it, otherwise we want it to bubble up.
|
||||||
|
if module_has_submodule(mod, module_to_search):
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
def module_has_submodule(package, module_name):
|
def module_has_submodule(package, module_name):
|
||||||
"""See if 'module' is in 'package'."""
|
"""See if 'module' is in 'package'."""
|
||||||
name = ".".join([package.__name__, module_name])
|
name = ".".join([package.__name__, module_name])
|
||||||
|
|
|
@ -92,7 +92,7 @@ def normalize(pattern):
|
||||||
result.append(".")
|
result.append(".")
|
||||||
elif ch == '|':
|
elif ch == '|':
|
||||||
# FIXME: One day we'll should do this, but not in 1.0.
|
# FIXME: One day we'll should do this, but not in 1.0.
|
||||||
raise NotImplementedError
|
raise NotImplementedError('Awaiting Implementation')
|
||||||
elif ch == "^":
|
elif ch == "^":
|
||||||
pass
|
pass
|
||||||
elif ch == '$':
|
elif ch == '$':
|
||||||
|
|
|
@ -18,7 +18,8 @@ from django.conf import settings
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'utc', 'get_default_timezone', 'get_current_timezone',
|
'utc', 'get_fixed_timezone',
|
||||||
|
'get_default_timezone', 'get_current_timezone',
|
||||||
'activate', 'deactivate', 'override',
|
'activate', 'deactivate', 'override',
|
||||||
'is_naive', 'is_aware', 'make_aware', 'make_naive',
|
'is_naive', 'is_aware', 'make_aware', 'make_naive',
|
||||||
]
|
]
|
||||||
|
@ -47,19 +48,45 @@ class UTC(tzinfo):
|
||||||
def dst(self, dt):
|
def dst(self, dt):
|
||||||
return ZERO
|
return ZERO
|
||||||
|
|
||||||
|
class FixedOffset(tzinfo):
|
||||||
|
"""
|
||||||
|
Fixed offset in minutes east from UTC. Taken from Python's docs.
|
||||||
|
|
||||||
|
Kept as close as possible to the reference version. __init__ was changed
|
||||||
|
to make its arguments optional, according to Python's requirement that
|
||||||
|
tzinfo subclasses can be instantiated without arguments.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, offset=None, name=None):
|
||||||
|
if offset is not None:
|
||||||
|
self.__offset = timedelta(minutes=offset)
|
||||||
|
if name is not None:
|
||||||
|
self.__name = name
|
||||||
|
|
||||||
|
def utcoffset(self, dt):
|
||||||
|
return self.__offset
|
||||||
|
|
||||||
|
def tzname(self, dt):
|
||||||
|
return self.__name
|
||||||
|
|
||||||
|
def dst(self, dt):
|
||||||
|
return ZERO
|
||||||
|
|
||||||
class ReferenceLocalTimezone(tzinfo):
|
class ReferenceLocalTimezone(tzinfo):
|
||||||
"""
|
"""
|
||||||
Local time implementation taken from Python's docs.
|
Local time. Taken from Python's docs.
|
||||||
|
|
||||||
Used only when pytz isn't available, and most likely inaccurate. If you're
|
Used only when pytz isn't available, and most likely inaccurate. If you're
|
||||||
having trouble with this class, don't waste your time, just install pytz.
|
having trouble with this class, don't waste your time, just install pytz.
|
||||||
|
|
||||||
Kept identical to the reference version. Subclasses contain improvements.
|
Kept as close as possible to the reference version. __init__ was added to
|
||||||
|
delay the computation of STDOFFSET, DSTOFFSET and DSTDIFF which is
|
||||||
|
performed at import time in the example.
|
||||||
|
|
||||||
|
Subclasses contain further improvements.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
# This code is moved in __init__ to execute it as late as possible
|
|
||||||
# See get_default_timezone().
|
|
||||||
self.STDOFFSET = timedelta(seconds=-_time.timezone)
|
self.STDOFFSET = timedelta(seconds=-_time.timezone)
|
||||||
if _time.daylight:
|
if _time.daylight:
|
||||||
self.DSTOFFSET = timedelta(seconds=-_time.altzone)
|
self.DSTOFFSET = timedelta(seconds=-_time.altzone)
|
||||||
|
@ -68,9 +95,6 @@ class ReferenceLocalTimezone(tzinfo):
|
||||||
self.DSTDIFF = self.DSTOFFSET - self.STDOFFSET
|
self.DSTDIFF = self.DSTOFFSET - self.STDOFFSET
|
||||||
tzinfo.__init__(self)
|
tzinfo.__init__(self)
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "<LocalTimezone>"
|
|
||||||
|
|
||||||
def utcoffset(self, dt):
|
def utcoffset(self, dt):
|
||||||
if self._isdst(dt):
|
if self._isdst(dt):
|
||||||
return self.DSTOFFSET
|
return self.DSTOFFSET
|
||||||
|
@ -84,8 +108,7 @@ class ReferenceLocalTimezone(tzinfo):
|
||||||
return ZERO
|
return ZERO
|
||||||
|
|
||||||
def tzname(self, dt):
|
def tzname(self, dt):
|
||||||
is_dst = False if dt is None else self._isdst(dt)
|
return _time.tzname[self._isdst(dt)]
|
||||||
return _time.tzname[is_dst]
|
|
||||||
|
|
||||||
def _isdst(self, dt):
|
def _isdst(self, dt):
|
||||||
tt = (dt.year, dt.month, dt.day,
|
tt = (dt.year, dt.month, dt.day,
|
||||||
|
@ -103,6 +126,10 @@ class LocalTimezone(ReferenceLocalTimezone):
|
||||||
error message is helpful.
|
error message is helpful.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def tzname(self, dt):
|
||||||
|
is_dst = False if dt is None else self._isdst(dt)
|
||||||
|
return _time.tzname[is_dst]
|
||||||
|
|
||||||
def _isdst(self, dt):
|
def _isdst(self, dt):
|
||||||
try:
|
try:
|
||||||
return super(LocalTimezone, self)._isdst(dt)
|
return super(LocalTimezone, self)._isdst(dt)
|
||||||
|
@ -116,6 +143,17 @@ class LocalTimezone(ReferenceLocalTimezone):
|
||||||
utc = pytz.utc if pytz else UTC()
|
utc = pytz.utc if pytz else UTC()
|
||||||
"""UTC time zone as a tzinfo instance."""
|
"""UTC time zone as a tzinfo instance."""
|
||||||
|
|
||||||
|
def get_fixed_timezone(offset):
|
||||||
|
"""
|
||||||
|
Returns a tzinfo instance with a fixed offset from UTC.
|
||||||
|
"""
|
||||||
|
if isinstance(offset, timedelta):
|
||||||
|
offset = offset.seconds // 60
|
||||||
|
sign = '-' if offset < 0 else '+'
|
||||||
|
hhmm = '%02d%02d' % divmod(abs(offset), 60)
|
||||||
|
name = sign + hhmm
|
||||||
|
return FixedOffset(offset, name)
|
||||||
|
|
||||||
# In order to avoid accessing the settings at compile time,
|
# In order to avoid accessing the settings at compile time,
|
||||||
# wrap the expression in a function and cache the result.
|
# wrap the expression in a function and cache the result.
|
||||||
_localtime = None
|
_localtime = None
|
||||||
|
@ -125,8 +163,6 @@ def get_default_timezone():
|
||||||
Returns the default time zone as a tzinfo instance.
|
Returns the default time zone as a tzinfo instance.
|
||||||
|
|
||||||
This is the time zone defined by settings.TIME_ZONE.
|
This is the time zone defined by settings.TIME_ZONE.
|
||||||
|
|
||||||
See also :func:`get_current_timezone`.
|
|
||||||
"""
|
"""
|
||||||
global _localtime
|
global _localtime
|
||||||
if _localtime is None:
|
if _localtime is None:
|
||||||
|
|
|
@ -668,7 +668,10 @@ def parse_accept_lang_header(lang_string):
|
||||||
if first:
|
if first:
|
||||||
return []
|
return []
|
||||||
if priority:
|
if priority:
|
||||||
priority = float(priority)
|
try:
|
||||||
|
priority = float(priority)
|
||||||
|
except ValueError:
|
||||||
|
return []
|
||||||
if not priority: # if priority is 0.0 at this point make it 1.0
|
if not priority: # if priority is 0.0 at this point make it 1.0
|
||||||
priority = 1.0
|
priority = 1.0
|
||||||
result.append((lang, priority))
|
result.append((lang, priority))
|
||||||
|
|
|
@ -2,11 +2,18 @@
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import time
|
|
||||||
from datetime import timedelta, tzinfo
|
from datetime import timedelta, tzinfo
|
||||||
|
import time
|
||||||
|
import warnings
|
||||||
|
|
||||||
from django.utils.encoding import force_str, force_text, DEFAULT_LOCALE_ENCODING
|
from django.utils.encoding import force_str, force_text, DEFAULT_LOCALE_ENCODING
|
||||||
|
|
||||||
|
warnings.warn(
|
||||||
|
"django.utils.tzinfo will be removed in Django 1.9. "
|
||||||
|
"Use django.utils.timezone instead.",
|
||||||
|
PendingDeprecationWarning)
|
||||||
|
|
||||||
|
|
||||||
# Python's doc say: "A tzinfo subclass must have an __init__() method that can
|
# Python's doc say: "A tzinfo subclass must have an __init__() method that can
|
||||||
# be called with no arguments". FixedOffset and LocalTimezone don't honor this
|
# be called with no arguments". FixedOffset and LocalTimezone don't honor this
|
||||||
# requirement. Defining __getinitargs__ is sufficient to fix copy/deepcopy as
|
# requirement. Defining __getinitargs__ is sufficient to fix copy/deepcopy as
|
||||||
|
@ -15,6 +22,10 @@ from django.utils.encoding import force_str, force_text, DEFAULT_LOCALE_ENCODING
|
||||||
class FixedOffset(tzinfo):
|
class FixedOffset(tzinfo):
|
||||||
"Fixed offset in minutes east from UTC."
|
"Fixed offset in minutes east from UTC."
|
||||||
def __init__(self, offset):
|
def __init__(self, offset):
|
||||||
|
warnings.warn(
|
||||||
|
"django.utils.tzinfo.FixedOffset will be removed in Django 1.9. "
|
||||||
|
"Use django.utils.timezone.get_fixed_timezone instead.",
|
||||||
|
PendingDeprecationWarning)
|
||||||
if isinstance(offset, timedelta):
|
if isinstance(offset, timedelta):
|
||||||
self.__offset = offset
|
self.__offset = offset
|
||||||
offset = self.__offset.seconds // 60
|
offset = self.__offset.seconds // 60
|
||||||
|
@ -48,6 +59,10 @@ class FixedOffset(tzinfo):
|
||||||
class LocalTimezone(tzinfo):
|
class LocalTimezone(tzinfo):
|
||||||
"Proxy timezone information from time module."
|
"Proxy timezone information from time module."
|
||||||
def __init__(self, dt):
|
def __init__(self, dt):
|
||||||
|
warnings.warn(
|
||||||
|
"django.utils.tzinfo.LocalTimezone will be removed in Django 1.9. "
|
||||||
|
"Use django.utils.timezone.get_default_timezone instead.",
|
||||||
|
PendingDeprecationWarning)
|
||||||
tzinfo.__init__(self)
|
tzinfo.__init__(self)
|
||||||
self.__dt = dt
|
self.__dt = dt
|
||||||
self._tzname = self.tzname(dt)
|
self._tzname = self.tzname(dt)
|
||||||
|
|
|
@ -106,7 +106,7 @@ this by adding the following snippet to your urls.py::
|
||||||
|
|
||||||
Also this helper function only serves the actual :setting:`STATIC_ROOT`
|
Also this helper function only serves the actual :setting:`STATIC_ROOT`
|
||||||
folder; it doesn't perform static files discovery like
|
folder; it doesn't perform static files discovery like
|
||||||
`:mod:`django.contrib.staticfiles`.
|
:mod:`django.contrib.staticfiles`.
|
||||||
|
|
||||||
Serving files uploaded by a user during development.
|
Serving files uploaded by a user during development.
|
||||||
====================================================
|
====================================================
|
||||||
|
|
|
@ -34,6 +34,8 @@ Decisions on new committers will follow the process explained in
|
||||||
existing committer privately. Public requests for commit access are potential
|
existing committer privately. Public requests for commit access are potential
|
||||||
flame-war starters, and will simply be ignored.
|
flame-war starters, and will simply be ignored.
|
||||||
|
|
||||||
|
.. _handling-pull-requests:
|
||||||
|
|
||||||
Handling pull requests
|
Handling pull requests
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
|
|
|
@ -212,7 +212,7 @@ This way your branch will contain only commits related to its topic, which
|
||||||
makes squashing easier.
|
makes squashing easier.
|
||||||
|
|
||||||
After review
|
After review
|
||||||
------------
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
It is unusual to get any non-trivial amount of code into core without changes
|
It is unusual to get any non-trivial amount of code into core without changes
|
||||||
requested by reviewers. In this case, it is often a good idea to add the
|
requested by reviewers. In this case, it is often a good idea to add the
|
||||||
|
@ -225,7 +225,8 @@ commits, you would run::
|
||||||
|
|
||||||
git rebase -i HEAD~2
|
git rebase -i HEAD~2
|
||||||
|
|
||||||
Squash the second commit into the first. Write a commit message along the lines of::
|
Squash the second commit into the first. Write a commit message along the lines
|
||||||
|
of::
|
||||||
|
|
||||||
Made changes asked in review by <reviewer>
|
Made changes asked in review by <reviewer>
|
||||||
|
|
||||||
|
@ -239,8 +240,25 @@ the public commits during the rebase, you should not need to force-push::
|
||||||
|
|
||||||
Your pull request should now contain the new commit too.
|
Your pull request should now contain the new commit too.
|
||||||
|
|
||||||
Note that the committer is likely to squash the review commit into the previous commit
|
Note that the committer is likely to squash the review commit into the previous
|
||||||
when committing the code.
|
commit when committing the code.
|
||||||
|
|
||||||
|
Working on a patch
|
||||||
|
------------------
|
||||||
|
|
||||||
|
One of the ways that developers can contribute to Django is by reviewing
|
||||||
|
patches. Those patches will typically exist as pull requests on GitHub and
|
||||||
|
can be easily integrated into your local repository::
|
||||||
|
|
||||||
|
git checkout -b pull_xxxxx upstream/master
|
||||||
|
curl https://github.com/django/django/pull/xxxxx.patch | git am
|
||||||
|
|
||||||
|
This will create a new branch and then apply the changes from the pull request
|
||||||
|
to it. At this point you can run the tests or do anything else you need to
|
||||||
|
do to investigate the quality of the patch.
|
||||||
|
|
||||||
|
For more detail on working with pull requests see the
|
||||||
|
:ref:`guidelines for committers <handling-pull-requests>`.
|
||||||
|
|
||||||
Summary
|
Summary
|
||||||
-------
|
-------
|
||||||
|
|
|
@ -117,9 +117,9 @@ these changes.
|
||||||
* The ``mod_python`` request handler will be removed. The ``mod_wsgi``
|
* The ``mod_python`` request handler will be removed. The ``mod_wsgi``
|
||||||
handler should be used instead.
|
handler should be used instead.
|
||||||
|
|
||||||
* The ``template`` attribute on :class:`~django.test.client.Response`
|
* The ``template`` attribute on :class:`~django.test.Response`
|
||||||
objects returned by the :ref:`test client <test-client>` will be removed.
|
objects returned by the :ref:`test client <test-client>` will be removed.
|
||||||
The :attr:`~django.test.client.Response.templates` attribute should be
|
The :attr:`~django.test.Response.templates` attribute should be
|
||||||
used instead.
|
used instead.
|
||||||
|
|
||||||
* The ``django.test.simple.DjangoTestRunner`` will be removed.
|
* The ``django.test.simple.DjangoTestRunner`` will be removed.
|
||||||
|
@ -417,6 +417,8 @@ these changes.
|
||||||
|
|
||||||
* ``django.utils.importlib`` will be removed.
|
* ``django.utils.importlib`` will be removed.
|
||||||
|
|
||||||
|
* ``django.utils.tzinfo`` will be removed.
|
||||||
|
|
||||||
* ``django.utils.unittest`` will be removed.
|
* ``django.utils.unittest`` will be removed.
|
||||||
|
|
||||||
* The ``syncdb`` command will be removed.
|
* The ``syncdb`` command will be removed.
|
||||||
|
|
|
@ -102,7 +102,7 @@ Installing some prerequisites
|
||||||
The current state of Python packaging is a bit muddled with various tools. For
|
The current state of Python packaging is a bit muddled with various tools. For
|
||||||
this tutorial, we're going to use distribute_ to build our package. It's a
|
this tutorial, we're going to use distribute_ to build our package. It's a
|
||||||
community-maintained fork of the older ``setuptools`` project. We'll also be
|
community-maintained fork of the older ``setuptools`` project. We'll also be
|
||||||
using `pip`_ to uninstall it after we're finished. You should install these
|
using `pip`_ to install and uninstall it. You should install these
|
||||||
two packages now. If you need help, you can refer to :ref:`how to install
|
two packages now. If you need help, you can refer to :ref:`how to install
|
||||||
Django with pip<installing-official-release>`. You can install ``distribute``
|
Django with pip<installing-official-release>`. You can install ``distribute``
|
||||||
the same way.
|
the same way.
|
||||||
|
@ -262,28 +262,18 @@ working. We'll now fix this by installing our new ``django-polls`` package.
|
||||||
tools that run as that user, so ``virtualenv`` is a more robust solution
|
tools that run as that user, so ``virtualenv`` is a more robust solution
|
||||||
(see below).
|
(see below).
|
||||||
|
|
||||||
1. Inside ``django-polls/dist``, untar the new package
|
1. To install the package, use pip (you already :ref:`installed it
|
||||||
``django-polls-0.1.tar.gz`` (e.g. ``tar xzvf django-polls-0.1.tar.gz``). If
|
<installing-reusable-apps-prerequisites>`, right?)::
|
||||||
you're using Windows, you can download the command-line tool bsdtar_ to do
|
|
||||||
this, or you can use a GUI-based tool such as 7-zip_.
|
|
||||||
|
|
||||||
2. Change into the directory created in step 1 (e.g. ``cd django-polls-0.1``).
|
pip install --user django-polls/dist/django-polls-0.1.tar.gz
|
||||||
|
|
||||||
3. If you're using GNU/Linux, Mac OS X or some other flavor of Unix, enter the
|
2. With luck, your Django project should now work correctly again. Run the
|
||||||
command ``python setup.py install --user`` at the shell prompt. If you're
|
|
||||||
using Windows, start up a command shell and run the command
|
|
||||||
``setup.py install --user``.
|
|
||||||
|
|
||||||
With luck, your Django project should now work correctly again. Run the
|
|
||||||
server again to confirm this.
|
server again to confirm this.
|
||||||
|
|
||||||
4. To uninstall the package, use pip (you already :ref:`installed it
|
3. To uninstall the package, use pip::
|
||||||
<installing-reusable-apps-prerequisites>`, right?)::
|
|
||||||
|
|
||||||
pip uninstall django-polls
|
pip uninstall django-polls
|
||||||
|
|
||||||
.. _bsdtar: http://gnuwin32.sourceforge.net/packages/bsdtar.htm
|
|
||||||
.. _7-zip: http://www.7-zip.org/
|
|
||||||
.. _pip: http://pypi.python.org/pypi/pip
|
.. _pip: http://pypi.python.org/pypi/pip
|
||||||
|
|
||||||
Publishing your app
|
Publishing your app
|
||||||
|
|
|
@ -319,7 +319,7 @@ Before we try to fix anything, let's have a look at the tools at our disposal.
|
||||||
The Django test client
|
The Django test client
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
Django provides a test :class:`~django.test.client.Client` to simulate a user
|
Django provides a test :class:`~django.test.Client` to simulate a user
|
||||||
interacting with the code at the view level. We can use it in ``tests.py``
|
interacting with the code at the view level. We can use it in ``tests.py``
|
||||||
or even in the shell.
|
or even in the shell.
|
||||||
|
|
||||||
|
@ -341,7 +341,7 @@ Next we need to import the test client class (later in ``tests.py`` we will use
|
||||||
the :class:`django.test.TestCase` class, which comes with its own client, so
|
the :class:`django.test.TestCase` class, which comes with its own client, so
|
||||||
this won't be required)::
|
this won't be required)::
|
||||||
|
|
||||||
>>> from django.test.client import Client
|
>>> from django.test import Client
|
||||||
>>> # create an instance of the client for our use
|
>>> # create an instance of the client for our use
|
||||||
>>> client = Client()
|
>>> client = Client()
|
||||||
|
|
||||||
|
@ -494,7 +494,7 @@ class::
|
||||||
"""
|
"""
|
||||||
The questions index page may display multiple questions.
|
The questions index page may display multiple questions.
|
||||||
"""
|
"""
|
||||||
create_question(question_text="Past quesiton 1.", days=-30)
|
create_question(question_text="Past question 1.", days=-30)
|
||||||
create_question(question_text="Past question 2.", days=-5)
|
create_question(question_text="Past question 2.", days=-5)
|
||||||
response = self.client.get(reverse('polls:index'))
|
response = self.client.get(reverse('polls:index'))
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
|
|
|
@ -142,7 +142,7 @@ Methods
|
||||||
.. versionchanged:: 1.6
|
.. versionchanged:: 1.6
|
||||||
|
|
||||||
In Django 1.4 and 1.5, a blank string was unintentionally stored
|
In Django 1.4 and 1.5, a blank string was unintentionally stored
|
||||||
as an unsable password.
|
as an unusable password.
|
||||||
|
|
||||||
.. method:: check_password(raw_password)
|
.. method:: check_password(raw_password)
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@ needs to be customized so that SQLite knows to build the R*Tree module::
|
||||||
__ http://www.sqlite.org/rtree.html
|
__ http://www.sqlite.org/rtree.html
|
||||||
__ http://www.sqlite.org/download.html
|
__ http://www.sqlite.org/download.html
|
||||||
|
|
||||||
.. _spatialitebuild :
|
.. _spatialitebuild:
|
||||||
|
|
||||||
SpatiaLite library (``libspatialite``) and tools (``spatialite``)
|
SpatiaLite library (``libspatialite``) and tools (``spatialite``)
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
@ -74,15 +74,17 @@ customization of the ``configure`` command is necessary. If not, then run the
|
||||||
``configure`` script, make, and install for the SpatiaLite library::
|
``configure`` script, make, and install for the SpatiaLite library::
|
||||||
|
|
||||||
$ cd libspatialite-amalgamation-2.3.1
|
$ cd libspatialite-amalgamation-2.3.1
|
||||||
$ ./configure # May need to modified, see notes below.
|
$ ./configure # May need to be modified, see notes below.
|
||||||
$ make
|
$ make
|
||||||
$ sudo make install
|
$ sudo make install
|
||||||
$ cd .... _spatialite
|
$ cd ..
|
||||||
|
|
||||||
|
.. _spatialite_tools:
|
||||||
|
|
||||||
Finally, do the same for the SpatiaLite tools::
|
Finally, do the same for the SpatiaLite tools::
|
||||||
|
|
||||||
$ cd spatialite-tools-2.3.1
|
$ cd spatialite-tools-2.3.1
|
||||||
$ ./configure # May need to modified, see notes below.
|
$ ./configure # May need to be modified, see notes below.
|
||||||
$ make
|
$ make
|
||||||
$ sudo make install
|
$ sudo make install
|
||||||
$ cd ..
|
$ cd ..
|
||||||
|
|
|
@ -1638,6 +1638,15 @@ Examples::
|
||||||
management.call_command('flush', verbosity=0, interactive=False)
|
management.call_command('flush', verbosity=0, interactive=False)
|
||||||
management.call_command('loaddata', 'test_data', verbosity=0)
|
management.call_command('loaddata', 'test_data', verbosity=0)
|
||||||
|
|
||||||
|
Note that command options that take no arguments are passed as keywords
|
||||||
|
with ``True`` or ``False``::
|
||||||
|
|
||||||
|
management.call_command('dumpdata', use_natural_keys=True)
|
||||||
|
|
||||||
|
Command options which take multiple options are passed a list::
|
||||||
|
|
||||||
|
management.call_command('dumpdata', exclude=['contenttypes', 'auth'])
|
||||||
|
|
||||||
Output redirection
|
Output redirection
|
||||||
==================
|
==================
|
||||||
|
|
||||||
|
|
|
@ -79,6 +79,20 @@ GZip middleware
|
||||||
|
|
||||||
.. class:: GZipMiddleware
|
.. class:: GZipMiddleware
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
Security researchers recently revealed that when compression techniques
|
||||||
|
(including ``GZipMiddleware``) are used on a website, the site becomes
|
||||||
|
exposed to a number of possible attacks. These approaches can be used to
|
||||||
|
compromise, amongst other things, Django's CSRF protection. Before using
|
||||||
|
``GZipMiddleware`` on your site, you should consider very carefully whether
|
||||||
|
you are subject to these attacks. If you're in *any* doubt about whether
|
||||||
|
you're affected, you should avoid using ``GZipMiddleware``. For more
|
||||||
|
details, see the `the BREACH paper (PDF)`_ and `breachattack.com`_.
|
||||||
|
|
||||||
|
.. _the BREACH paper (PDF): http://breachattack.com/resources/BREACH%20-%20SSL,%20gone%20in%2030%20seconds.pdf
|
||||||
|
.. _breachattack.com: http://breachattack.com
|
||||||
|
|
||||||
Compresses content for browsers that understand GZip compression (all modern
|
Compresses content for browsers that understand GZip compression (all modern
|
||||||
browsers).
|
browsers).
|
||||||
|
|
||||||
|
|
|
@ -221,6 +221,12 @@ Django quotes column and table names behind the scenes.
|
||||||
|
|
||||||
ordering = ['-pub_date', 'author']
|
ordering = ['-pub_date', 'author']
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
Ordering is not a free operation. Each field you add to the ordering
|
||||||
|
incurs a cost to your database. Each foreign key you add will
|
||||||
|
implicitly include all of its default orderings as well.
|
||||||
|
|
||||||
``permissions``
|
``permissions``
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
|
|
@ -334,6 +334,12 @@ You can tell if a query is ordered or not by checking the
|
||||||
:attr:`.QuerySet.ordered` attribute, which will be ``True`` if the
|
:attr:`.QuerySet.ordered` attribute, which will be ``True`` if the
|
||||||
``QuerySet`` has been ordered in any way.
|
``QuerySet`` has been ordered in any way.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
Ordering is not a free operation. Each field you add to the ordering
|
||||||
|
incurs a cost to your database. Each foreign key you add will
|
||||||
|
implicitly include all of its default orderings as well.
|
||||||
|
|
||||||
reverse
|
reverse
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
|
|
||||||
|
@ -2268,7 +2274,8 @@ SQL equivalent::
|
||||||
(The exact SQL syntax varies for each database engine.)
|
(The exact SQL syntax varies for each database engine.)
|
||||||
|
|
||||||
When :setting:`USE_TZ` is ``True``, datetime fields are converted to the
|
When :setting:`USE_TZ` is ``True``, datetime fields are converted to the
|
||||||
current time zone before filtering.
|
current time zone before filtering. This requires :ref:`time zone definitions
|
||||||
|
in the database <database-time-zone-definitions>`.
|
||||||
|
|
||||||
.. fieldlookup:: day
|
.. fieldlookup:: day
|
||||||
|
|
||||||
|
@ -2291,7 +2298,8 @@ Note this will match any record with a pub_date on the third day of the month,
|
||||||
such as January 3, July 3, etc.
|
such as January 3, July 3, etc.
|
||||||
|
|
||||||
When :setting:`USE_TZ` is ``True``, datetime fields are converted to the
|
When :setting:`USE_TZ` is ``True``, datetime fields are converted to the
|
||||||
current time zone before filtering.
|
current time zone before filtering. This requires :ref:`time zone definitions
|
||||||
|
in the database <database-time-zone-definitions>`.
|
||||||
|
|
||||||
.. fieldlookup:: week_day
|
.. fieldlookup:: week_day
|
||||||
|
|
||||||
|
@ -2315,7 +2323,8 @@ Note this will match any record with a ``pub_date`` that falls on a Monday (day
|
||||||
are indexed with day 1 being Sunday and day 7 being Saturday.
|
are indexed with day 1 being Sunday and day 7 being Saturday.
|
||||||
|
|
||||||
When :setting:`USE_TZ` is ``True``, datetime fields are converted to the
|
When :setting:`USE_TZ` is ``True``, datetime fields are converted to the
|
||||||
current time zone before filtering.
|
current time zone before filtering. This requires :ref:`time zone definitions
|
||||||
|
in the database <database-time-zone-definitions>`.
|
||||||
|
|
||||||
.. fieldlookup:: hour
|
.. fieldlookup:: hour
|
||||||
|
|
||||||
|
|
|
@ -565,7 +565,7 @@ setting_changed
|
||||||
|
|
||||||
This signal is sent when the value of a setting is changed through the
|
This signal is sent when the value of a setting is changed through the
|
||||||
``django.test.TestCase.settings()`` context manager or the
|
``django.test.TestCase.settings()`` context manager or the
|
||||||
:func:`django.test.utils.override_settings` decorator/context manager.
|
:func:`django.test.override_settings` decorator/context manager.
|
||||||
|
|
||||||
It's actually sent twice: when the new value is applied ("setup") and when the
|
It's actually sent twice: when the new value is applied ("setup") and when the
|
||||||
original value is restored ("teardown"). Use the ``enter`` argument to
|
original value is restored ("teardown"). Use the ``enter`` argument to
|
||||||
|
|
|
@ -2226,7 +2226,7 @@ If ``value`` is ``"http://www.example.org/"``, the output will be
|
||||||
urlize
|
urlize
|
||||||
^^^^^^
|
^^^^^^
|
||||||
|
|
||||||
Converts URLs in text into clickable links.
|
Converts URLs and email addresses in text into clickable links.
|
||||||
|
|
||||||
This template tag works on links prefixed with ``http://``, ``https://``, or
|
This template tag works on links prefixed with ``http://``, ``https://``, or
|
||||||
``www.``. For example, ``http://goo.gl/aia1t`` will get converted but
|
``www.``. For example, ``http://goo.gl/aia1t`` will get converted but
|
||||||
|
@ -2250,6 +2250,11 @@ If ``value`` is ``"Check out www.djangoproject.com"``, the output will be
|
||||||
``"Check out <a href="http://www.djangoproject.com"
|
``"Check out <a href="http://www.djangoproject.com"
|
||||||
rel="nofollow">www.djangoproject.com</a>"``.
|
rel="nofollow">www.djangoproject.com</a>"``.
|
||||||
|
|
||||||
|
In addition to web links, ``urlize`` also converts email addresses into
|
||||||
|
``mailto:`` links. If ``value`` is
|
||||||
|
``"Send questions to foo@example.com"``, the output will be
|
||||||
|
``"Send questions to <a href="mailto:foo@example.com">foo@example</a>"``.
|
||||||
|
|
||||||
The ``urlize`` filter also takes an optional parameter ``autoescape``. If
|
The ``urlize`` filter also takes an optional parameter ``autoescape``. If
|
||||||
``autoescape`` is ``True``, the link text and URLs will be escaped using
|
``autoescape`` is ``True``, the link text and URLs will be escaped using
|
||||||
Django's built-in :tfilter:`escape` filter. The default value for
|
Django's built-in :tfilter:`escape` filter. The default value for
|
||||||
|
@ -2265,7 +2270,7 @@ Django's built-in :tfilter:`escape` filter. The default value for
|
||||||
urlizetrunc
|
urlizetrunc
|
||||||
^^^^^^^^^^^
|
^^^^^^^^^^^
|
||||||
|
|
||||||
Converts URLs into clickable links just like urlize_, but truncates URLs
|
Converts URLs and email addresses into clickable links just like urlize_, but truncates URLs
|
||||||
longer than the given character limit.
|
longer than the given character limit.
|
||||||
|
|
||||||
**Argument:** Number of characters that link text should be truncated to,
|
**Argument:** Number of characters that link text should be truncated to,
|
||||||
|
|
|
@ -927,6 +927,17 @@ For a complete discussion on the usage of the following see the
|
||||||
|
|
||||||
:class:`~datetime.tzinfo` instance that represents UTC.
|
:class:`~datetime.tzinfo` instance that represents UTC.
|
||||||
|
|
||||||
|
.. function:: get_fixed_timezone(offset)
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
|
Returns a :class:`~datetime.tzinfo` instance that represents a time zone
|
||||||
|
with a fixed offset from UTC.
|
||||||
|
|
||||||
|
``offset`` is a :class:`datetime.timedelta` or an integer number of
|
||||||
|
minutes. Use positive values for time zones east of UTC and negative
|
||||||
|
values for west of UTC.
|
||||||
|
|
||||||
.. function:: get_default_timezone()
|
.. function:: get_default_timezone()
|
||||||
|
|
||||||
Returns a :class:`~datetime.tzinfo` instance that represents the
|
Returns a :class:`~datetime.tzinfo` instance that represents the
|
||||||
|
@ -1021,6 +1032,9 @@ For a complete discussion on the usage of the following see the
|
||||||
``django.utils.tzinfo``
|
``django.utils.tzinfo``
|
||||||
=======================
|
=======================
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
Use :mod:`~django.utils.timezone` instead.
|
||||||
|
|
||||||
.. module:: django.utils.tzinfo
|
.. module:: django.utils.tzinfo
|
||||||
:synopsis: Implementation of ``tzinfo`` classes for use with ``datetime.datetime``.
|
:synopsis: Implementation of ``tzinfo`` classes for use with ``datetime.datetime``.
|
||||||
|
|
||||||
|
@ -1028,6 +1042,12 @@ For a complete discussion on the usage of the following see the
|
||||||
|
|
||||||
Fixed offset in minutes east from UTC.
|
Fixed offset in minutes east from UTC.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
Use :func:`~django.utils.timezone.get_fixed_timezone` instead.
|
||||||
|
|
||||||
.. class:: LocalTimezone
|
.. class:: LocalTimezone
|
||||||
|
|
||||||
Proxy timezone information from time module.
|
Proxy timezone information from time module.
|
||||||
|
|
||||||
|
.. deprecated:: 1.7
|
||||||
|
Use :func:`~django.utils.timezone.get_default_timezone` instead.
|
||||||
|
|
|
@ -643,7 +643,7 @@ The generic relation classes -- ``GenericForeignKey`` and ``GenericRelation``
|
||||||
Testing
|
Testing
|
||||||
-------
|
-------
|
||||||
|
|
||||||
:meth:`django.test.client.Client.login` has changed
|
:meth:`django.test.Client.login` has changed
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Old (0.96)::
|
Old (0.96)::
|
||||||
|
|
|
@ -99,7 +99,7 @@ one fell swoop.
|
||||||
Testing improvements
|
Testing improvements
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
.. currentmodule:: django.test.client
|
.. currentmodule:: django.test
|
||||||
|
|
||||||
A couple of small but very useful improvements have been made to the
|
A couple of small but very useful improvements have been made to the
|
||||||
:doc:`testing framework </topics/testing/index>`:
|
:doc:`testing framework </topics/testing/index>`:
|
||||||
|
|
|
@ -285,8 +285,6 @@ full description, and some important notes on database support.
|
||||||
Test client improvements
|
Test client improvements
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
.. currentmodule:: django.test.client
|
|
||||||
|
|
||||||
A couple of small -- but highly useful -- improvements have been made to the
|
A couple of small -- but highly useful -- improvements have been made to the
|
||||||
test client:
|
test client:
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ case of Django 1.2.2, we have made an exception to this rule.
|
||||||
|
|
||||||
In order to test a bug fix that forms part of the 1.2.2 release, it
|
In order to test a bug fix that forms part of the 1.2.2 release, it
|
||||||
was necessary to add a feature -- the ``enforce_csrf_checks`` flag --
|
was necessary to add a feature -- the ``enforce_csrf_checks`` flag --
|
||||||
to the :mod:`test client <django.test.client>`. This flag forces
|
to the :ref:`test client <test-client>`. This flag forces
|
||||||
the test client to perform full CSRF checks on forms. The default
|
the test client to perform full CSRF checks on forms. The default
|
||||||
behavior of the test client hasn't changed, but if you want to do
|
behavior of the test client hasn't changed, but if you want to do
|
||||||
CSRF checks with the test client, it is now possible to do so.
|
CSRF checks with the test client, it is now possible to do so.
|
||||||
|
|
|
@ -150,7 +150,7 @@ requests. These include:
|
||||||
* Improved tools for accessing and manipulating the current Site via
|
* Improved tools for accessing and manipulating the current Site via
|
||||||
``django.contrib.sites.models.get_current_site()``.
|
``django.contrib.sites.models.get_current_site()``.
|
||||||
|
|
||||||
* A :class:`~django.test.client.RequestFactory` for mocking
|
* A :class:`~django.test.RequestFactory` for mocking
|
||||||
requests in tests.
|
requests in tests.
|
||||||
|
|
||||||
* A new test assertion --
|
* A new test assertion --
|
||||||
|
@ -318,7 +318,7 @@ Test client response ``template`` attribute
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Django's :ref:`test client <test-client>` returns
|
Django's :ref:`test client <test-client>` returns
|
||||||
:class:`~django.test.client.Response` objects annotated with extra testing
|
:class:`~django.test.Response` objects annotated with extra testing
|
||||||
information. In Django versions prior to 1.3, this included a ``template``
|
information. In Django versions prior to 1.3, this included a ``template``
|
||||||
attribute containing information about templates rendered in generating the
|
attribute containing information about templates rendered in generating the
|
||||||
response: either None, a single :class:`~django.template.Template` object, or a
|
response: either None, a single :class:`~django.template.Template` object, or a
|
||||||
|
@ -327,7 +327,7 @@ return values (sometimes a list, sometimes not) made the attribute difficult
|
||||||
to work with.
|
to work with.
|
||||||
|
|
||||||
In Django 1.3 the ``template`` attribute is deprecated in favor of a new
|
In Django 1.3 the ``template`` attribute is deprecated in favor of a new
|
||||||
:attr:`~django.test.client.Response.templates` attribute, which is always a
|
:attr:`~django.test.Response.templates` attribute, which is always a
|
||||||
list, even if it has only a single element or no elements.
|
list, even if it has only a single element or no elements.
|
||||||
|
|
||||||
``DjangoTestRunner``
|
``DjangoTestRunner``
|
||||||
|
|
|
@ -295,7 +295,7 @@ requests. These include:
|
||||||
:class:`~django.contrib.sites.models.Site` object in
|
:class:`~django.contrib.sites.models.Site` object in
|
||||||
:doc:`the sites framework </ref/contrib/sites>`.
|
:doc:`the sites framework </ref/contrib/sites>`.
|
||||||
|
|
||||||
* A :class:`~django.test.client.RequestFactory` for mocking requests
|
* A :class:`~django.test.RequestFactory` for mocking requests
|
||||||
in tests.
|
in tests.
|
||||||
|
|
||||||
* A new test assertion --
|
* A new test assertion --
|
||||||
|
@ -715,7 +715,7 @@ Test client response ``template`` attribute
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Django's :ref:`test client <test-client>` returns
|
Django's :ref:`test client <test-client>` returns
|
||||||
:class:`~django.test.client.Response` objects annotated with extra testing
|
:class:`~django.test.Response` objects annotated with extra testing
|
||||||
information. In Django versions prior to 1.3, this included a ``template``
|
information. In Django versions prior to 1.3, this included a ``template``
|
||||||
attribute containing information about templates rendered in generating the
|
attribute containing information about templates rendered in generating the
|
||||||
response: either None, a single :class:`~django.template.Template` object, or a
|
response: either None, a single :class:`~django.template.Template` object, or a
|
||||||
|
@ -724,7 +724,7 @@ return values (sometimes a list, sometimes not) made the attribute difficult
|
||||||
to work with.
|
to work with.
|
||||||
|
|
||||||
In Django 1.3 the ``template`` attribute is deprecated in favor of a new
|
In Django 1.3 the ``template`` attribute is deprecated in favor of a new
|
||||||
:attr:`~django.test.client.Response.templates` attribute, which is always a
|
:attr:`~django.test.Response.templates` attribute, which is always a
|
||||||
list, even if it has only a single element or no elements.
|
list, even if it has only a single element or no elements.
|
||||||
|
|
||||||
``DjangoTestRunner``
|
``DjangoTestRunner``
|
||||||
|
|
|
@ -26,6 +26,6 @@ header and browsers seem to ignore JavaScript there.
|
||||||
Bugfixes
|
Bugfixes
|
||||||
========
|
========
|
||||||
|
|
||||||
* Fixed an obscure bug with the :func:`~django.test.utils.override_settings`
|
* Fixed an obscure bug with the :func:`~django.test.override_settings`
|
||||||
decorator. If you hit an ``AttributeError: 'Settings' object has no attribute
|
decorator. If you hit an ``AttributeError: 'Settings' object has no attribute
|
||||||
'_original_allowed_hosts'`` exception, it's probably fixed (#20636).
|
'_original_allowed_hosts'`` exception, it's probably fixed (#20636).
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
==========================
|
||||||
|
Django 1.4.7 release notes
|
||||||
|
==========================
|
||||||
|
|
||||||
|
*September 10, 2013*
|
||||||
|
|
||||||
|
Django 1.4.7 fixes one security issue present in previous Django releases in
|
||||||
|
the 1.4 series.
|
||||||
|
|
||||||
|
Directory traversal vulnerability in :ttag:`ssi` template tag
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
In previous versions of Django it was possible to bypass the
|
||||||
|
:setting:`ALLOWED_INCLUDE_ROOTS` setting used for security with the :ttag:`ssi`
|
||||||
|
template tag by specifying a relative path that starts with one of the allowed
|
||||||
|
roots. For example, if ``ALLOWED_INCLUDE_ROOTS = ("/var/www",)`` the following
|
||||||
|
would be possible:
|
||||||
|
|
||||||
|
.. code-block:: html+django
|
||||||
|
|
||||||
|
{% ssi "/var/www/../../etc/passwd" %}
|
||||||
|
|
||||||
|
In practice this is not a very common problem, as it would require the template
|
||||||
|
author to put the :ttag:`ssi` file in a user-controlled variable, but it's
|
||||||
|
possible in principle.
|
|
@ -57,6 +57,6 @@ Bugfixes
|
||||||
* Ensured that the WSGI request's path is correctly based on the
|
* Ensured that the WSGI request's path is correctly based on the
|
||||||
``SCRIPT_NAME`` environment variable or the :setting:`FORCE_SCRIPT_NAME`
|
``SCRIPT_NAME`` environment variable or the :setting:`FORCE_SCRIPT_NAME`
|
||||||
setting, regardless of whether or not either has a trailing slash (#20169).
|
setting, regardless of whether or not either has a trailing slash (#20169).
|
||||||
* Fixed an obscure bug with the :func:`~django.test.utils.override_settings`
|
* Fixed an obscure bug with the :func:`~django.test.override_settings`
|
||||||
decorator. If you hit an ``AttributeError: 'Settings' object has no attribute
|
decorator. If you hit an ``AttributeError: 'Settings' object has no attribute
|
||||||
'_original_allowed_hosts'`` exception, it's probably fixed (#20636).
|
'_original_allowed_hosts'`` exception, it's probably fixed (#20636).
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
==========================
|
||||||
|
Django 1.5.3 release notes
|
||||||
|
==========================
|
||||||
|
|
||||||
|
*September 10, 2013*
|
||||||
|
|
||||||
|
This is Django 1.5.3, the third release in the Django 1.5 series. It addresses
|
||||||
|
one security issue and also contains an opt-in feature to enhance the security
|
||||||
|
of :mod:`django.contrib.sessions`.
|
||||||
|
|
||||||
|
Directory traversal vulnerability in :ttag:`ssi` template tag
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
In previous versions of Django it was possible to bypass the
|
||||||
|
:setting:`ALLOWED_INCLUDE_ROOTS` setting used for security with the :ttag:`ssi`
|
||||||
|
template tag by specifying a relative path that starts with one of the allowed
|
||||||
|
roots. For example, if ``ALLOWED_INCLUDE_ROOTS = ("/var/www",)`` the following
|
||||||
|
would be possible:
|
||||||
|
|
||||||
|
.. code-block:: html+django
|
||||||
|
|
||||||
|
{% ssi "/var/www/../../etc/passwd" %}
|
||||||
|
|
||||||
|
In practice this is not a very common problem, as it would require the template
|
||||||
|
author to put the :ttag:`ssi` file in a user-controlled variable, but it's
|
||||||
|
possible in principle.
|
||||||
|
|
||||||
|
Mitigating a remote-code execution vulnerability in :mod:`django.contrib.sessions`
|
||||||
|
----------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
:mod:`django.contrib.sessions` currently uses :mod:`pickle` to serialize
|
||||||
|
session data before storing it in the backend. If you're using the :ref:`signed
|
||||||
|
cookie session backend<cookie-session-backend>` and :setting:`SECRET_KEY` is
|
||||||
|
known by an attacker (there isn't an inherent vulnerability in Django that
|
||||||
|
would cause it to leak), the attacker could insert a string into his session
|
||||||
|
which, when unpickled, executes arbitrary code on the server. The technique for
|
||||||
|
doing so is simple and easily available on the internet. Although the cookie
|
||||||
|
session storage signs the cookie-stored data to prevent tampering, a
|
||||||
|
:setting:`SECRET_KEY` leak immediately escalates to a remote code execution
|
||||||
|
vulnerability.
|
||||||
|
|
||||||
|
This attack can be mitigated by serializing session data using JSON rather
|
||||||
|
than :mod:`pickle`. To facilitate this, Django 1.5.3 introduces a new setting,
|
||||||
|
:setting:`SESSION_SERIALIZER`, to customize the session serialization format.
|
||||||
|
For backwards compatibility, this setting defaults to using :mod:`pickle`.
|
||||||
|
While JSON serialization does not support all Python objects like :mod:`pickle`
|
||||||
|
does, we highly recommend switching to JSON-serialized values. Also,
|
||||||
|
as JSON requires string keys, you will likely run into problems if you are
|
||||||
|
using non-string keys in ``request.session``. See the
|
||||||
|
:ref:`session_serialization` documentation for more details.
|
|
@ -435,6 +435,21 @@ but will not be removed from Django until version 1.8.
|
||||||
|
|
||||||
.. _recommendations in the Python documentation: http://docs.python.org/2/library/doctest.html#unittest-api
|
.. _recommendations in the Python documentation: http://docs.python.org/2/library/doctest.html#unittest-api
|
||||||
|
|
||||||
|
Time zone-aware ``day``, ``month``, and ``week_day`` lookups
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Django 1.6 introduces time zone support for :lookup:`day`, :lookup:`month`,
|
||||||
|
and :lookup:`week_day` lookups when :setting:`USE_TZ` is ``True``. These
|
||||||
|
lookups were previously performed in UTC regardless of the current time zone.
|
||||||
|
|
||||||
|
This requires :ref:`time zone definitions in the database
|
||||||
|
<database-time-zone-definitions>`. If you're using SQLite, you must install
|
||||||
|
pytz_. If you're using MySQL, you must install pytz_ and load the time zone
|
||||||
|
tables with `mysql_tzinfo_to_sql`_.
|
||||||
|
|
||||||
|
.. _pytz: http://pytz.sourceforge.net/
|
||||||
|
.. _mysql_tzinfo_to_sql: http://dev.mysql.com/doc/refman/5.5/en/mysql-tzinfo-to-sql.html
|
||||||
|
|
||||||
Addition of ``QuerySet.datetimes()``
|
Addition of ``QuerySet.datetimes()``
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -814,7 +829,7 @@ Miscellaneous
|
||||||
``{% url %}`` tag, it causes template rendering to fail like always when
|
``{% url %}`` tag, it causes template rendering to fail like always when
|
||||||
``NoReverseMatch`` is raised.
|
``NoReverseMatch`` is raised.
|
||||||
|
|
||||||
* :meth:`django.test.client.Client.logout` now calls
|
* :meth:`django.test.Client.logout` now calls
|
||||||
:meth:`django.contrib.auth.logout` which will send the
|
:meth:`django.contrib.auth.logout` which will send the
|
||||||
:func:`~django.contrib.auth.signals.user_logged_out` signal.
|
:func:`~django.contrib.auth.signals.user_logged_out` signal.
|
||||||
|
|
||||||
|
@ -1046,12 +1061,12 @@ security problem described in the section above, because they can automatically
|
||||||
create a ``ModelForm`` that uses all fields for a model.
|
create a ``ModelForm`` that uses all fields for a model.
|
||||||
|
|
||||||
For this reason, if you use these views for editing models, you must also supply
|
For this reason, if you use these views for editing models, you must also supply
|
||||||
the ``fields`` attribute, which is a list of model fields and works in the same
|
the ``fields`` attribute (new in Django 1.6), which is a list of model fields
|
||||||
way as the :class:`~django.forms.ModelForm` ``Meta.fields`` attribute. Alternatively,
|
and works in the same way as the :class:`~django.forms.ModelForm`
|
||||||
you can set set the ``form_class`` attribute to a ``ModelForm`` that explicitly
|
``Meta.fields`` attribute. Alternatively, you can set set the ``form_class``
|
||||||
defines the fields to be used. Defining an ``UpdateView`` or ``CreateView``
|
attribute to a ``ModelForm`` that explicitly defines the fields to be used.
|
||||||
subclass to be used with a model but without an explicit list of fields is
|
Defining an ``UpdateView`` or ``CreateView`` subclass to be used with a model
|
||||||
deprecated.
|
but without an explicit list of fields is deprecated.
|
||||||
|
|
||||||
.. _m2m-help_text-deprecation:
|
.. _m2m-help_text-deprecation:
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,7 @@ but a few of the key features are:
|
||||||
* A new ``makemigrations`` command provides an easy way to autodetect changes
|
* A new ``makemigrations`` command provides an easy way to autodetect changes
|
||||||
to your models and make migrations for them.
|
to your models and make migrations for them.
|
||||||
|
|
||||||
* :data:`~django.db.models.signals.post_syncdb` and
|
* :data:`~django.db.models.signals.pre_syncdb` and
|
||||||
:data:`~django.db.models.signals.post_syncdb` have been renamed to
|
:data:`~django.db.models.signals.post_syncdb` have been renamed to
|
||||||
:data:`~django.db.models.signals.pre_migrate` and
|
:data:`~django.db.models.signals.pre_migrate` and
|
||||||
:data:`~django.db.models.signals.post_migrate` respectively. The
|
:data:`~django.db.models.signals.post_migrate` respectively. The
|
||||||
|
@ -285,6 +285,19 @@ Templates
|
||||||
* ``TypeError`` exceptions are not longer silenced when raised during the
|
* ``TypeError`` exceptions are not longer silenced when raised during the
|
||||||
rendering of a template.
|
rendering of a template.
|
||||||
|
|
||||||
|
Tests
|
||||||
|
^^^^^
|
||||||
|
|
||||||
|
* :class:`~django.test.runner.DiscoverRunner` has two new attributes,
|
||||||
|
:attr:`~django.test.runner.DiscoverRunner.test_suite` and
|
||||||
|
:attr:`~django.test.runner.DiscoverRunner.test_runner`, which facilitate
|
||||||
|
overriding the way tests are collected and run.
|
||||||
|
|
||||||
|
* The ``fetch_redirect_response`` argument was added to
|
||||||
|
:meth:`~django.test.SimpleTestCase.assertRedirects`. Since the test
|
||||||
|
client can't fetch externals URLs, this allows you to use ``assertRedirects``
|
||||||
|
with redirects that aren't part of your Django app.
|
||||||
|
|
||||||
Backwards incompatible changes in 1.7
|
Backwards incompatible changes in 1.7
|
||||||
=====================================
|
=====================================
|
||||||
|
|
||||||
|
@ -308,6 +321,16 @@ For apps with migrations, ``allow_migrate`` will now get passed
|
||||||
without custom attributes, methods or managers. Make sure your ``allow_migrate``
|
without custom attributes, methods or managers. Make sure your ``allow_migrate``
|
||||||
methods are only referring to fields or other items in ``model._meta``.
|
methods are only referring to fields or other items in ``model._meta``.
|
||||||
|
|
||||||
|
pytz may be required
|
||||||
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
If your project handles datetimes before 1970 or after 2037 and Django raises
|
||||||
|
a :exc:`~exceptions.ValueError` when encountering them, you will have to
|
||||||
|
install pytz_. You may be affected by this problem if you use Django's time
|
||||||
|
zone-related date formats or :mod:`django.contrib.syndication`.
|
||||||
|
|
||||||
|
.. _pytz: https://pypi.python.org/pypi/pytz/
|
||||||
|
|
||||||
Miscellaneous
|
Miscellaneous
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -345,13 +368,18 @@ Miscellaneous
|
||||||
when called on an instance without a primary key value. This is done to
|
when called on an instance without a primary key value. This is done to
|
||||||
avoid mutable ``__hash__`` values in containers.
|
avoid mutable ``__hash__`` values in containers.
|
||||||
|
|
||||||
* The :meth:`django.db.backends.sqlite3.DatabaseCreation.sql_create_model`
|
* :class:`~django.db.models.AutoField` columns in SQLite databases will now be
|
||||||
will now create :class:`~django.db.models.AutoField` columns in SQLite
|
created using the ``AUTOINCREMENT`` option, which guarantees monotonic
|
||||||
databases using the ``AUTOINCREMENT`` option, which guarantees monotonic
|
|
||||||
increments. This will cause primary key numbering behavior to change on
|
increments. This will cause primary key numbering behavior to change on
|
||||||
SQLite, becoming consistent with most other SQL databases. If you have a
|
SQLite, becoming consistent with most other SQL databases. This will only
|
||||||
database created with an older version of Django, you will need to migrate
|
apply to newly created tables. If you have a database created with an older
|
||||||
it to take advantage of this feature. See ticket #10164 for details.
|
version of Django, you will need to migrate it to take advantage of this
|
||||||
|
feature. For example, you could do the following:
|
||||||
|
|
||||||
|
#) Use :djadmin:`dumpdata` to save your data.
|
||||||
|
#) Rename the existing database file (keep it as a backup).
|
||||||
|
#) Run :djadmin:`migrate` to create the updated schema.
|
||||||
|
#) Use :djadmin:`loaddata` to import the fixtures you exported in (1).
|
||||||
|
|
||||||
* ``django.contrib.auth.models.AbstractUser`` no longer defines a
|
* ``django.contrib.auth.models.AbstractUser`` no longer defines a
|
||||||
:meth:`~django.db.models.Model.get_absolute_url()` method. The old definition
|
:meth:`~django.db.models.Model.get_absolute_url()` method. The old definition
|
||||||
|
@ -384,6 +412,15 @@ Features deprecated in 1.7
|
||||||
respectively :mod:`logging.config` and :mod:`importlib` provided for Python
|
respectively :mod:`logging.config` and :mod:`importlib` provided for Python
|
||||||
versions prior to 2.7. They have been deprecated.
|
versions prior to 2.7. They have been deprecated.
|
||||||
|
|
||||||
|
``django.utils.tzinfo``
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
``django.utils.tzinfo`` provided two :class:`~datetime.tzinfo` subclasses,
|
||||||
|
``LocalTimezone`` and ``FixedOffset``. They've been deprecated in favor of
|
||||||
|
more correct alternatives provided by :mod:`django.utils.timezone`,
|
||||||
|
:func:`django.utils.timezone.get_default_timezone` and
|
||||||
|
:func:`django.utils.timezone.get_fixed_timezone`.
|
||||||
|
|
||||||
``django.utils.unittest``
|
``django.utils.unittest``
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,7 @@ Final releases
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
|
1.5.3
|
||||||
1.5.2
|
1.5.2
|
||||||
1.5.1
|
1.5.1
|
||||||
1.5
|
1.5
|
||||||
|
@ -45,6 +46,7 @@ Final releases
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
|
1.4.7
|
||||||
1.4.6
|
1.4.6
|
||||||
1.4.5
|
1.4.5
|
||||||
1.4.4
|
1.4.4
|
||||||
|
|
|
@ -869,8 +869,7 @@ would test three possible User models -- the default, plus the two User
|
||||||
models provided by ``auth`` app::
|
models provided by ``auth`` app::
|
||||||
|
|
||||||
from django.contrib.auth.tests.utils import skipIfCustomUser
|
from django.contrib.auth.tests.utils import skipIfCustomUser
|
||||||
from django.test import TestCase
|
from django.test import TestCase, override_settings
|
||||||
from django.test.utils import override_settings
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationTestCase(TestCase):
|
class ApplicationTestCase(TestCase):
|
||||||
|
|
|
@ -341,40 +341,37 @@ been well-tested and are easy to use.
|
||||||
Cache arguments
|
Cache arguments
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
In addition to the defining the engine and name of the each cache
|
Each cache backend can be given additional arguments to control caching
|
||||||
backend, each cache backend can be given additional arguments to
|
behavior. These arguments are provided as additional keys in the
|
||||||
control caching behavior. These arguments are provided as additional
|
:setting:`CACHES` setting. Valid arguments are as follows:
|
||||||
keys in the :setting:`CACHES` setting. Valid arguments are as follows:
|
|
||||||
|
|
||||||
* :setting:`TIMEOUT <CACHES-TIMEOUT>`: The default timeout, in
|
* :setting:`TIMEOUT <CACHES-TIMEOUT>`: The default timeout, in
|
||||||
seconds, to use for the cache. This argument defaults to 300
|
seconds, to use for the cache. This argument defaults to ``300``
|
||||||
seconds (5 minutes).
|
seconds (5 minutes).
|
||||||
|
|
||||||
* :setting:`OPTIONS <CACHES-OPTIONS>`: Any options that should be
|
* :setting:`OPTIONS <CACHES-OPTIONS>`: Any options that should be
|
||||||
passed to cache backend. The list options understood by each
|
passed to the cache backend. The list of valid options will vary
|
||||||
backend vary with each backend.
|
with each backend, and cache backends backed by a third-party library
|
||||||
|
will pass their options directly to the underlying cache library.
|
||||||
|
|
||||||
Cache backends that implement their own culling strategy (i.e.,
|
Cache backends that implement their own culling strategy (i.e.,
|
||||||
the ``locmem``, ``filesystem`` and ``database`` backends) will
|
the ``locmem``, ``filesystem`` and ``database`` backends) will
|
||||||
honor the following options:
|
honor the following options:
|
||||||
|
|
||||||
* ``MAX_ENTRIES``: the maximum number of entries allowed in
|
* ``MAX_ENTRIES``: The maximum number of entries allowed in
|
||||||
the cache before old values are deleted. This argument
|
the cache before old values are deleted. This argument
|
||||||
defaults to ``300``.
|
defaults to ``300``.
|
||||||
|
|
||||||
* ``CULL_FREQUENCY``: The fraction of entries that are culled
|
* ``CULL_FREQUENCY``: The fraction of entries that are culled
|
||||||
when ``MAX_ENTRIES`` is reached. The actual ratio is
|
when ``MAX_ENTRIES`` is reached. The actual ratio is
|
||||||
``1/CULL_FREQUENCY``, so set ``CULL_FREQUENCY``: to ``2`` to
|
``1 / CULL_FREQUENCY``, so set ``CULL_FREQUENCY`` to ``2`` to
|
||||||
cull half of the entries when ``MAX_ENTRIES`` is reached.
|
cull half the entries when ``MAX_ENTRIES`` is reached. This argument
|
||||||
|
should be an integer and defaults to ``3``.
|
||||||
|
|
||||||
A value of ``0`` for ``CULL_FREQUENCY`` means that the
|
A value of ``0`` for ``CULL_FREQUENCY`` means that the
|
||||||
entire cache will be dumped when ``MAX_ENTRIES`` is reached.
|
entire cache will be dumped when ``MAX_ENTRIES`` is reached.
|
||||||
This makes culling *much* faster at the expense of more
|
On some backends (``database`` in particular) this makes culling *much*
|
||||||
cache misses.
|
faster at the expense of more cache misses.
|
||||||
|
|
||||||
Cache backends backed by a third-party library will pass their
|
|
||||||
options directly to the underlying cache library. As a result,
|
|
||||||
the list of valid options depends on the library in use.
|
|
||||||
|
|
||||||
* :setting:`KEY_PREFIX <CACHES-KEY_PREFIX>`: A string that will be
|
* :setting:`KEY_PREFIX <CACHES-KEY_PREFIX>`: A string that will be
|
||||||
automatically included (prepended by default) to all cache keys
|
automatically included (prepended by default) to all cache keys
|
||||||
|
@ -1176,7 +1173,10 @@ site's performance:
|
||||||
and ``Last-Modified`` headers.
|
and ``Last-Modified`` headers.
|
||||||
|
|
||||||
* :class:`django.middleware.gzip.GZipMiddleware` compresses responses for all
|
* :class:`django.middleware.gzip.GZipMiddleware` compresses responses for all
|
||||||
modern browsers, saving bandwidth and transfer time.
|
modern browsers, saving bandwidth and transfer time. Be warned, however,
|
||||||
|
that compression techniques like ``GZipMiddleware`` are subject to attacks.
|
||||||
|
See the warning in :class:`~django.middleware.gzip.GZipMiddleware` for
|
||||||
|
details.
|
||||||
|
|
||||||
Order of MIDDLEWARE_CLASSES
|
Order of MIDDLEWARE_CLASSES
|
||||||
===========================
|
===========================
|
||||||
|
|
|
@ -306,6 +306,17 @@ instead of::
|
||||||
|
|
||||||
entry.blog.id
|
entry.blog.id
|
||||||
|
|
||||||
|
Don't order results if you don't care
|
||||||
|
-------------------------------------
|
||||||
|
|
||||||
|
Ordering is not free; each field to order by is an operation the database must
|
||||||
|
perform. If a model has a default ordering (:attr:`Meta.ordering
|
||||||
|
<django.db.models.Options.ordering>`) and you don't need it, remove
|
||||||
|
it on a ``QuerySet`` by calling
|
||||||
|
:meth:`~django.db.models.query.QuerySet.order_by()` with no parameters.
|
||||||
|
|
||||||
|
Adding an index to your database may help to improve ordering performance.
|
||||||
|
|
||||||
Insert in bulk
|
Insert in bulk
|
||||||
==============
|
==============
|
||||||
|
|
||||||
|
|
|
@ -5,18 +5,18 @@ Advanced testing topics
|
||||||
The request factory
|
The request factory
|
||||||
===================
|
===================
|
||||||
|
|
||||||
.. module:: django.test.client
|
.. currentmodule:: django.test
|
||||||
|
|
||||||
.. class:: RequestFactory
|
.. class:: RequestFactory
|
||||||
|
|
||||||
The :class:`~django.test.client.RequestFactory` shares the same API as
|
The :class:`~django.test.RequestFactory` shares the same API as
|
||||||
the test client. However, instead of behaving like a browser, the
|
the test client. However, instead of behaving like a browser, the
|
||||||
RequestFactory provides a way to generate a request instance that can
|
RequestFactory provides a way to generate a request instance that can
|
||||||
be used as the first argument to any view. This means you can test a
|
be used as the first argument to any view. This means you can test a
|
||||||
view function the same way as you would test any other function -- as
|
view function the same way as you would test any other function -- as
|
||||||
a black box, with exactly known inputs, testing for specific outputs.
|
a black box, with exactly known inputs, testing for specific outputs.
|
||||||
|
|
||||||
The API for the :class:`~django.test.client.RequestFactory` is a slightly
|
The API for the :class:`~django.test.RequestFactory` is a slightly
|
||||||
restricted subset of the test client API:
|
restricted subset of the test client API:
|
||||||
|
|
||||||
* It only has access to the HTTP methods :meth:`~Client.get()`,
|
* It only has access to the HTTP methods :meth:`~Client.get()`,
|
||||||
|
@ -38,8 +38,7 @@ Example
|
||||||
The following is a simple unit test using the request factory::
|
The following is a simple unit test using the request factory::
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.test import TestCase
|
from django.test import TestCase, RequestFactory
|
||||||
from django.test.client import RequestFactory
|
|
||||||
|
|
||||||
class SimpleTest(TestCase):
|
class SimpleTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -165,8 +164,6 @@ exception will be raised.
|
||||||
Advanced features of ``TransactionTestCase``
|
Advanced features of ``TransactionTestCase``
|
||||||
============================================
|
============================================
|
||||||
|
|
||||||
.. currentmodule:: django.test
|
|
||||||
|
|
||||||
.. attribute:: TransactionTestCase.available_apps
|
.. attribute:: TransactionTestCase.available_apps
|
||||||
|
|
||||||
.. versionadded:: 1.6
|
.. versionadded:: 1.6
|
||||||
|
@ -341,6 +338,25 @@ execute and tear down the test suite.
|
||||||
Attributes
|
Attributes
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attribute:: DiscoverRunner.test_suite
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
|
The class used to build the test suite. By default it is set to
|
||||||
|
``unittest.TestSuite``. This can be overridden if you wish to implement
|
||||||
|
different logic for collecting tests.
|
||||||
|
|
||||||
|
.. attribute:: DiscoverRunner.test_runner
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
|
This is the class of the low-level test runner which is used to execute
|
||||||
|
the individual tests and format the results. By default it is set to
|
||||||
|
``unittest.TextTestRunner``. Despite the unfortunate similarity in
|
||||||
|
naming conventions, this is not the same type of class as
|
||||||
|
``DiscoverRunner``, which covers a broader set of responsibilites. You
|
||||||
|
can override this attribute to modify the way tests are run and reported.
|
||||||
|
|
||||||
.. attribute:: DiscoverRunner.test_loader
|
.. attribute:: DiscoverRunner.test_loader
|
||||||
|
|
||||||
This is the class that loads tests, whether from TestCases or modules or
|
This is the class that loads tests, whether from TestCases or modules or
|
||||||
|
|
|
@ -313,9 +313,6 @@ Django provides a small set of tools that come in handy when writing tests.
|
||||||
The test client
|
The test client
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
.. module:: django.test.client
|
|
||||||
:synopsis: Django's test client.
|
|
||||||
|
|
||||||
The test client is a Python class that acts as a dummy Web browser, allowing
|
The test client is a Python class that acts as a dummy Web browser, allowing
|
||||||
you to test your views and interact with your Django-powered application
|
you to test your views and interact with your Django-powered application
|
||||||
programmatically.
|
programmatically.
|
||||||
|
@ -349,10 +346,10 @@ A comprehensive test suite should use a combination of both test types.
|
||||||
Overview and a quick example
|
Overview and a quick example
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
To use the test client, instantiate ``django.test.client.Client`` and retrieve
|
To use the test client, instantiate ``django.test.Client`` and retrieve
|
||||||
Web pages::
|
Web pages::
|
||||||
|
|
||||||
>>> from django.test.client import Client
|
>>> from django.test import Client
|
||||||
>>> c = Client()
|
>>> c = Client()
|
||||||
>>> response = c.post('/login/', {'username': 'john', 'password': 'smith'})
|
>>> response = c.post('/login/', {'username': 'john', 'password': 'smith'})
|
||||||
>>> response.status_code
|
>>> response.status_code
|
||||||
|
@ -413,7 +410,7 @@ Note a few important things about how the test client works:
|
||||||
Making requests
|
Making requests
|
||||||
~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Use the ``django.test.client.Client`` class to make requests.
|
Use the ``django.test.Client`` class to make requests.
|
||||||
|
|
||||||
.. class:: Client(enforce_csrf_checks=False, **defaults)
|
.. class:: Client(enforce_csrf_checks=False, **defaults)
|
||||||
|
|
||||||
|
@ -424,8 +421,8 @@ Use the ``django.test.client.Client`` class to make requests.
|
||||||
>>> c = Client(HTTP_USER_AGENT='Mozilla/5.0')
|
>>> c = Client(HTTP_USER_AGENT='Mozilla/5.0')
|
||||||
|
|
||||||
The values from the ``extra`` keywords arguments passed to
|
The values from the ``extra`` keywords arguments passed to
|
||||||
:meth:`~django.test.client.Client.get()`,
|
:meth:`~django.test.Client.get()`,
|
||||||
:meth:`~django.test.client.Client.post()`, etc. have precedence over
|
:meth:`~django.test.Client.post()`, etc. have precedence over
|
||||||
the defaults passed to the class constructor.
|
the defaults passed to the class constructor.
|
||||||
|
|
||||||
The ``enforce_csrf_checks`` argument can be used to test CSRF
|
The ``enforce_csrf_checks`` argument can be used to test CSRF
|
||||||
|
@ -778,7 +775,7 @@ Example
|
||||||
The following is a simple unit test using the test client::
|
The following is a simple unit test using the test client::
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
from django.test.client import Client
|
from django.test import Client
|
||||||
|
|
||||||
class SimpleTest(unittest.TestCase):
|
class SimpleTest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -797,15 +794,13 @@ The following is a simple unit test using the test client::
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
|
|
||||||
:class:`django.test.client.RequestFactory`
|
:class:`django.test.RequestFactory`
|
||||||
|
|
||||||
.. _django-testcase-subclasses:
|
.. _django-testcase-subclasses:
|
||||||
|
|
||||||
Provided test case classes
|
Provided test case classes
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
.. currentmodule:: django.test
|
|
||||||
|
|
||||||
Normal Python unit test classes extend a base class of
|
Normal Python unit test classes extend a base class of
|
||||||
:class:`unittest.TestCase`. Django provides a few extensions of this base class:
|
:class:`unittest.TestCase`. Django provides a few extensions of this base class:
|
||||||
|
|
||||||
|
@ -847,7 +842,7 @@ functionality like:
|
||||||
for equality.
|
for equality.
|
||||||
|
|
||||||
* The ability to run tests with :ref:`modified settings <overriding-settings>`.
|
* The ability to run tests with :ref:`modified settings <overriding-settings>`.
|
||||||
* Using the :attr:`~SimpleTestCase.client` :class:`~django.test.client.Client`.
|
* Using the :attr:`~SimpleTestCase.client` :class:`~django.test.Client`.
|
||||||
* Custom test-time :attr:`URL maps <SimpleTestCase.urls>`.
|
* Custom test-time :attr:`URL maps <SimpleTestCase.urls>`.
|
||||||
|
|
||||||
.. versionchanged:: 1.6
|
.. versionchanged:: 1.6
|
||||||
|
@ -1111,7 +1106,7 @@ worry about state (such as cookies) carrying over from one test to another.
|
||||||
This means, instead of instantiating a ``Client`` in each test::
|
This means, instead of instantiating a ``Client`` in each test::
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
from django.test.client import Client
|
from django.test import Client
|
||||||
|
|
||||||
class SimpleTest(unittest.TestCase):
|
class SimpleTest(unittest.TestCase):
|
||||||
def test_details(self):
|
def test_details(self):
|
||||||
|
@ -1146,8 +1141,7 @@ If you want to use a different ``Client`` class (for example, a subclass
|
||||||
with customized behavior), use the :attr:`~SimpleTestCase.client_class` class
|
with customized behavior), use the :attr:`~SimpleTestCase.client_class` class
|
||||||
attribute::
|
attribute::
|
||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase, Client
|
||||||
from django.test.client import Client
|
|
||||||
|
|
||||||
class MyTestClient(Client):
|
class MyTestClient(Client):
|
||||||
# Specialized methods for your environment...
|
# Specialized methods for your environment...
|
||||||
|
@ -1330,17 +1324,14 @@ Django provides a standard Python context manager (see :pep:`343`)
|
||||||
This example will override the :setting:`LOGIN_URL` setting for the code
|
This example will override the :setting:`LOGIN_URL` setting for the code
|
||||||
in the ``with`` block and reset its value to the previous state afterwards.
|
in the ``with`` block and reset its value to the previous state afterwards.
|
||||||
|
|
||||||
.. currentmodule:: django.test.utils
|
|
||||||
|
|
||||||
.. function:: override_settings
|
.. function:: override_settings
|
||||||
|
|
||||||
In case you want to override a setting for just one test method or even the
|
In case you want to override a setting for just one test method or even the
|
||||||
whole :class:`~django.test.TestCase` class, Django provides the
|
whole :class:`~django.test.TestCase` class, Django provides the
|
||||||
:func:`~django.test.utils.override_settings` decorator (see :pep:`318`). It's
|
:func:`~django.test.override_settings` decorator (see :pep:`318`). It's
|
||||||
used like this::
|
used like this::
|
||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase, override_settings
|
||||||
from django.test.utils import override_settings
|
|
||||||
|
|
||||||
class LoginTestCase(TestCase):
|
class LoginTestCase(TestCase):
|
||||||
|
|
||||||
|
@ -1351,8 +1342,7 @@ used like this::
|
||||||
|
|
||||||
The decorator can also be applied to test case classes::
|
The decorator can also be applied to test case classes::
|
||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase, override_settings
|
||||||
from django.test.utils import override_settings
|
|
||||||
|
|
||||||
@override_settings(LOGIN_URL='/other/login/')
|
@override_settings(LOGIN_URL='/other/login/')
|
||||||
class LoginTestCase(TestCase):
|
class LoginTestCase(TestCase):
|
||||||
|
@ -1361,6 +1351,11 @@ The decorator can also be applied to test case classes::
|
||||||
response = self.client.get('/sekrit/')
|
response = self.client.get('/sekrit/')
|
||||||
self.assertRedirects(response, '/other/login/?next=/sekrit/')
|
self.assertRedirects(response, '/other/login/?next=/sekrit/')
|
||||||
|
|
||||||
|
.. versionchanged:: 1.7
|
||||||
|
|
||||||
|
Previously, ``override_settings`` was imported from
|
||||||
|
``django.test.utils``.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
When given a class, the decorator modifies the class directly and
|
When given a class, the decorator modifies the class directly and
|
||||||
|
@ -1427,8 +1422,6 @@ For more detail on email services during tests, see `Email services`_ below.
|
||||||
Assertions
|
Assertions
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
|
|
||||||
.. currentmodule:: django.test
|
|
||||||
|
|
||||||
As Python's normal :class:`unittest.TestCase` class implements assertion methods
|
As Python's normal :class:`unittest.TestCase` class implements assertion methods
|
||||||
such as :meth:`~unittest.TestCase.assertTrue` and
|
such as :meth:`~unittest.TestCase.assertTrue` and
|
||||||
:meth:`~unittest.TestCase.assertEqual`, Django's custom :class:`TestCase` class
|
:meth:`~unittest.TestCase.assertEqual`, Django's custom :class:`TestCase` class
|
||||||
|
@ -1549,7 +1542,7 @@ your test suite.
|
||||||
You can use this as a context manager in the same way as
|
You can use this as a context manager in the same way as
|
||||||
:meth:`~SimpleTestCase.assertTemplateUsed`.
|
:meth:`~SimpleTestCase.assertTemplateUsed`.
|
||||||
|
|
||||||
.. method:: SimpleTestCase.assertRedirects(response, expected_url, status_code=302, target_status_code=200, msg_prefix='')
|
.. method:: SimpleTestCase.assertRedirects(response, expected_url, status_code=302, target_status_code=200, msg_prefix='', fetch_redirect_response=True)
|
||||||
|
|
||||||
Asserts that the response return a ``status_code`` redirect status, it
|
Asserts that the response return a ``status_code`` redirect status, it
|
||||||
redirected to ``expected_url`` (including any GET data), and the final
|
redirected to ``expected_url`` (including any GET data), and the final
|
||||||
|
@ -1559,6 +1552,12 @@ your test suite.
|
||||||
``target_status_code`` will be the url and status code for the final
|
``target_status_code`` will be the url and status code for the final
|
||||||
point of the redirect chain.
|
point of the redirect chain.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
|
If ``fetch_redirect_response`` is ``False``, the final page won't be
|
||||||
|
loaded. Since the test client can't fetch externals URLs, this is
|
||||||
|
particularly useful if ``expected_url`` isn't part of your Django app.
|
||||||
|
|
||||||
.. method:: SimpleTestCase.assertHTMLEqual(html1, html2, msg=None)
|
.. method:: SimpleTestCase.assertHTMLEqual(html1, html2, msg=None)
|
||||||
|
|
||||||
Asserts that the strings ``html1`` and ``html2`` are equal. The comparison
|
Asserts that the strings ``html1`` and ``html2`` are equal. The comparison
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
#
|
|
|
@ -9,7 +9,7 @@ import unittest
|
||||||
from django.conf import settings, global_settings
|
from django.conf import settings, global_settings
|
||||||
from django.core import mail
|
from django.core import mail
|
||||||
from django.core.files import temp as tempfile
|
from django.core.files import temp as tempfile
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse, NoReverseMatch
|
||||||
# Register auth models with the admin.
|
# Register auth models with the admin.
|
||||||
from django.contrib.auth import get_permission_codename
|
from django.contrib.auth import get_permission_codename
|
||||||
from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
|
from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
|
||||||
|
@ -640,6 +640,20 @@ class AdminViewBasicTest(AdminViewBasicTestCase):
|
||||||
# Check the format of the shown object -- shouldn't contain a change link
|
# Check the format of the shown object -- shouldn't contain a change link
|
||||||
self.assertContains(response, '<th class="field-__str__">UnchangeableObject object</th>', html=True)
|
self.assertContains(response, '<th class="field-__str__">UnchangeableObject object</th>', html=True)
|
||||||
|
|
||||||
|
def test_invalid_appindex_url(self):
|
||||||
|
"""
|
||||||
|
#21056 -- URL reversing shouldn't work for nonexistent apps.
|
||||||
|
"""
|
||||||
|
good_url = '/test_admin/admin/admin_views/'
|
||||||
|
confirm_good_url = reverse('admin:app_list',
|
||||||
|
kwargs={'app_label': 'admin_views'})
|
||||||
|
self.assertEqual(good_url, confirm_good_url)
|
||||||
|
|
||||||
|
with self.assertRaises(NoReverseMatch):
|
||||||
|
reverse('admin:app_list', kwargs={'app_label': 'this_should_fail'})
|
||||||
|
with self.assertRaises(NoReverseMatch):
|
||||||
|
reverse('admin:app_list', args=('admin_views2',))
|
||||||
|
|
||||||
|
|
||||||
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
|
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
|
||||||
class AdminViewFormUrlTest(TestCase):
|
class AdminViewFormUrlTest(TestCase):
|
||||||
|
@ -1296,6 +1310,40 @@ class AdminViewPermissionsTest(TestCase):
|
||||||
response = self.client.get('/test_admin/admin/secure-view/')
|
response = self.client.get('/test_admin/admin/secure-view/')
|
||||||
self.assertContains(response, 'id="login-form"')
|
self.assertContains(response, 'id="login-form"')
|
||||||
|
|
||||||
|
def testDisabledStaffPermissionsWhenLoggedIn(self):
|
||||||
|
self.client.login(username='super', password='secret')
|
||||||
|
superuser = User.objects.get(username='super')
|
||||||
|
superuser.is_staff = False
|
||||||
|
superuser.save()
|
||||||
|
|
||||||
|
response = self.client.get('/test_admin/admin/')
|
||||||
|
self.assertContains(response, 'id="login-form"')
|
||||||
|
self.assertNotContains(response, 'Log out')
|
||||||
|
|
||||||
|
response = self.client.get('/test_admin/admin/secure-view/')
|
||||||
|
self.assertContains(response, 'id="login-form"')
|
||||||
|
|
||||||
|
def testAppIndexFailEarly(self):
|
||||||
|
"""
|
||||||
|
If a user has no module perms, avoid iterating over all the modeladmins
|
||||||
|
in the registry.
|
||||||
|
"""
|
||||||
|
opts = Article._meta
|
||||||
|
change_user = User.objects.get(username='changeuser')
|
||||||
|
permission = get_perm(Article, get_permission_codename('change', opts))
|
||||||
|
|
||||||
|
self.client.post('/test_admin/admin/', self.changeuser_login)
|
||||||
|
|
||||||
|
# the user has no module permissions, because this module doesn't exist
|
||||||
|
change_user.user_permissions.remove(permission)
|
||||||
|
response = self.client.get('/test_admin/admin/admin_views/')
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
|
# the user now has module permissions
|
||||||
|
change_user.user_permissions.add(permission)
|
||||||
|
response = self.client.get('/test_admin/admin/admin_views/')
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
|
||||||
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
|
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
|
||||||
class AdminViewsNoUrlTest(TestCase):
|
class AdminViewsNoUrlTest(TestCase):
|
||||||
|
|
|
@ -549,6 +549,79 @@ class DateTimePickerSeleniumFirefoxTests(AdminSeleniumWebDriverTestCase):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.get_css_value('#clockbox0', 'display'), 'none')
|
self.get_css_value('#clockbox0', 'display'), 'none')
|
||||||
|
|
||||||
|
def test_calendar_nonday_class(self):
|
||||||
|
"""
|
||||||
|
Ensure cells that are not days of the month have the `nonday` CSS class.
|
||||||
|
Refs #4574.
|
||||||
|
"""
|
||||||
|
self.admin_login(username='super', password='secret', login_url='/')
|
||||||
|
# Open a page that has a date and time picker widgets
|
||||||
|
self.selenium.get('%s%s' % (self.live_server_url,
|
||||||
|
'/admin_widgets/member/add/'))
|
||||||
|
|
||||||
|
# fill in the birth date.
|
||||||
|
self.selenium.find_element_by_id('id_birthdate_0').send_keys('2013-06-01')
|
||||||
|
|
||||||
|
# Click the calendar icon
|
||||||
|
self.selenium.find_element_by_id('calendarlink0').click()
|
||||||
|
|
||||||
|
# get all the tds within the calendar
|
||||||
|
calendar0 = self.selenium.find_element_by_id('calendarin0')
|
||||||
|
tds = calendar0.find_elements_by_tag_name('td')
|
||||||
|
|
||||||
|
# make sure the first and last 6 cells have class nonday
|
||||||
|
for td in tds[:6] + tds[-6:]:
|
||||||
|
self.assertEqual(td.get_attribute('class'), 'nonday')
|
||||||
|
|
||||||
|
def test_calendar_selected_class(self):
|
||||||
|
"""
|
||||||
|
Ensure cell for the day in the input has the `selected` CSS class.
|
||||||
|
Refs #4574.
|
||||||
|
"""
|
||||||
|
self.admin_login(username='super', password='secret', login_url='/')
|
||||||
|
# Open a page that has a date and time picker widgets
|
||||||
|
self.selenium.get('%s%s' % (self.live_server_url,
|
||||||
|
'/admin_widgets/member/add/'))
|
||||||
|
|
||||||
|
# fill in the birth date.
|
||||||
|
self.selenium.find_element_by_id('id_birthdate_0').send_keys('2013-06-01')
|
||||||
|
|
||||||
|
# Click the calendar icon
|
||||||
|
self.selenium.find_element_by_id('calendarlink0').click()
|
||||||
|
|
||||||
|
# get all the tds within the calendar
|
||||||
|
calendar0 = self.selenium.find_element_by_id('calendarin0')
|
||||||
|
tds = calendar0.find_elements_by_tag_name('td')
|
||||||
|
|
||||||
|
# verify the selected cell
|
||||||
|
selected = tds[6]
|
||||||
|
self.assertEqual(selected.get_attribute('class'), 'selected')
|
||||||
|
|
||||||
|
self.assertEqual(selected.text, '1')
|
||||||
|
|
||||||
|
def test_calendar_no_selected_class(self):
|
||||||
|
"""
|
||||||
|
Ensure no cells are given the selected class when the field is empty.
|
||||||
|
Refs #4574.
|
||||||
|
"""
|
||||||
|
self.admin_login(username='super', password='secret', login_url='/')
|
||||||
|
# Open a page that has a date and time picker widgets
|
||||||
|
self.selenium.get('%s%s' % (self.live_server_url,
|
||||||
|
'/admin_widgets/member/add/'))
|
||||||
|
|
||||||
|
# Click the calendar icon
|
||||||
|
self.selenium.find_element_by_id('calendarlink0').click()
|
||||||
|
|
||||||
|
# get all the tds within the calendar
|
||||||
|
calendar0 = self.selenium.find_element_by_id('calendarin0')
|
||||||
|
tds = calendar0.find_elements_by_tag_name('td')
|
||||||
|
|
||||||
|
# verify there are no cells with the selected class
|
||||||
|
selected = [td for td in tds if td.get_attribute('class') == 'selected']
|
||||||
|
|
||||||
|
self.assertEqual(len(selected), 0)
|
||||||
|
|
||||||
|
|
||||||
class DateTimePickerSeleniumChromeTests(DateTimePickerSeleniumFirefoxTests):
|
class DateTimePickerSeleniumChromeTests(DateTimePickerSeleniumFirefoxTests):
|
||||||
webdriver_class = 'selenium.webdriver.chrome.webdriver.WebDriver'
|
webdriver_class = 'selenium.webdriver.chrome.webdriver.WebDriver'
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,8 @@ import re
|
||||||
|
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
from django.db.models import Avg, Sum, Count, Max, Min
|
from django.db.models import Avg, Sum, Count, Max, Min
|
||||||
from django.test import TestCase, Approximate
|
from django.test import TestCase
|
||||||
|
from django.test.utils import Approximate
|
||||||
from django.test.utils import CaptureQueriesContext
|
from django.test.utils import CaptureQueriesContext
|
||||||
|
|
||||||
from .models import Author, Publisher, Book, Store
|
from .models import Author, Publisher, Book, Store
|
||||||
|
|
|
@ -8,7 +8,8 @@ from operator import attrgetter
|
||||||
from django.core.exceptions import FieldError
|
from django.core.exceptions import FieldError
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db.models import Count, Max, Avg, Sum, StdDev, Variance, F, Q
|
from django.db.models import Count, Max, Avg, Sum, StdDev, Variance, F, Q
|
||||||
from django.test import TestCase, Approximate, skipUnlessDBFeature
|
from django.test import TestCase, skipUnlessDBFeature
|
||||||
|
from django.test.utils import Approximate
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
|
||||||
from .models import (Author, Book, Publisher, Clues, Entries, HardbackBook,
|
from .models import (Author, Book, Publisher, Clues, Entries, HardbackBook,
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
|
|
|
@ -83,11 +83,3 @@ class CommentTestCase(TestCase):
|
||||||
d.update(f.initial)
|
d.update(f.initial)
|
||||||
return d
|
return d
|
||||||
|
|
||||||
from comment_tests.tests.test_app_api import *
|
|
||||||
from comment_tests.tests.test_feeds import *
|
|
||||||
from comment_tests.tests.test_models import *
|
|
||||||
from comment_tests.tests.test_comment_form import *
|
|
||||||
from comment_tests.tests.test_templatetags import *
|
|
||||||
from comment_tests.tests.test_comment_view import *
|
|
||||||
from comment_tests.tests.test_comment_utils_moderators import *
|
|
||||||
from comment_tests.tests.test_moderation_views import *
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue