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
|
||||
Frantisek Malina <vizualbod@vizualbod.com>
|
||||
Mike Malone <mjmalone@gmail.com>
|
||||
Curtis Maloney (FunkyBob) <curtis@tinbrain.net>
|
||||
Martin Maney <http://www.chipy.org/Martin_Maney>
|
||||
Michael Manfre <mmanfre@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,
|
||||
FieldListFilter, BooleanFieldListFilter, RelatedFieldListFilter,
|
||||
ChoicesFieldListFilter, DateFieldListFilter, AllValuesFieldListFilter)
|
||||
from django.utils.module_loading import autodiscover_modules
|
||||
|
||||
|
||||
def autodiscover():
|
||||
"""
|
||||
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
|
||||
autodiscover_modules('admin', register_to=site)
|
||||
|
|
|
@ -33,26 +33,26 @@ class ListFilter(object):
|
|||
"""
|
||||
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):
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
Returns the filtered queryset.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError('subclasses of ListFilter must provide a queryset() method')
|
||||
|
||||
def expected_parameters(self):
|
||||
"""
|
||||
Returns the list of parameter names that are expected from the
|
||||
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):
|
||||
|
@ -89,7 +89,9 @@ class SimpleListFilter(ListFilter):
|
|||
"""
|
||||
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):
|
||||
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.views.decorators.csrf import csrf_protect
|
||||
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.template.response import TemplateResponse
|
||||
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'^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'^(?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):
|
||||
urlpatterns += patterns('',
|
||||
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
|
||||
|
||||
@property
|
||||
|
@ -399,44 +410,45 @@ class AdminSite(object):
|
|||
def app_index(self, request, app_label, extra_context=None):
|
||||
user = request.user
|
||||
has_module_perms = user.has_module_perms(app_label)
|
||||
if not has_module_perms:
|
||||
raise PermissionDenied
|
||||
app_dict = {}
|
||||
for model, model_admin in self._registry.items():
|
||||
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.
|
||||
# If so, add the module to the model_list.
|
||||
if True in perms.values():
|
||||
info = (app_label, model._meta.model_name)
|
||||
model_dict = {
|
||||
'name': capfirst(model._meta.verbose_name_plural),
|
||||
'object_name': model._meta.object_name,
|
||||
'perms': perms,
|
||||
# Check whether user has any perm for this module.
|
||||
# If so, add the module to the model_list.
|
||||
if True in perms.values():
|
||||
info = (app_label, model._meta.model_name)
|
||||
model_dict = {
|
||||
'name': capfirst(model._meta.verbose_name_plural),
|
||||
'object_name': model._meta.object_name,
|
||||
'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:
|
||||
raise Http404('The requested admin page does not exist.')
|
||||
# Sort the models alphabetically within each app.
|
||||
|
|
|
@ -293,8 +293,9 @@ var DateTimeShortcuts = {
|
|||
var date_parts = inp.value.split('-');
|
||||
var year = date_parts[0];
|
||||
var month = parseFloat(date_parts[1]);
|
||||
var selected = new Date(inp.value);
|
||||
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;
|
||||
},
|
||||
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 todayDay = today.getDate();
|
||||
var todayMonth = today.getMonth()+1;
|
||||
var todayYear = today.getFullYear();
|
||||
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);
|
||||
year = parseInt(year);
|
||||
var calDiv = document.getElementById(div_id);
|
||||
|
@ -55,7 +71,7 @@ var CalendarNamespace = {
|
|||
tableRow = quickElement('tr', tableBody);
|
||||
for (var i = 0; i < startingPos; i++) {
|
||||
var _cell = quickElement('td', tableRow, ' ');
|
||||
_cell.style.backgroundColor = '#f3f3f3';
|
||||
_cell.className = "nonday";
|
||||
}
|
||||
|
||||
// Draw days of month
|
||||
|
@ -69,6 +85,13 @@ var CalendarNamespace = {
|
|||
} else {
|
||||
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);
|
||||
|
||||
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)
|
||||
while (tableRow.childNodes.length < 7) {
|
||||
var _cell = quickElement('td', tableRow, ' ');
|
||||
_cell.style.backgroundColor = '#f3f3f3';
|
||||
_cell.className = "nonday";
|
||||
}
|
||||
|
||||
calDiv.appendChild(calTable);
|
||||
|
@ -86,7 +109,7 @@ var CalendarNamespace = {
|
|||
}
|
||||
|
||||
// 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
|
||||
// be displayed
|
||||
// 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.currentMonth = this.today.getMonth() + 1;
|
||||
this.currentYear = this.today.getFullYear();
|
||||
if (typeof selected != 'undefined') {
|
||||
this.selected = selected;
|
||||
}
|
||||
}
|
||||
Calendar.prototype = {
|
||||
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.currentYear = year;
|
||||
|
||||
if(selected) {
|
||||
this.selected = selected;
|
||||
}
|
||||
|
||||
this.drawCurrent();
|
||||
},
|
||||
drawPreviousMonth: function() {
|
||||
|
|
|
@ -26,8 +26,10 @@
|
|||
</div>
|
||||
{% if user.is_active and user.is_staff %}
|
||||
<div id="user-tools">
|
||||
{% trans 'Welcome,' %}
|
||||
<strong>{% firstof user.get_short_name user.get_username %}</strong>.
|
||||
{% block welcome-msg %}
|
||||
{% trans 'Welcome,' %}
|
||||
<strong>{% firstof user.get_short_name user.get_username %}</strong>.
|
||||
{% endblock %}
|
||||
{% block userlinks %}
|
||||
{% url 'django-admindocs-docroot' as docsroot %}
|
||||
{% if docsroot %}
|
||||
|
|
|
@ -192,7 +192,7 @@ class BasePasswordHasher(object):
|
|||
"""
|
||||
Checks if the given password is correct
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
raise NotImplementedError('subclasses of BasePasswordHasher must provide a verify() method')
|
||||
|
||||
def encode(self, password, salt):
|
||||
"""
|
||||
|
@ -201,7 +201,7 @@ class BasePasswordHasher(object):
|
|||
The result is normally formatted as "algorithm$salt$hash" and
|
||||
must be fewer than 128 characters.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
raise NotImplementedError('subclasses of BasePasswordHasher must provide an encode() method')
|
||||
|
||||
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
|
||||
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):
|
||||
|
|
|
@ -245,10 +245,10 @@ class AbstractBaseUser(models.Model):
|
|||
return is_password_usable(self.password)
|
||||
|
||||
def get_full_name(self):
|
||||
raise NotImplementedError()
|
||||
raise NotImplementedError('subclasses of AbstractBaseUser must provide a get_full_name() method')
|
||||
|
||||
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.
|
||||
|
@ -441,16 +441,16 @@ class AnonymousUser(object):
|
|||
return 1 # instances always return the same hash value
|
||||
|
||||
def save(self):
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError("Django doesn't provide a DB representation for AnonymousUser.")
|
||||
|
||||
def delete(self):
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError("Django doesn't provide a DB representation for AnonymousUser.")
|
||||
|
||||
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):
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError("Django doesn't provide a DB representation for AnonymousUser.")
|
||||
|
||||
def _get_groups(self):
|
||||
return self._groups
|
||||
|
|
|
@ -112,7 +112,7 @@ class BaseCommentNode(six.with_metaclass(RenameBaseCommentNodeMethods, template.
|
|||
|
||||
def get_context_value_from_queryset(self, context, qs):
|
||||
"""Subclasses should override this."""
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError('subclasses of BaseCommentNode must provide a get_context_value_from_queryset() method')
|
||||
|
||||
class CommentListNode(BaseCommentNode):
|
||||
"""Insert a list of comments into the context."""
|
||||
|
@ -338,4 +338,3 @@ def get_comment_permalink(comment, anchor_pattern=None):
|
|||
if anchor_pattern:
|
||||
return comment.get_absolute_url(anchor_pattern)
|
||||
return comment.get_absolute_url()
|
||||
|
||||
|
|
|
@ -101,7 +101,7 @@ class BaseSpatialOperations(object):
|
|||
Returns the database column type for the geometry field on
|
||||
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):
|
||||
"""
|
||||
|
@ -117,7 +117,7 @@ class BaseSpatialOperations(object):
|
|||
stored procedure call to the transformation function of the spatial
|
||||
backend.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError('subclasses of BaseSpatialOperations must provide a geo_db_placeholder() method')
|
||||
|
||||
def get_expression_column(self, evaluator):
|
||||
"""
|
||||
|
@ -134,14 +134,14 @@ class BaseSpatialOperations(object):
|
|||
raise NotImplementedError('Aggregate support not implemented for this spatial backend.')
|
||||
|
||||
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.
|
||||
def geometry_columns(self):
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError('subclasses of BaseSpatialOperations must a provide geometry_columns() method')
|
||||
|
||||
def spatial_ref_sys(self):
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError('subclasses of BaseSpatialOperations must a provide spatial_ref_sys() method')
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class SpatialRefSysMixin(object):
|
||||
|
|
|
@ -25,9 +25,10 @@ class SpatiaLiteIntrospection(DatabaseIntrospection):
|
|||
cursor = self.connection.cursor()
|
||||
try:
|
||||
# Querying the `geometry_columns` table to get additional metadata.
|
||||
cursor.execute('SELECT "coord_dimension", "srid", "type" '
|
||||
'FROM "geometry_columns" '
|
||||
'WHERE "f_table_name"=%s AND "f_geometry_column"=%s',
|
||||
type_col = 'type' if self.connection.ops.spatial_version < (4, 0, 0) else 'geometry_type'
|
||||
cursor.execute('SELECT coord_dimension, srid, %s '
|
||||
'FROM geometry_columns '
|
||||
'WHERE f_table_name=%%s AND f_geometry_column=%%s' % type_col,
|
||||
(table_name, geo_col))
|
||||
row = cursor.fetchone()
|
||||
if not row:
|
||||
|
|
|
@ -9,5 +9,6 @@ class AllOGRFields(models.Model):
|
|||
f_datetime = models.DateTimeField()
|
||||
f_time = models.TimeField()
|
||||
geom = models.PolygonField()
|
||||
point = models.PointField()
|
||||
|
||||
objects = models.GeoManager()
|
||||
|
|
|
@ -3,11 +3,13 @@ from __future__ import unicode_literals
|
|||
import os
|
||||
from unittest import skipUnless
|
||||
|
||||
from django.core.management import call_command
|
||||
from django.db import connections
|
||||
from django.test import TestCase
|
||||
from django.contrib.gis.gdal import HAS_GDAL
|
||||
from django.contrib.gis.geometry.test_data import TEST_DATA
|
||||
from django.contrib.gis.tests.utils import HAS_SPATIAL_DB
|
||||
from django.utils.six import StringIO
|
||||
|
||||
if HAS_GDAL:
|
||||
from django.contrib.gis.gdal import Driver
|
||||
|
@ -16,6 +18,22 @@ if HAS_GDAL:
|
|||
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.")
|
||||
class OGRInspectTest(TestCase):
|
||||
maxDiff = 1024
|
||||
|
|
|
@ -14,10 +14,9 @@ from django.template import Template, Context, defaultfilters
|
|||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
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.translation import ugettext as _
|
||||
from django.utils import tzinfo
|
||||
|
||||
from i18n import TransRealMixin
|
||||
|
||||
|
@ -153,8 +152,8 @@ class HumanizeTests(TransRealMixin, TestCase):
|
|||
|
||||
def test_naturalday_tz(self):
|
||||
today = datetime.date.today()
|
||||
tz_one = tzinfo.FixedOffset(datetime.timedelta(hours=-12))
|
||||
tz_two = tzinfo.FixedOffset(datetime.timedelta(hours=12))
|
||||
tz_one = get_fixed_timezone(-720)
|
||||
tz_two = get_fixed_timezone(720)
|
||||
|
||||
# Can be today or yesterday
|
||||
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
|
||||
place of ``messages``.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
raise NotImplementedError('subclasses of BaseStorage must provide a _get() method')
|
||||
|
||||
def _store(self, messages, response, *args, **kwargs):
|
||||
"""
|
||||
|
@ -116,7 +116,7 @@ class BaseStorage(object):
|
|||
|
||||
**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):
|
||||
"""
|
||||
|
|
|
@ -284,7 +284,7 @@ class SessionBase(object):
|
|||
"""
|
||||
Returns True if the given session_key already exists.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError('subclasses of SessionBase must provide an exists() method')
|
||||
|
||||
def create(self):
|
||||
"""
|
||||
|
@ -292,7 +292,7 @@ class SessionBase(object):
|
|||
a unique key and will have saved the result once (with empty data)
|
||||
before the method returns.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError('subclasses of SessionBase must provide a create() method')
|
||||
|
||||
def save(self, must_create=False):
|
||||
"""
|
||||
|
@ -300,20 +300,20 @@ class SessionBase(object):
|
|||
is created (otherwise a CreateError exception is raised). Otherwise,
|
||||
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):
|
||||
"""
|
||||
Deletes the session data under this key. If the key is None, the
|
||||
current session key value is used.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError('subclasses of SessionBase must provide a delete() method')
|
||||
|
||||
def load(self):
|
||||
"""
|
||||
Loads the session data and returns a dictionary.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError('subclasses of SessionBase must provide a load() method')
|
||||
|
||||
@classmethod
|
||||
def clear_expired(cls):
|
||||
|
@ -324,4 +324,4 @@ class SessionBase(object):
|
|||
NotImplementedError. If it isn't necessary, because the backend has
|
||||
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
|
||||
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):
|
||||
"""
|
||||
|
@ -36,7 +36,7 @@ class BaseFinder(object):
|
|||
a two item iterable consisting of the relative path and storage
|
||||
instance.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
raise NotImplementedError('subclasses of BaseFinder must provide a list() method')
|
||||
|
||||
|
||||
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.http import HttpResponse, Http404
|
||||
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.html import escape
|
||||
from django.utils.http import http_date
|
||||
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):
|
||||
|
@ -186,15 +186,15 @@ class Feed(object):
|
|||
else:
|
||||
author_email = author_link = None
|
||||
|
||||
tz = get_default_timezone()
|
||||
|
||||
pubdate = self.__get_dynamic_attr('item_pubdate', item)
|
||||
if pubdate and is_naive(pubdate):
|
||||
ltz = tzinfo.LocalTimezone(pubdate)
|
||||
pubdate = pubdate.replace(tzinfo=ltz)
|
||||
pubdate = make_aware(pubdate, tz)
|
||||
|
||||
updateddate = self.__get_dynamic_attr('item_updateddate', item)
|
||||
if updateddate and is_naive(updateddate):
|
||||
ltz = tzinfo.LocalTimezone(updateddate)
|
||||
updateddate = updateddate.replace(tzinfo=ltz)
|
||||
updateddate = make_aware(updateddate, tz)
|
||||
|
||||
feed.add_item(
|
||||
title = title,
|
||||
|
|
|
@ -96,27 +96,27 @@ class BaseCache(object):
|
|||
|
||||
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):
|
||||
"""
|
||||
Fetch a given key from the cache. If the key does not exist, return
|
||||
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):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError('subclasses of BaseCache must provide a set() method')
|
||||
|
||||
def delete(self, key, version=None):
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
|
@ -190,7 +190,7 @@ class BaseCache(object):
|
|||
|
||||
def clear(self):
|
||||
"""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):
|
||||
"""
|
||||
|
|
|
@ -92,55 +92,55 @@ class Storage(object):
|
|||
"""
|
||||
Deletes the specified file from the storage system.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
raise NotImplementedError('subclasses of Storage must provide a delete() method')
|
||||
|
||||
def exists(self, name):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
raise NotImplementedError('subclasses of Storage must provide a exists() method')
|
||||
|
||||
def listdir(self, path):
|
||||
"""
|
||||
Lists the contents of the specified path, returning a 2-tuple of lists;
|
||||
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):
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
Returns an absolute URL where the file's contents can be accessed
|
||||
directly by a Web browser.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
raise NotImplementedError('subclasses of Storage must provide a url() method')
|
||||
|
||||
def accessed_time(self, name):
|
||||
"""
|
||||
Returns the last accessed time (as datetime object) of the file
|
||||
specified by name.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
raise NotImplementedError('subclasses of Storage must provide an accessed_time() method')
|
||||
|
||||
def created_time(self, name):
|
||||
"""
|
||||
Returns the creation time (as datetime object) of the file
|
||||
specified by name.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
raise NotImplementedError('subclasses of Storage must provide a created_time() method')
|
||||
|
||||
def modified_time(self, name):
|
||||
"""
|
||||
Returns the last modified time (as datetime object) of the file
|
||||
specified by name.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
raise NotImplementedError('subclasses of Storage must provide a modified_time() method')
|
||||
|
||||
class FileSystemStorage(Storage):
|
||||
"""
|
||||
|
@ -215,6 +215,7 @@ class FileSystemStorage(Storage):
|
|||
_file = os.fdopen(fd, mode)
|
||||
_file.write(chunk)
|
||||
finally:
|
||||
content.close()
|
||||
locks.unlock(fd)
|
||||
if _file is not None:
|
||||
_file.close()
|
||||
|
|
|
@ -46,6 +46,7 @@ class UploadedFile(File):
|
|||
# File names longer than 255 characters can cause problems on older OSes.
|
||||
if len(name) > 255:
|
||||
name, ext = os.path.splitext(name)
|
||||
ext = ext[:255]
|
||||
name = name[:255 - len(ext)] + ext
|
||||
|
||||
self._name = name
|
||||
|
|
|
@ -104,7 +104,7 @@ class FileUploadHandler(object):
|
|||
Receive data from the streamed upload parser. ``start`` is the position
|
||||
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):
|
||||
"""
|
||||
|
@ -113,7 +113,7 @@ class FileUploadHandler(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):
|
||||
"""
|
||||
|
|
|
@ -36,4 +36,4 @@ class BaseEmailBackend(object):
|
|||
Sends one or more EmailMessage objects and returns the number of email
|
||||
messages sent.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError('subclasses of BaseEmailBackend must override send_messages() method')
|
||||
|
|
|
@ -325,7 +325,7 @@ class BaseCommand(object):
|
|||
this method.
|
||||
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
raise NotImplementedError('subclasses of BaseCommand must provide a handle() method')
|
||||
|
||||
|
||||
class AppCommand(BaseCommand):
|
||||
|
@ -361,7 +361,7 @@ class AppCommand(BaseCommand):
|
|||
the command line.
|
||||
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
raise NotImplementedError('subclasses of AppCommand must provide a handle_app() method')
|
||||
|
||||
|
||||
class LabelCommand(BaseCommand):
|
||||
|
@ -397,7 +397,7 @@ class LabelCommand(BaseCommand):
|
|||
string as given on the command line.
|
||||
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
raise NotImplementedError('subclasses of LabelCommand must provide a handle_label() method')
|
||||
|
||||
|
||||
class NoArgsCommand(BaseCommand):
|
||||
|
@ -423,4 +423,4 @@ class NoArgsCommand(BaseCommand):
|
|||
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
|
||||
# that's assumed if it doesn't exist.
|
||||
if att_name == 'id' and field_type == 'AutoField(' and extra_params == {'primary_key': True}:
|
||||
continue
|
||||
if att_name == 'id' and extra_params == {'primary_key': True}:
|
||||
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
|
||||
# table description.
|
||||
|
@ -117,7 +120,12 @@ class Command(NoArgsCommand):
|
|||
if not field_type in ('TextField(', 'CharField('):
|
||||
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 not field_desc.endswith('('):
|
||||
field_desc += ', '
|
||||
|
|
|
@ -65,7 +65,7 @@ class Serializer(object):
|
|||
"""
|
||||
Called when serializing of the queryset starts.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError('subclasses of Serializer must provide a start_serialization() method')
|
||||
|
||||
def end_serialization(self):
|
||||
"""
|
||||
|
@ -77,7 +77,7 @@ class Serializer(object):
|
|||
"""
|
||||
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):
|
||||
"""
|
||||
|
@ -89,19 +89,19 @@ class Serializer(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):
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
Called to handle a ManyToManyField.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError('subclasses of Serializer must provide an handle_m2m_field() method')
|
||||
|
||||
def getvalue(self):
|
||||
"""
|
||||
|
@ -135,7 +135,7 @@ class Deserializer(six.Iterator):
|
|||
|
||||
def __next__(self):
|
||||
"""Iteration iterface -- return the next item in the stream"""
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError('subclasses of Deserializer must provide a __next__() method')
|
||||
|
||||
class DeserializedObject(object):
|
||||
"""
|
||||
|
|
|
@ -84,19 +84,19 @@ class BaseDatabaseWrapper(object):
|
|||
|
||||
def get_connection_params(self):
|
||||
"""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):
|
||||
"""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):
|
||||
"""Initializes the database connection settings."""
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError('subclasses of BaseDatabaseWrapper may require an init_connection_state() method')
|
||||
|
||||
def create_cursor(self):
|
||||
"""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 #####
|
||||
|
||||
|
@ -262,7 +262,7 @@ class BaseDatabaseWrapper(object):
|
|||
"""
|
||||
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 #####
|
||||
|
||||
|
@ -440,7 +440,7 @@ class BaseDatabaseWrapper(object):
|
|||
Tests if the database connection is usable.
|
||||
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):
|
||||
"""
|
||||
|
@ -519,11 +519,11 @@ class BaseDatabaseWrapper(object):
|
|||
"""
|
||||
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):
|
||||
"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):
|
||||
|
@ -627,6 +627,9 @@ class BaseDatabaseFeatures(object):
|
|||
# which can't do it for MyISAM tables
|
||||
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
|
||||
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
|
||||
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):
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
|
@ -755,7 +758,7 @@ class BaseDatabaseOperations(object):
|
|||
truncates the given date field field_name to a date object with only
|
||||
the given specificity.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
raise NotImplementedError('subclasses of BaseDatabaseOperations may require a datetrunc_sql() method')
|
||||
|
||||
def datetime_cast_sql(self):
|
||||
"""
|
||||
|
@ -772,7 +775,7 @@ class BaseDatabaseOperations(object):
|
|||
'second', returns the SQL that extracts a value from the given
|
||||
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):
|
||||
"""
|
||||
|
@ -781,7 +784,7 @@ class BaseDatabaseOperations(object):
|
|||
field_name to a datetime object with only the given specificity, and
|
||||
a tuple of parameters.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
raise NotImplementedError('subclasses of BaseDatabaseOperations may require a datetime_trunk_sql() method')
|
||||
|
||||
def deferrable_sql(self):
|
||||
"""
|
||||
|
@ -916,7 +919,7 @@ class BaseDatabaseOperations(object):
|
|||
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.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError('subclasses of BaseDatabaseOperations may require a no_limit_value() method')
|
||||
|
||||
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
|
||||
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):
|
||||
"""
|
||||
|
@ -982,7 +985,7 @@ class BaseDatabaseOperations(object):
|
|||
If the feature is not supported (or part of it is not supported), a
|
||||
NotImplementedError exception can be raised.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError('subclasses of BaseDatabaseOperations may require a regex_lookup() method')
|
||||
|
||||
def savepoint_create_sql(self, sid):
|
||||
"""
|
||||
|
@ -1028,7 +1031,7 @@ class BaseDatabaseOperations(object):
|
|||
to tables with foreign keys pointing the tables being truncated.
|
||||
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):
|
||||
"""
|
||||
|
@ -1245,7 +1248,7 @@ class BaseDatabaseIntrospection(object):
|
|||
Returns an unsorted list of names of all tables that exist in the
|
||||
database.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError('subclasses of BaseDatabaseIntrospection may require a get_table_list() method')
|
||||
|
||||
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,
|
||||
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):
|
||||
"""
|
||||
|
@ -1342,7 +1345,7 @@ class BaseDatabaseIntrospection(object):
|
|||
|
||||
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):
|
||||
"""
|
||||
|
@ -1361,7 +1364,7 @@ class BaseDatabaseIntrospection(object):
|
|||
Some backends may return special constraint names that don't exist
|
||||
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):
|
||||
|
@ -1378,7 +1381,7 @@ class BaseDatabaseClient(object):
|
|||
self.connection = connection
|
||||
|
||||
def runshell(self):
|
||||
raise NotImplementedError()
|
||||
raise NotImplementedError('subclasses of BaseDatabaseClient must provide a runshell() method')
|
||||
|
||||
|
||||
class BaseDatabaseValidation(object):
|
||||
|
|
|
@ -181,6 +181,7 @@ class DatabaseCreation(BaseDatabaseCreation):
|
|||
IDENTIFIED BY %(password)s
|
||||
DEFAULT TABLESPACE %(tblspace)s
|
||||
TEMPORARY TABLESPACE %(tblspace_temp)s
|
||||
QUOTA UNLIMITED ON %(tblspace)s
|
||||
""",
|
||||
"""GRANT CONNECT, RESOURCE TO %(user)s""",
|
||||
]
|
||||
|
|
|
@ -148,7 +148,7 @@ class BaseDatabaseSchemaEditor(object):
|
|||
"""
|
||||
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):
|
||||
"""
|
||||
|
|
|
@ -38,14 +38,14 @@ class Operation(object):
|
|||
Takes the state from the previous migration, and mutates it
|
||||
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):
|
||||
"""
|
||||
Performs the mutation on the database schema in the normal
|
||||
(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):
|
||||
"""
|
||||
|
@ -53,7 +53,7 @@ class Operation(object):
|
|||
direction - e.g. if this were CreateModel, it would in fact
|
||||
drop the model's table.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
raise NotImplementedError('subclasses of Operation must provide a database_backwards() method')
|
||||
|
||||
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))
|
||||
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
|
||||
def serialize(cls, value):
|
||||
"""
|
||||
|
@ -119,23 +139,7 @@ class MigrationWriter(object):
|
|||
# Django fields
|
||||
elif isinstance(value, models.Field):
|
||||
attr_name, path, args, kwargs = value.deconstruct()
|
||||
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
|
||||
return cls.serialize_deconstructed(path, args, kwargs)
|
||||
# Functions
|
||||
elif isinstance(value, (types.FunctionType, types.BuiltinFunctionType)):
|
||||
# Special-cases, as these don't have im_class
|
||||
|
@ -152,6 +156,8 @@ class MigrationWriter(object):
|
|||
klass = value.im_class
|
||||
module = klass.__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>':
|
||||
raise ValueError("Cannot serialize function: lambda")
|
||||
elif value.__module__ is None:
|
||||
|
|
|
@ -35,10 +35,12 @@ def SET(value):
|
|||
else:
|
||||
def set_on_delete(collector, field, sub_objs, using):
|
||||
collector.add_field_update(field, value, sub_objs)
|
||||
set_on_delete.deconstruct = lambda: ('django.db.models.SET', (value,), {})
|
||||
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):
|
||||
|
|
|
@ -391,7 +391,7 @@ class SQLCompiler(object):
|
|||
if not distinct or elt in select_aliases:
|
||||
result.append('%s %s' % (elt, order))
|
||||
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
|
||||
# '-field1__field2__field', etc.
|
||||
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
|
||||
# from other tables.
|
||||
query = self.query.clone(klass=Query)
|
||||
query.extra = {}
|
||||
query._extra = {}
|
||||
query.select = []
|
||||
query.add_fields([query.get_meta().pk.name])
|
||||
# Recheck the count - it is possible that fiddling with the select
|
||||
|
|
|
@ -143,7 +143,10 @@ class Query(object):
|
|||
self.select_related = False
|
||||
|
||||
# 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_cache = None
|
||||
|
||||
|
@ -153,7 +156,9 @@ class Query(object):
|
|||
|
||||
# These are for extensions. The contents are more or less appended
|
||||
# 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_cache = None
|
||||
|
||||
|
@ -165,6 +170,18 @@ class Query(object):
|
|||
# load.
|
||||
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):
|
||||
"""
|
||||
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_related = self.select_related
|
||||
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:
|
||||
obj.aggregate_select_mask = None
|
||||
else:
|
||||
|
@ -257,7 +274,7 @@ class Query(object):
|
|||
# used.
|
||||
obj._aggregate_select_cache = None
|
||||
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:
|
||||
obj.extra_select_mask = None
|
||||
else:
|
||||
|
@ -344,7 +361,7 @@ class Query(object):
|
|||
# and move them to the outer AggregateQuery.
|
||||
for alias, aggregate in self.aggregate_select.items():
|
||||
if aggregate.is_summary:
|
||||
query.aggregate_select[alias] = aggregate.relabeled_clone(relabels)
|
||||
query.aggregates[alias] = aggregate.relabeled_clone(relabels)
|
||||
del obj.aggregate_select[alias]
|
||||
|
||||
try:
|
||||
|
@ -358,7 +375,7 @@ class Query(object):
|
|||
query = self
|
||||
self.select = []
|
||||
self.default_cols = False
|
||||
self.extra = {}
|
||||
self._extra = {}
|
||||
self.remove_inherited_models()
|
||||
|
||||
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
|
||||
# really make sense (or return consistent value sets). Not worth
|
||||
# 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 "
|
||||
"cannot have extra(select=...) on both sides.")
|
||||
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.select = [SelectInfo(relabel_column(s.col), s.field)
|
||||
for s in self.select]
|
||||
self.aggregates = OrderedDict(
|
||||
(key, relabel_column(col)) for key, col in self.aggregates.items())
|
||||
if self._aggregates:
|
||||
self._aggregates = OrderedDict(
|
||||
(key, relabel_column(col)) for key, col in self._aggregates.items())
|
||||
|
||||
# 2. Rename the alias in the internal table/alias datastructures.
|
||||
for ident, aliases in self.join_map.items():
|
||||
|
@ -967,7 +985,7 @@ class Query(object):
|
|||
"""
|
||||
opts = model._meta
|
||||
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
|
||||
field_name = field_list[0]
|
||||
col = field_name
|
||||
|
@ -1049,7 +1067,7 @@ class Query(object):
|
|||
lookup_parts = lookup.split(LOOKUP_SEP)
|
||||
num_parts = len(lookup_parts)
|
||||
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
|
||||
# lookup types.
|
||||
lookup_model = self.model
|
||||
|
@ -1108,10 +1126,11 @@ class Query(object):
|
|||
value, lookup_type = self.prepare_lookup_value(value, lookup_type, can_reuse)
|
||||
|
||||
clause = self.where_class()
|
||||
for alias, aggregate in self.aggregates.items():
|
||||
if alias in (parts[0], LOOKUP_SEP.join(parts)):
|
||||
clause.add((aggregate, lookup_type, value), AND)
|
||||
return clause
|
||||
if self._aggregates:
|
||||
for alias, aggregate in self.aggregates.items():
|
||||
if alias in (parts[0], LOOKUP_SEP.join(parts)):
|
||||
clause.add((aggregate, lookup_type, value), AND)
|
||||
return clause
|
||||
|
||||
opts = self.get_meta()
|
||||
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
|
||||
together in the HAVING clause.
|
||||
"""
|
||||
if not self._aggregates:
|
||||
return False
|
||||
if not isinstance(obj, Node):
|
||||
return (refs_aggregate(obj[0].split(LOOKUP_SEP), self.aggregates)
|
||||
or (hasattr(obj[1], 'contains_aggregate')
|
||||
|
@ -1632,7 +1653,7 @@ class Query(object):
|
|||
|
||||
# Set only aggregate to be the count column.
|
||||
# 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.group_by = None
|
||||
|
||||
|
@ -1781,7 +1802,8 @@ class Query(object):
|
|||
self.extra_select_mask = set(names)
|
||||
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
|
||||
be used in the SELECT clause.
|
||||
|
||||
|
@ -1789,6 +1811,8 @@ class Query(object):
|
|||
"""
|
||||
if self._aggregate_select_cache is not None:
|
||||
return self._aggregate_select_cache
|
||||
elif not self._aggregates:
|
||||
return {}
|
||||
elif self.aggregate_select_mask is not None:
|
||||
self._aggregate_select_cache = OrderedDict(
|
||||
(k, v) for k, v in self.aggregates.items()
|
||||
|
@ -1797,11 +1821,13 @@ class Query(object):
|
|||
return self._aggregate_select_cache
|
||||
else:
|
||||
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:
|
||||
return self._extra_select_cache
|
||||
if not self._extra:
|
||||
return {}
|
||||
elif self.extra_select_mask is not None:
|
||||
self._extra_select_cache = OrderedDict(
|
||||
(k, v) for k, v in self.extra.items()
|
||||
|
@ -1810,7 +1836,6 @@ class Query(object):
|
|||
return self._extra_select_cache
|
||||
else:
|
||||
return self.extra
|
||||
extra_select = property(_extra_select)
|
||||
|
||||
def trim_start(self, names_with_path):
|
||||
"""
|
||||
|
|
|
@ -977,6 +977,11 @@ class MultiValueField(Field):
|
|||
f.required = False
|
||||
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):
|
||||
pass
|
||||
|
||||
|
|
|
@ -185,7 +185,8 @@ class BaseForm(object):
|
|||
'label': force_text(label),
|
||||
'field': six.text_type(bf),
|
||||
'help_text': help_text,
|
||||
'html_class_attr': html_class_attr
|
||||
'html_class_attr': html_class_attr,
|
||||
'field_name': bf.html_name,
|
||||
})
|
||||
|
||||
if top_errors:
|
||||
|
|
|
@ -23,11 +23,18 @@ def flatatt(attrs):
|
|||
|
||||
The result is passed through 'mark_safe'.
|
||||
"""
|
||||
if [v for v in attrs.values() if v is True or v is False]:
|
||||
warnings.warn(
|
||||
'The meaning of boolean values for widget attributes will change in Django 1.8',
|
||||
DeprecationWarning
|
||||
)
|
||||
for attr_name, value in attrs.items():
|
||||
if type(value) is bool:
|
||||
warnings.warn(
|
||||
"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()))
|
||||
|
||||
@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
|
||||
implementations should program defensively.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError('subclasses of Widget must provide a render() method')
|
||||
|
||||
def build_attrs(self, extra_attrs=None, **kwargs):
|
||||
"Helper function for building an attribute dictionary."
|
||||
|
|
|
@ -64,6 +64,8 @@ else:
|
|||
M.set(key, real_value, coded_value)
|
||||
dict.__setitem__(self, key, M)
|
||||
except http_cookies.CookieError:
|
||||
if not hasattr(self, 'bad_cookies'):
|
||||
self.bad_cookies = set()
|
||||
self.bad_cookies.add(key)
|
||||
dict.__setitem__(self, key, http_cookies.Morsel())
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ from __future__ import unicode_literals
|
|||
|
||||
import datetime
|
||||
import time
|
||||
import sys
|
||||
from email.header import Header
|
||||
try:
|
||||
from urllib.parse import urlparse
|
||||
|
@ -160,7 +161,7 @@ class HttpResponseBase(six.Iterator):
|
|||
except UnicodeError as e:
|
||||
if mime_encode:
|
||||
# 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:
|
||||
e.reason += ', HTTP response headers must be in %s format' % charset
|
||||
raise
|
||||
|
|
|
@ -99,7 +99,7 @@ class Origin(object):
|
|||
self.name = name
|
||||
|
||||
def reload(self):
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError('subclasses of Origin must provide a reload() method')
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
@ -385,7 +385,7 @@ class TokenParser(object):
|
|||
"""
|
||||
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):
|
||||
"""
|
||||
|
@ -622,34 +622,17 @@ class FilterExpression(object):
|
|||
|
||||
def args_check(name, func, 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.
|
||||
func = getattr(func, '_decorated_function', func)
|
||||
args, varargs, varkw, defaults = getargspec(func)
|
||||
# First argument is filter input.
|
||||
args.pop(0)
|
||||
if defaults:
|
||||
nondefs = args[:-len(defaults)]
|
||||
else:
|
||||
nondefs = args
|
||||
# Args without defaults must be provided.
|
||||
try:
|
||||
for arg in nondefs:
|
||||
provided.pop(0)
|
||||
except IndexError:
|
||||
# Not enough
|
||||
alen = len(args)
|
||||
dlen = len(defaults or [])
|
||||
# Not enough OR Too many
|
||||
if plen < (alen - dlen) or plen > alen:
|
||||
raise TemplateSyntaxError("%s requires %d arguments, %d provided" %
|
||||
(name, len(nondefs), 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))
|
||||
(name, alen - dlen, plen))
|
||||
|
||||
return True
|
||||
args_check = staticmethod(args_check)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"""Default tags used by the template system, available to all templates."""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
@ -328,6 +329,7 @@ class RegroupNode(Node):
|
|||
return ''
|
||||
|
||||
def include_is_allowed(filepath):
|
||||
filepath = os.path.abspath(filepath)
|
||||
for root in settings.ALLOWED_INCLUDE_ROOTS:
|
||||
if filepath.startswith(root):
|
||||
return True
|
||||
|
|
|
@ -61,7 +61,7 @@ class BaseLoader(object):
|
|||
name.
|
||||
|
||||
"""
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError('subclasses of BaseLoader must provide a load_template_source() method')
|
||||
|
||||
def reset(self):
|
||||
"""
|
||||
|
|
|
@ -7,4 +7,4 @@ from django.test.testcases import (TestCase, TransactionTestCase,
|
|||
SimpleTestCase, LiveServerTestCase, skipIfDBFeature,
|
||||
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.
|
||||
"""
|
||||
|
||||
test_suite = TestSuite
|
||||
test_runner = unittest.TextTestRunner
|
||||
test_loader = defaultTestLoader
|
||||
reorder_by = (TestCase, )
|
||||
option_list = (
|
||||
|
@ -42,7 +44,7 @@ class DiscoverRunner(object):
|
|||
unittest.installHandler()
|
||||
|
||||
def build_suite(self, test_labels=None, extra_tests=None, **kwargs):
|
||||
suite = TestSuite()
|
||||
suite = self.test_suite()
|
||||
test_labels = test_labels or ['.']
|
||||
extra_tests = extra_tests or []
|
||||
|
||||
|
@ -107,7 +109,7 @@ class DiscoverRunner(object):
|
|||
return setup_databases(self.verbosity, self.interactive, **kwargs)
|
||||
|
||||
def run_suite(self, suite, **kwargs):
|
||||
return unittest.TextTestRunner(
|
||||
return self.test_runner(
|
||||
verbosity=self.verbosity,
|
||||
failfast=self.failfast,
|
||||
).run(suite)
|
||||
|
@ -201,7 +203,8 @@ def reorder_suite(suite, classes):
|
|||
classes[1], etc. Tests with no match in classes are placed last.
|
||||
"""
|
||||
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)
|
||||
for i in range(class_count):
|
||||
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 with no match found in classes are place in bins[-1]
|
||||
"""
|
||||
suite_class = type(suite)
|
||||
for test in suite:
|
||||
if isinstance(test, unittest.TestSuite):
|
||||
if isinstance(test, suite_class):
|
||||
partition_suite(test, classes, bins)
|
||||
else:
|
||||
for i in range(len(classes)):
|
||||
|
|
|
@ -225,12 +225,14 @@ class SimpleTestCase(unittest.TestCase):
|
|||
return override_settings(**kwargs)
|
||||
|
||||
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
|
||||
redirect URL can be loaded.
|
||||
|
||||
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:
|
||||
msg_prefix += ": "
|
||||
|
@ -264,14 +266,15 @@ class SimpleTestCase(unittest.TestCase):
|
|||
url = response.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
|
||||
# to obtain the original response.
|
||||
self.assertEqual(redirect_response.status_code, target_status_code,
|
||||
msg_prefix + "Couldn't retrieve redirection page '%s':"
|
||||
" response code was %d (expected %d)" %
|
||||
(path, redirect_response.status_code, target_status_code))
|
||||
# Get the redirection page, using the same client that was used
|
||||
# to obtain the original response.
|
||||
self.assertEqual(redirect_response.status_code, target_status_code,
|
||||
msg_prefix + "Couldn't retrieve redirection page '%s':"
|
||||
" response code was %d (expected %d)" %
|
||||
(path, redirect_response.status_code, target_status_code))
|
||||
|
||||
e_scheme, e_netloc, e_path, e_query, e_fragment = urlsplit(
|
||||
expected_url)
|
||||
|
@ -696,6 +699,9 @@ class TransactionTestCase(SimpleTestCase):
|
|||
# Subclasses can enable only a subset of apps for faster tests
|
||||
available_apps = None
|
||||
|
||||
# Subclasses can define fixtures which will be automatically installed.
|
||||
fixtures = None
|
||||
|
||||
def _pre_setup(self):
|
||||
"""Performs any pre-test setup. This includes:
|
||||
|
||||
|
@ -743,7 +749,7 @@ class TransactionTestCase(SimpleTestCase):
|
|||
if self.reset_sequences:
|
||||
self._reset_sequences(db_name)
|
||||
|
||||
if hasattr(self, 'fixtures'):
|
||||
if self.fixtures:
|
||||
# We have to use this slightly awkward syntax due to the fact
|
||||
# that we're using *args and **kwargs together.
|
||||
call_command('loaddata', *self.fixtures,
|
||||
|
@ -835,7 +841,7 @@ class TestCase(TransactionTestCase):
|
|||
disable_transaction_methods()
|
||||
|
||||
for db_name in self._databases_names(include_mirrors=False):
|
||||
if hasattr(self, 'fixtures'):
|
||||
if self.fixtures:
|
||||
try:
|
||||
call_command('loaddata', *self.fixtures,
|
||||
**{
|
||||
|
|
|
@ -126,10 +126,10 @@ class BaseArchive(object):
|
|||
return True
|
||||
|
||||
def extract(self):
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError('subclasses of BaseArchive must provide an extract() method')
|
||||
|
||||
def list(self):
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError('subclasses of BaseArchive must provide a list() method')
|
||||
|
||||
|
||||
class TarArchive(BaseArchive):
|
||||
|
|
|
@ -18,11 +18,10 @@ import calendar
|
|||
import datetime
|
||||
|
||||
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.encoding import force_text
|
||||
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_escaped = re.compile(r'\\(.)')
|
||||
|
@ -48,7 +47,7 @@ class TimeFormat(Formatter):
|
|||
# or time objects (against established django policy).
|
||||
if isinstance(obj, datetime.datetime):
|
||||
if is_naive(obj):
|
||||
self.timezone = LocalTimezone(obj)
|
||||
self.timezone = get_default_timezone()
|
||||
else:
|
||||
self.timezone = obj.tzinfo
|
||||
|
||||
|
@ -66,7 +65,7 @@ class TimeFormat(Formatter):
|
|||
|
||||
def B(self):
|
||||
"Swatch Internet time"
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError('may be implemented in a future release')
|
||||
|
||||
def e(self):
|
||||
"""
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
import datetime
|
||||
import re
|
||||
from django.utils import six
|
||||
from django.utils.timezone import utc
|
||||
from django.utils.tzinfo import FixedOffset
|
||||
from django.utils.timezone import utc, get_fixed_timezone
|
||||
|
||||
|
||||
date_re = re.compile(
|
||||
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})?$'
|
||||
)
|
||||
|
||||
|
||||
def parse_date(value):
|
||||
"""Parses a string and return a datetime.date.
|
||||
|
||||
|
@ -59,7 +60,7 @@ def parse_datetime(value):
|
|||
"""Parses a string and return a datetime.datetime.
|
||||
|
||||
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.
|
||||
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:])
|
||||
if tzinfo[0] == '-':
|
||||
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['tzinfo'] = tzinfo
|
||||
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
|
||||
object. Subclasses should override this.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError('subclasses of SyndicationFeed must provide a write() method')
|
||||
|
||||
def writeString(self, encoding):
|
||||
"""
|
||||
|
|
|
@ -257,7 +257,7 @@ class LazyObject(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
|
||||
__dir__ = new_method_proxy(dir)
|
||||
|
|
|
@ -84,24 +84,22 @@ class AdminEmailHandler(logging.Handler):
|
|||
record.getMessage()
|
||||
)
|
||||
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:
|
||||
subject = '%s: %s' % (
|
||||
record.levelname,
|
||||
record.getMessage()
|
||||
)
|
||||
request = None
|
||||
request_repr = "Request repr() unavailable."
|
||||
request_repr = "unavailable"
|
||||
subject = self.format_subject(subject)
|
||||
|
||||
if record.exc_info:
|
||||
exc_info = record.exc_info
|
||||
stack_trace = '\n'.join(traceback.format_exception(*record.exc_info))
|
||||
else:
|
||||
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)
|
||||
html_message = reporter.get_traceback_html() if self.include_html else None
|
||||
mail.mail_admins(subject, message, fail_silently=True,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from __future__ import absolute_import # Avoid importing `importlib` from this package.
|
||||
|
||||
import copy
|
||||
import imp
|
||||
from importlib import import_module
|
||||
import os
|
||||
|
@ -34,6 +35,43 @@ def import_by_path(dotted_path, error_prefix=''):
|
|||
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):
|
||||
"""See if 'module' is in 'package'."""
|
||||
name = ".".join([package.__name__, module_name])
|
||||
|
|
|
@ -92,7 +92,7 @@ def normalize(pattern):
|
|||
result.append(".")
|
||||
elif ch == '|':
|
||||
# FIXME: One day we'll should do this, but not in 1.0.
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError('Awaiting Implementation')
|
||||
elif ch == "^":
|
||||
pass
|
||||
elif ch == '$':
|
||||
|
|
|
@ -18,7 +18,8 @@ from django.conf import settings
|
|||
from django.utils import six
|
||||
|
||||
__all__ = [
|
||||
'utc', 'get_default_timezone', 'get_current_timezone',
|
||||
'utc', 'get_fixed_timezone',
|
||||
'get_default_timezone', 'get_current_timezone',
|
||||
'activate', 'deactivate', 'override',
|
||||
'is_naive', 'is_aware', 'make_aware', 'make_naive',
|
||||
]
|
||||
|
@ -47,19 +48,45 @@ class UTC(tzinfo):
|
|||
def dst(self, dt):
|
||||
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):
|
||||
"""
|
||||
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
|
||||
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):
|
||||
# This code is moved in __init__ to execute it as late as possible
|
||||
# See get_default_timezone().
|
||||
self.STDOFFSET = timedelta(seconds=-_time.timezone)
|
||||
if _time.daylight:
|
||||
self.DSTOFFSET = timedelta(seconds=-_time.altzone)
|
||||
|
@ -68,9 +95,6 @@ class ReferenceLocalTimezone(tzinfo):
|
|||
self.DSTDIFF = self.DSTOFFSET - self.STDOFFSET
|
||||
tzinfo.__init__(self)
|
||||
|
||||
def __repr__(self):
|
||||
return "<LocalTimezone>"
|
||||
|
||||
def utcoffset(self, dt):
|
||||
if self._isdst(dt):
|
||||
return self.DSTOFFSET
|
||||
|
@ -84,8 +108,7 @@ class ReferenceLocalTimezone(tzinfo):
|
|||
return ZERO
|
||||
|
||||
def tzname(self, dt):
|
||||
is_dst = False if dt is None else self._isdst(dt)
|
||||
return _time.tzname[is_dst]
|
||||
return _time.tzname[self._isdst(dt)]
|
||||
|
||||
def _isdst(self, dt):
|
||||
tt = (dt.year, dt.month, dt.day,
|
||||
|
@ -103,6 +126,10 @@ class LocalTimezone(ReferenceLocalTimezone):
|
|||
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):
|
||||
try:
|
||||
return super(LocalTimezone, self)._isdst(dt)
|
||||
|
@ -116,6 +143,17 @@ class LocalTimezone(ReferenceLocalTimezone):
|
|||
utc = pytz.utc if pytz else UTC()
|
||||
"""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,
|
||||
# wrap the expression in a function and cache the result.
|
||||
_localtime = None
|
||||
|
@ -125,8 +163,6 @@ def get_default_timezone():
|
|||
Returns the default time zone as a tzinfo instance.
|
||||
|
||||
This is the time zone defined by settings.TIME_ZONE.
|
||||
|
||||
See also :func:`get_current_timezone`.
|
||||
"""
|
||||
global _localtime
|
||||
if _localtime is None:
|
||||
|
|
|
@ -668,7 +668,10 @@ def parse_accept_lang_header(lang_string):
|
|||
if first:
|
||||
return []
|
||||
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
|
||||
priority = 1.0
|
||||
result.append((lang, priority))
|
||||
|
|
|
@ -2,11 +2,18 @@
|
|||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import time
|
||||
from datetime import timedelta, tzinfo
|
||||
import time
|
||||
import warnings
|
||||
|
||||
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
|
||||
# be called with no arguments". FixedOffset and LocalTimezone don't honor this
|
||||
# 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):
|
||||
"Fixed offset in minutes east from UTC."
|
||||
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):
|
||||
self.__offset = offset
|
||||
offset = self.__offset.seconds // 60
|
||||
|
@ -48,6 +59,10 @@ class FixedOffset(tzinfo):
|
|||
class LocalTimezone(tzinfo):
|
||||
"Proxy timezone information from time module."
|
||||
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)
|
||||
self.__dt = 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`
|
||||
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.
|
||||
====================================================
|
||||
|
|
|
@ -34,6 +34,8 @@ Decisions on new committers will follow the process explained in
|
|||
existing committer privately. Public requests for commit access are potential
|
||||
flame-war starters, and will simply be ignored.
|
||||
|
||||
.. _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.
|
||||
|
||||
After review
|
||||
------------
|
||||
~~~~~~~~~~~~
|
||||
|
||||
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
|
||||
|
@ -225,7 +225,8 @@ commits, you would run::
|
|||
|
||||
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>
|
||||
|
||||
|
@ -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.
|
||||
|
||||
Note that the committer is likely to squash the review commit into the previous commit
|
||||
when committing the code.
|
||||
Note that the committer is likely to squash the review commit into the previous
|
||||
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
|
||||
-------
|
||||
|
|
|
@ -117,9 +117,9 @@ these changes.
|
|||
* The ``mod_python`` request handler will be removed. The ``mod_wsgi``
|
||||
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.
|
||||
The :attr:`~django.test.client.Response.templates` attribute should be
|
||||
The :attr:`~django.test.Response.templates` attribute should be
|
||||
used instead.
|
||||
|
||||
* The ``django.test.simple.DjangoTestRunner`` will be removed.
|
||||
|
@ -417,6 +417,8 @@ these changes.
|
|||
|
||||
* ``django.utils.importlib`` will be removed.
|
||||
|
||||
* ``django.utils.tzinfo`` will be removed.
|
||||
|
||||
* ``django.utils.unittest`` 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
|
||||
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
|
||||
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
|
||||
Django with pip<installing-official-release>`. You can install ``distribute``
|
||||
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
|
||||
(see below).
|
||||
|
||||
1. Inside ``django-polls/dist``, untar the new package
|
||||
``django-polls-0.1.tar.gz`` (e.g. ``tar xzvf django-polls-0.1.tar.gz``). If
|
||||
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_.
|
||||
1. To install the package, use pip (you already :ref:`installed it
|
||||
<installing-reusable-apps-prerequisites>`, right?)::
|
||||
|
||||
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
|
||||
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
|
||||
2. With luck, your Django project should now work correctly again. Run the
|
||||
server again to confirm this.
|
||||
|
||||
4. To uninstall the package, use pip (you already :ref:`installed it
|
||||
<installing-reusable-apps-prerequisites>`, right?)::
|
||||
3. To uninstall the package, use pip::
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
----------------------
|
||||
|
||||
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``
|
||||
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
|
||||
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
|
||||
>>> client = Client()
|
||||
|
||||
|
@ -494,7 +494,7 @@ class::
|
|||
"""
|
||||
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)
|
||||
response = self.client.get(reverse('polls:index'))
|
||||
self.assertQuerysetEqual(
|
||||
|
|
|
@ -142,7 +142,7 @@ Methods
|
|||
.. versionchanged:: 1.6
|
||||
|
||||
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)
|
||||
|
||||
|
|
|
@ -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/download.html
|
||||
|
||||
.. _spatialitebuild :
|
||||
.. _spatialitebuild:
|
||||
|
||||
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::
|
||||
|
||||
$ cd libspatialite-amalgamation-2.3.1
|
||||
$ ./configure # May need to modified, see notes below.
|
||||
$ ./configure # May need to be modified, see notes below.
|
||||
$ make
|
||||
$ sudo make install
|
||||
$ cd .... _spatialite
|
||||
$ cd ..
|
||||
|
||||
.. _spatialite_tools:
|
||||
|
||||
Finally, do the same for the SpatiaLite tools::
|
||||
|
||||
$ cd spatialite-tools-2.3.1
|
||||
$ ./configure # May need to modified, see notes below.
|
||||
$ ./configure # May need to be modified, see notes below.
|
||||
$ make
|
||||
$ sudo make install
|
||||
$ cd ..
|
||||
|
|
|
@ -1638,6 +1638,15 @@ Examples::
|
|||
management.call_command('flush', verbosity=0, interactive=False)
|
||||
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
|
||||
==================
|
||||
|
||||
|
|
|
@ -79,6 +79,20 @@ GZip middleware
|
|||
|
||||
.. 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
|
||||
browsers).
|
||||
|
||||
|
|
|
@ -221,6 +221,12 @@ Django quotes column and table names behind the scenes.
|
|||
|
||||
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``
|
||||
---------------
|
||||
|
||||
|
|
|
@ -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
|
||||
``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
|
||||
~~~~~~~
|
||||
|
||||
|
@ -2268,7 +2274,8 @@ SQL equivalent::
|
|||
(The exact SQL syntax varies for each database engine.)
|
||||
|
||||
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
|
||||
|
||||
|
@ -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.
|
||||
|
||||
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
|
||||
|
||||
|
@ -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.
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -565,7 +565,7 @@ setting_changed
|
|||
|
||||
This signal is sent when the value of a setting is changed through 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
|
||||
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
|
||||
^^^^^^
|
||||
|
||||
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
|
||||
``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"
|
||||
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
|
||||
``autoescape`` is ``True``, the link text and URLs will be escaped using
|
||||
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
|
||||
^^^^^^^^^^^
|
||||
|
||||
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.
|
||||
|
||||
**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.
|
||||
|
||||
.. 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()
|
||||
|
||||
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``
|
||||
=======================
|
||||
|
||||
.. deprecated:: 1.7
|
||||
Use :mod:`~django.utils.timezone` instead.
|
||||
|
||||
.. module:: django.utils.tzinfo
|
||||
: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.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
Use :func:`~django.utils.timezone.get_fixed_timezone` instead.
|
||||
|
||||
.. class:: LocalTimezone
|
||||
|
||||
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
|
||||
-------
|
||||
|
||||
:meth:`django.test.client.Client.login` has changed
|
||||
:meth:`django.test.Client.login` has changed
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Old (0.96)::
|
||||
|
|
|
@ -99,7 +99,7 @@ one fell swoop.
|
|||
Testing improvements
|
||||
--------------------
|
||||
|
||||
.. currentmodule:: django.test.client
|
||||
.. currentmodule:: django.test
|
||||
|
||||
A couple of small but very useful improvements have been made to the
|
||||
:doc:`testing framework </topics/testing/index>`:
|
||||
|
|
|
@ -285,8 +285,6 @@ full description, and some important notes on database support.
|
|||
Test client improvements
|
||||
------------------------
|
||||
|
||||
.. currentmodule:: django.test.client
|
||||
|
||||
A couple of small -- but highly useful -- improvements have been made to the
|
||||
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
|
||||
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
|
||||
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.
|
||||
|
|
|
@ -150,7 +150,7 @@ requests. These include:
|
|||
* Improved tools for accessing and manipulating the current Site via
|
||||
``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.
|
||||
|
||||
* A new test assertion --
|
||||
|
@ -318,7 +318,7 @@ Test client response ``template`` attribute
|
|||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
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``
|
||||
attribute containing information about templates rendered in generating the
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
``DjangoTestRunner``
|
||||
|
|
|
@ -295,7 +295,7 @@ requests. These include:
|
|||
:class:`~django.contrib.sites.models.Site` object in
|
||||
: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.
|
||||
|
||||
* A new test assertion --
|
||||
|
@ -715,7 +715,7 @@ Test client response ``template`` attribute
|
|||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
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``
|
||||
attribute containing information about templates rendered in generating the
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
``DjangoTestRunner``
|
||||
|
|
|
@ -26,6 +26,6 @@ header and browsers seem to ignore JavaScript there.
|
|||
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
|
||||
'_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
|
||||
``SCRIPT_NAME`` environment variable or the :setting:`FORCE_SCRIPT_NAME`
|
||||
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
|
||||
'_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
|
||||
|
||||
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()``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -814,7 +829,7 @@ Miscellaneous
|
|||
``{% url %}`` tag, it causes template rendering to fail like always when
|
||||
``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
|
||||
: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.
|
||||
|
||||
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
|
||||
way as the :class:`~django.forms.ModelForm` ``Meta.fields`` attribute. Alternatively,
|
||||
you can set set the ``form_class`` attribute to a ``ModelForm`` that explicitly
|
||||
defines the fields to be used. Defining an ``UpdateView`` or ``CreateView``
|
||||
subclass to be used with a model but without an explicit list of fields is
|
||||
deprecated.
|
||||
the ``fields`` attribute (new in Django 1.6), which is a list of model fields
|
||||
and works in the same way as the :class:`~django.forms.ModelForm`
|
||||
``Meta.fields`` attribute. Alternatively, you can set set the ``form_class``
|
||||
attribute to a ``ModelForm`` that explicitly defines the fields to be used.
|
||||
Defining an ``UpdateView`` or ``CreateView`` subclass to be used with a model
|
||||
but without an explicit list of fields is deprecated.
|
||||
|
||||
.. _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
|
||||
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.pre_migrate` and
|
||||
:data:`~django.db.models.signals.post_migrate` respectively. The
|
||||
|
@ -285,6 +285,19 @@ Templates
|
|||
* ``TypeError`` exceptions are not longer silenced when raised during the
|
||||
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
|
||||
=====================================
|
||||
|
||||
|
@ -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``
|
||||
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
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
|
@ -345,13 +368,18 @@ Miscellaneous
|
|||
when called on an instance without a primary key value. This is done to
|
||||
avoid mutable ``__hash__`` values in containers.
|
||||
|
||||
* The :meth:`django.db.backends.sqlite3.DatabaseCreation.sql_create_model`
|
||||
will now create :class:`~django.db.models.AutoField` columns in SQLite
|
||||
databases using the ``AUTOINCREMENT`` option, which guarantees monotonic
|
||||
* :class:`~django.db.models.AutoField` columns in SQLite databases will now be
|
||||
created using the ``AUTOINCREMENT`` option, which guarantees monotonic
|
||||
increments. This will cause primary key numbering behavior to change on
|
||||
SQLite, becoming consistent with most other SQL databases. If you have a
|
||||
database created with an older version of Django, you will need to migrate
|
||||
it to take advantage of this feature. See ticket #10164 for details.
|
||||
SQLite, becoming consistent with most other SQL databases. This will only
|
||||
apply to newly created tables. If you have a database created with an older
|
||||
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
|
||||
: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
|
||||
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``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ Final releases
|
|||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
1.5.3
|
||||
1.5.2
|
||||
1.5.1
|
||||
1.5
|
||||
|
@ -45,6 +46,7 @@ Final releases
|
|||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
1.4.7
|
||||
1.4.6
|
||||
1.4.5
|
||||
1.4.4
|
||||
|
|
|
@ -869,8 +869,7 @@ would test three possible User models -- the default, plus the two User
|
|||
models provided by ``auth`` app::
|
||||
|
||||
from django.contrib.auth.tests.utils import skipIfCustomUser
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
from django.test import TestCase, override_settings
|
||||
|
||||
|
||||
class ApplicationTestCase(TestCase):
|
||||
|
|
|
@ -341,40 +341,37 @@ been well-tested and are easy to use.
|
|||
Cache arguments
|
||||
---------------
|
||||
|
||||
In addition to the defining the engine and name of the each cache
|
||||
backend, each cache backend can be given additional arguments to
|
||||
control caching behavior. These arguments are provided as additional
|
||||
keys in the :setting:`CACHES` setting. Valid arguments are as follows:
|
||||
Each cache backend can be given additional arguments to control caching
|
||||
behavior. These arguments are provided as additional keys in the
|
||||
:setting:`CACHES` setting. Valid arguments are as follows:
|
||||
|
||||
* :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).
|
||||
|
||||
* :setting:`OPTIONS <CACHES-OPTIONS>`: Any options that should be
|
||||
passed to cache backend. The list options understood by each
|
||||
backend vary with each backend.
|
||||
passed to the cache backend. The list of valid options will vary
|
||||
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.,
|
||||
the ``locmem``, ``filesystem`` and ``database`` backends) will
|
||||
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
|
||||
defaults to ``300``.
|
||||
|
||||
* ``CULL_FREQUENCY``: The fraction of entries that are culled
|
||||
when ``MAX_ENTRIES`` is reached. The actual ratio is
|
||||
``1/CULL_FREQUENCY``, so set ``CULL_FREQUENCY``: to ``2`` to
|
||||
cull half of the entries when ``MAX_ENTRIES`` is reached.
|
||||
``1 / CULL_FREQUENCY``, so set ``CULL_FREQUENCY`` to ``2`` to
|
||||
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
|
||||
entire cache will be dumped when ``MAX_ENTRIES`` is reached.
|
||||
This makes culling *much* 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.
|
||||
On some backends (``database`` in particular) this makes culling *much*
|
||||
faster at the expense of more cache misses.
|
||||
|
||||
* :setting:`KEY_PREFIX <CACHES-KEY_PREFIX>`: A string that will be
|
||||
automatically included (prepended by default) to all cache keys
|
||||
|
@ -1176,7 +1173,10 @@ site's performance:
|
|||
and ``Last-Modified`` headers.
|
||||
|
||||
* :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
|
||||
===========================
|
||||
|
|
|
@ -306,6 +306,17 @@ instead of::
|
|||
|
||||
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
|
||||
==============
|
||||
|
||||
|
|
|
@ -5,18 +5,18 @@ Advanced testing topics
|
|||
The request factory
|
||||
===================
|
||||
|
||||
.. module:: django.test.client
|
||||
.. currentmodule:: django.test
|
||||
|
||||
.. 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
|
||||
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
|
||||
view function the same way as you would test any other function -- as
|
||||
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:
|
||||
|
||||
* 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::
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
from django.test import TestCase, RequestFactory
|
||||
|
||||
class SimpleTest(TestCase):
|
||||
def setUp(self):
|
||||
|
@ -165,8 +164,6 @@ exception will be raised.
|
|||
Advanced features of ``TransactionTestCase``
|
||||
============================================
|
||||
|
||||
.. currentmodule:: django.test
|
||||
|
||||
.. attribute:: TransactionTestCase.available_apps
|
||||
|
||||
.. versionadded:: 1.6
|
||||
|
@ -341,6 +338,25 @@ execute and tear down the test suite.
|
|||
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
|
||||
|
||||
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
|
||||
---------------
|
||||
|
||||
.. module:: django.test.client
|
||||
:synopsis: Django's test client.
|
||||
|
||||
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
|
||||
programmatically.
|
||||
|
@ -349,10 +346,10 @@ A comprehensive test suite should use a combination of both test types.
|
|||
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::
|
||||
|
||||
>>> from django.test.client import Client
|
||||
>>> from django.test import Client
|
||||
>>> c = Client()
|
||||
>>> response = c.post('/login/', {'username': 'john', 'password': 'smith'})
|
||||
>>> response.status_code
|
||||
|
@ -413,7 +410,7 @@ Note a few important things about how the test client works:
|
|||
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)
|
||||
|
||||
|
@ -424,8 +421,8 @@ Use the ``django.test.client.Client`` class to make requests.
|
|||
>>> c = Client(HTTP_USER_AGENT='Mozilla/5.0')
|
||||
|
||||
The values from the ``extra`` keywords arguments passed to
|
||||
:meth:`~django.test.client.Client.get()`,
|
||||
:meth:`~django.test.client.Client.post()`, etc. have precedence over
|
||||
:meth:`~django.test.Client.get()`,
|
||||
:meth:`~django.test.Client.post()`, etc. have precedence over
|
||||
the defaults passed to the class constructor.
|
||||
|
||||
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::
|
||||
|
||||
import unittest
|
||||
from django.test.client import Client
|
||||
from django.test import Client
|
||||
|
||||
class SimpleTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
|
@ -797,15 +794,13 @@ The following is a simple unit test using the test client::
|
|||
|
||||
.. seealso::
|
||||
|
||||
:class:`django.test.client.RequestFactory`
|
||||
:class:`django.test.RequestFactory`
|
||||
|
||||
.. _django-testcase-subclasses:
|
||||
|
||||
Provided test case classes
|
||||
--------------------------
|
||||
|
||||
.. currentmodule:: django.test
|
||||
|
||||
Normal Python unit test classes extend a base class of
|
||||
:class:`unittest.TestCase`. Django provides a few extensions of this base class:
|
||||
|
||||
|
@ -847,7 +842,7 @@ functionality like:
|
|||
for equality.
|
||||
|
||||
* 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>`.
|
||||
|
||||
.. 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::
|
||||
|
||||
import unittest
|
||||
from django.test.client import Client
|
||||
from django.test import Client
|
||||
|
||||
class SimpleTest(unittest.TestCase):
|
||||
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
|
||||
attribute::
|
||||
|
||||
from django.test import TestCase
|
||||
from django.test.client import Client
|
||||
from django.test import TestCase, Client
|
||||
|
||||
class MyTestClient(Client):
|
||||
# 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
|
||||
in the ``with`` block and reset its value to the previous state afterwards.
|
||||
|
||||
.. currentmodule:: django.test.utils
|
||||
|
||||
.. function:: override_settings
|
||||
|
||||
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
|
||||
: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::
|
||||
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
from django.test import TestCase, override_settings
|
||||
|
||||
class LoginTestCase(TestCase):
|
||||
|
||||
|
@ -1351,8 +1342,7 @@ used like this::
|
|||
|
||||
The decorator can also be applied to test case classes::
|
||||
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
from django.test import TestCase, override_settings
|
||||
|
||||
@override_settings(LOGIN_URL='/other/login/')
|
||||
class LoginTestCase(TestCase):
|
||||
|
@ -1361,6 +1351,11 @@ The decorator can also be applied to test case classes::
|
|||
response = self.client.get('/sekrit/')
|
||||
self.assertRedirects(response, '/other/login/?next=/sekrit/')
|
||||
|
||||
.. versionchanged:: 1.7
|
||||
|
||||
Previously, ``override_settings`` was imported from
|
||||
``django.test.utils``.
|
||||
|
||||
.. note::
|
||||
|
||||
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
|
||||
~~~~~~~~~~
|
||||
|
||||
.. currentmodule:: django.test
|
||||
|
||||
As Python's normal :class:`unittest.TestCase` class implements assertion methods
|
||||
such as :meth:`~unittest.TestCase.assertTrue` and
|
||||
: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
|
||||
: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
|
||||
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
|
||||
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)
|
||||
|
||||
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.core import mail
|
||||
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.
|
||||
from django.contrib.auth import get_permission_codename
|
||||
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
|
||||
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',))
|
||||
class AdminViewFormUrlTest(TestCase):
|
||||
|
@ -1296,6 +1310,40 @@ class AdminViewPermissionsTest(TestCase):
|
|||
response = self.client.get('/test_admin/admin/secure-view/')
|
||||
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',))
|
||||
class AdminViewsNoUrlTest(TestCase):
|
||||
|
|
|
@ -549,6 +549,79 @@ class DateTimePickerSeleniumFirefoxTests(AdminSeleniumWebDriverTestCase):
|
|||
self.assertEqual(
|
||||
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):
|
||||
webdriver_class = 'selenium.webdriver.chrome.webdriver.WebDriver'
|
||||
|
||||
|
|
|
@ -6,7 +6,8 @@ import re
|
|||
|
||||
from django.db import connection
|
||||
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 .models import Author, Publisher, Book, Store
|
||||
|
|
|
@ -8,7 +8,8 @@ from operator import attrgetter
|
|||
from django.core.exceptions import FieldError
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
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 .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)
|
||||
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