This commit is contained in:
Alex Gaynor 2013-09-14 12:32:45 -07:00
commit 5c645ec81a
162 changed files with 1298 additions and 589 deletions

View File

@ -397,6 +397,7 @@ answer newbie questions, and generally made Django that much better:
Yann Malet Yann Malet
Frantisek Malina <vizualbod@vizualbod.com> Frantisek Malina <vizualbod@vizualbod.com>
Mike Malone <mjmalone@gmail.com> Mike Malone <mjmalone@gmail.com>
Curtis Maloney (FunkyBob) <curtis@tinbrain.net>
Martin Maney <http://www.chipy.org/Martin_Maney> Martin Maney <http://www.chipy.org/Martin_Maney>
Michael Manfre <mmanfre@gmail.com> Michael Manfre <mmanfre@gmail.com>
Javier Mansilla <javimansilla@gmail.com> Javier Mansilla <javimansilla@gmail.com>

View File

@ -7,35 +7,8 @@ from django.contrib.admin.sites import AdminSite, site
from django.contrib.admin.filters import (ListFilter, SimpleListFilter, from django.contrib.admin.filters import (ListFilter, SimpleListFilter,
FieldListFilter, BooleanFieldListFilter, RelatedFieldListFilter, FieldListFilter, BooleanFieldListFilter, RelatedFieldListFilter,
ChoicesFieldListFilter, DateFieldListFilter, AllValuesFieldListFilter) ChoicesFieldListFilter, DateFieldListFilter, AllValuesFieldListFilter)
from django.utils.module_loading import autodiscover_modules
def autodiscover(): def autodiscover():
""" autodiscover_modules('admin', register_to=site)
Auto-discover INSTALLED_APPS admin.py modules and fail silently when
not present. This forces an import on them to register any admin bits they
may want.
"""
import copy
from importlib import import_module
from django.conf import settings
from django.utils.module_loading import module_has_submodule
for app in settings.INSTALLED_APPS:
mod = import_module(app)
# Attempt to import the app's admin module.
try:
before_import_registry = copy.copy(site._registry)
import_module('%s.admin' % app)
except:
# Reset the model registry to the state before the last import as
# this import will have to reoccur on the next request and this
# could raise NotRegistered and AlreadyRegistered exceptions
# (see #8245).
site._registry = before_import_registry
# Decide whether to bubble up this error. If the app just
# doesn't have an admin module, we can ignore the error
# attempting to import it, otherwise we want it to bubble up.
if module_has_submodule(mod, 'admin'):
raise

View File

@ -33,26 +33,26 @@ class ListFilter(object):
""" """
Returns True if some choices would be output for this filter. Returns True if some choices would be output for this filter.
""" """
raise NotImplementedError raise NotImplementedError('subclasses of ListFilter must provide a has_output() method')
def choices(self, cl): def choices(self, cl):
""" """
Returns choices ready to be output in the template. Returns choices ready to be output in the template.
""" """
raise NotImplementedError raise NotImplementedError('subclasses of ListFilter must provide a choices() method')
def queryset(self, request, queryset): def queryset(self, request, queryset):
""" """
Returns the filtered queryset. Returns the filtered queryset.
""" """
raise NotImplementedError raise NotImplementedError('subclasses of ListFilter must provide a queryset() method')
def expected_parameters(self): def expected_parameters(self):
""" """
Returns the list of parameter names that are expected from the Returns the list of parameter names that are expected from the
request's query string and that will be used by this filter. request's query string and that will be used by this filter.
""" """
raise NotImplementedError raise NotImplementedError('subclasses of ListFilter must provide an expected_parameters() method')
class SimpleListFilter(ListFilter): class SimpleListFilter(ListFilter):
@ -89,7 +89,9 @@ class SimpleListFilter(ListFilter):
""" """
Must be overridden to return a list of tuples (value, verbose value) Must be overridden to return a list of tuples (value, verbose value)
""" """
raise NotImplementedError raise NotImplementedError(
'The SimpleListFilter.lookups() method must be overridden to '
'return a list of tuples (value, verbose value)')
def expected_parameters(self): def expected_parameters(self):
return [self.parameter_name] return [self.parameter_name]

View File

@ -6,7 +6,7 @@ from django.contrib.auth import logout as auth_logout, REDIRECT_FIELD_NAME
from django.contrib.contenttypes import views as contenttype_views from django.contrib.contenttypes import views as contenttype_views
from django.views.decorators.csrf import csrf_protect from django.views.decorators.csrf import csrf_protect
from django.db.models.base import ModelBase from django.db.models.base import ModelBase
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured, PermissionDenied
from django.core.urlresolvers import reverse, NoReverseMatch from django.core.urlresolvers import reverse, NoReverseMatch
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.utils import six from django.utils import six
@ -232,14 +232,25 @@ class AdminSite(object):
url(r'^password_change/done/$', wrap(self.password_change_done, cacheable=True), name='password_change_done'), url(r'^password_change/done/$', wrap(self.password_change_done, cacheable=True), name='password_change_done'),
url(r'^jsi18n/$', wrap(self.i18n_javascript, cacheable=True), name='jsi18n'), url(r'^jsi18n/$', wrap(self.i18n_javascript, cacheable=True), name='jsi18n'),
url(r'^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$', wrap(contenttype_views.shortcut), name='view_on_site'), url(r'^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$', wrap(contenttype_views.shortcut), name='view_on_site'),
url(r'^(?P<app_label>\w+)/$', wrap(self.app_index), name='app_list'),
) )
# Add in each model's views. # Add in each model's views, and create a list of valid URLS for the
# app_index
valid_app_labels = []
for model, model_admin in six.iteritems(self._registry): for model, model_admin in six.iteritems(self._registry):
urlpatterns += patterns('', urlpatterns += patterns('',
url(r'^%s/%s/' % (model._meta.app_label, model._meta.model_name), include(model_admin.urls)) url(r'^%s/%s/' % (model._meta.app_label, model._meta.model_name), include(model_admin.urls))
) )
if model._meta.app_label not in valid_app_labels:
valid_app_labels.append(model._meta.app_label)
# If there were ModelAdmins registered, we should have a list of app
# labels for which we need to allow access to the app_index view,
if valid_app_labels:
regex = r'^(?P<app_label>' + '|'.join(valid_app_labels) + ')/$'
urlpatterns += patterns('',
url(regex, wrap(self.app_index), name='app_list'),
)
return urlpatterns return urlpatterns
@property @property
@ -399,44 +410,45 @@ class AdminSite(object):
def app_index(self, request, app_label, extra_context=None): def app_index(self, request, app_label, extra_context=None):
user = request.user user = request.user
has_module_perms = user.has_module_perms(app_label) has_module_perms = user.has_module_perms(app_label)
if not has_module_perms:
raise PermissionDenied
app_dict = {} app_dict = {}
for model, model_admin in self._registry.items(): for model, model_admin in self._registry.items():
if app_label == model._meta.app_label: if app_label == model._meta.app_label:
if has_module_perms: perms = model_admin.get_model_perms(request)
perms = model_admin.get_model_perms(request)
# Check whether user has any perm for this module. # Check whether user has any perm for this module.
# If so, add the module to the model_list. # If so, add the module to the model_list.
if True in perms.values(): if True in perms.values():
info = (app_label, model._meta.model_name) info = (app_label, model._meta.model_name)
model_dict = { model_dict = {
'name': capfirst(model._meta.verbose_name_plural), 'name': capfirst(model._meta.verbose_name_plural),
'object_name': model._meta.object_name, 'object_name': model._meta.object_name,
'perms': perms, 'perms': perms,
}
if perms.get('change'):
try:
model_dict['admin_url'] = reverse('admin:%s_%s_changelist' % info, current_app=self.name)
except NoReverseMatch:
pass
if perms.get('add'):
try:
model_dict['add_url'] = reverse('admin:%s_%s_add' % info, current_app=self.name)
except NoReverseMatch:
pass
if app_dict:
app_dict['models'].append(model_dict),
else:
# First time around, now that we know there's
# something to display, add in the necessary meta
# information.
app_dict = {
'name': app_label.title(),
'app_label': app_label,
'app_url': '',
'has_module_perms': has_module_perms,
'models': [model_dict],
} }
if perms.get('change', False):
try:
model_dict['admin_url'] = reverse('admin:%s_%s_changelist' % info, current_app=self.name)
except NoReverseMatch:
pass
if perms.get('add', False):
try:
model_dict['add_url'] = reverse('admin:%s_%s_add' % info, current_app=self.name)
except NoReverseMatch:
pass
if app_dict:
app_dict['models'].append(model_dict),
else:
# First time around, now that we know there's
# something to display, add in the necessary meta
# information.
app_dict = {
'name': app_label.title(),
'app_label': app_label,
'app_url': '',
'has_module_perms': has_module_perms,
'models': [model_dict],
}
if not app_dict: if not app_dict:
raise Http404('The requested admin page does not exist.') raise Http404('The requested admin page does not exist.')
# Sort the models alphabetically within each app. # Sort the models alphabetically within each app.

View File

@ -293,8 +293,9 @@ var DateTimeShortcuts = {
var date_parts = inp.value.split('-'); var date_parts = inp.value.split('-');
var year = date_parts[0]; var year = date_parts[0];
var month = parseFloat(date_parts[1]); var month = parseFloat(date_parts[1]);
var selected = new Date(inp.value);
if (year.match(/\d\d\d\d/) && month >= 1 && month <= 12) { if (year.match(/\d\d\d\d/) && month >= 1 && month <= 12) {
DateTimeShortcuts.calendars[num].drawDate(month, year); DateTimeShortcuts.calendars[num].drawDate(month, year, selected);
} }
} }

View File

@ -27,13 +27,29 @@ var CalendarNamespace = {
} }
return days; return days;
}, },
draw: function(month, year, div_id, callback) { // month = 1-12, year = 1-9999 draw: function(month, year, div_id, callback, selected) { // month = 1-12, year = 1-9999
var today = new Date(); var today = new Date();
var todayDay = today.getDate(); var todayDay = today.getDate();
var todayMonth = today.getMonth()+1; var todayMonth = today.getMonth()+1;
var todayYear = today.getFullYear(); var todayYear = today.getFullYear();
var todayClass = ''; var todayClass = '';
// Use UTC functions here because the date field does not contain time
// and using the UTC function variants prevent the local time offset
// from altering the date, specifically the day field. For example:
//
// ```
// var x = new Date('2013-10-02');
// var day = x.getDate();
// ```
//
// The day variable above will be 1 instead of 2 in, say, US Pacific time
// zone.
var isSelectedMonth = false;
if (typeof selected != 'undefined') {
isSelectedMonth = (selected.getUTCFullYear() == year && (selected.getUTCMonth()+1) == month);
}
month = parseInt(month); month = parseInt(month);
year = parseInt(year); year = parseInt(year);
var calDiv = document.getElementById(div_id); var calDiv = document.getElementById(div_id);
@ -55,7 +71,7 @@ var CalendarNamespace = {
tableRow = quickElement('tr', tableBody); tableRow = quickElement('tr', tableBody);
for (var i = 0; i < startingPos; i++) { for (var i = 0; i < startingPos; i++) {
var _cell = quickElement('td', tableRow, ' '); var _cell = quickElement('td', tableRow, ' ');
_cell.style.backgroundColor = '#f3f3f3'; _cell.className = "nonday";
} }
// Draw days of month // Draw days of month
@ -69,6 +85,13 @@ var CalendarNamespace = {
} else { } else {
todayClass=''; todayClass='';
} }
// use UTC function; see above for explanation.
if (isSelectedMonth && currentDay == selected.getUTCDate()) {
if (todayClass != '') todayClass += " ";
todayClass += "selected";
}
var cell = quickElement('td', tableRow, '', 'class', todayClass); var cell = quickElement('td', tableRow, '', 'class', todayClass);
quickElement('a', cell, currentDay, 'href', 'javascript:void(' + callback + '('+year+','+month+','+currentDay+'));'); quickElement('a', cell, currentDay, 'href', 'javascript:void(' + callback + '('+year+','+month+','+currentDay+'));');
@ -78,7 +101,7 @@ var CalendarNamespace = {
// Draw blanks after end of month (optional, but makes for valid code) // Draw blanks after end of month (optional, but makes for valid code)
while (tableRow.childNodes.length < 7) { while (tableRow.childNodes.length < 7) {
var _cell = quickElement('td', tableRow, ' '); var _cell = quickElement('td', tableRow, ' ');
_cell.style.backgroundColor = '#f3f3f3'; _cell.className = "nonday";
} }
calDiv.appendChild(calTable); calDiv.appendChild(calTable);
@ -86,7 +109,7 @@ var CalendarNamespace = {
} }
// Calendar -- A calendar instance // Calendar -- A calendar instance
function Calendar(div_id, callback) { function Calendar(div_id, callback, selected) {
// div_id (string) is the ID of the element in which the calendar will // div_id (string) is the ID of the element in which the calendar will
// be displayed // be displayed
// callback (string) is the name of a JavaScript function that will be // callback (string) is the name of a JavaScript function that will be
@ -97,14 +120,22 @@ function Calendar(div_id, callback) {
this.today = new Date(); this.today = new Date();
this.currentMonth = this.today.getMonth() + 1; this.currentMonth = this.today.getMonth() + 1;
this.currentYear = this.today.getFullYear(); this.currentYear = this.today.getFullYear();
if (typeof selected != 'undefined') {
this.selected = selected;
}
} }
Calendar.prototype = { Calendar.prototype = {
drawCurrent: function() { drawCurrent: function() {
CalendarNamespace.draw(this.currentMonth, this.currentYear, this.div_id, this.callback); CalendarNamespace.draw(this.currentMonth, this.currentYear, this.div_id, this.callback, this.selected);
}, },
drawDate: function(month, year) { drawDate: function(month, year, selected) {
this.currentMonth = month; this.currentMonth = month;
this.currentYear = year; this.currentYear = year;
if(selected) {
this.selected = selected;
}
this.drawCurrent(); this.drawCurrent();
}, },
drawPreviousMonth: function() { drawPreviousMonth: function() {

View File

@ -26,8 +26,10 @@
</div> </div>
{% if user.is_active and user.is_staff %} {% if user.is_active and user.is_staff %}
<div id="user-tools"> <div id="user-tools">
{% trans 'Welcome,' %} {% block welcome-msg %}
<strong>{% firstof user.get_short_name user.get_username %}</strong>. {% trans 'Welcome,' %}
<strong>{% firstof user.get_short_name user.get_username %}</strong>.
{% endblock %}
{% block userlinks %} {% block userlinks %}
{% url 'django-admindocs-docroot' as docsroot %} {% url 'django-admindocs-docroot' as docsroot %}
{% if docsroot %} {% if docsroot %}

View File

@ -192,7 +192,7 @@ class BasePasswordHasher(object):
""" """
Checks if the given password is correct Checks if the given password is correct
""" """
raise NotImplementedError() raise NotImplementedError('subclasses of BasePasswordHasher must provide a verify() method')
def encode(self, password, salt): def encode(self, password, salt):
""" """
@ -201,7 +201,7 @@ class BasePasswordHasher(object):
The result is normally formatted as "algorithm$salt$hash" and The result is normally formatted as "algorithm$salt$hash" and
must be fewer than 128 characters. must be fewer than 128 characters.
""" """
raise NotImplementedError() raise NotImplementedError('subclasses of BasePasswordHasher must provide an encode() method')
def safe_summary(self, encoded): def safe_summary(self, encoded):
""" """
@ -210,7 +210,7 @@ class BasePasswordHasher(object):
The result is a dictionary and will be used where the password field The result is a dictionary and will be used where the password field
must be displayed to construct a safe representation of the password. must be displayed to construct a safe representation of the password.
""" """
raise NotImplementedError() raise NotImplementedError('subclasses of BasePasswordHasher must provide a safe_summary() method')
class PBKDF2PasswordHasher(BasePasswordHasher): class PBKDF2PasswordHasher(BasePasswordHasher):

View File

@ -245,10 +245,10 @@ class AbstractBaseUser(models.Model):
return is_password_usable(self.password) return is_password_usable(self.password)
def get_full_name(self): def get_full_name(self):
raise NotImplementedError() raise NotImplementedError('subclasses of AbstractBaseUser must provide a get_full_name() method')
def get_short_name(self): def get_short_name(self):
raise NotImplementedError() raise NotImplementedError('subclasses of AbstractBaseUser must provide a get_short_name() method.')
# A few helper functions for common logic between User and AnonymousUser. # A few helper functions for common logic between User and AnonymousUser.
@ -441,16 +441,16 @@ class AnonymousUser(object):
return 1 # instances always return the same hash value return 1 # instances always return the same hash value
def save(self): def save(self):
raise NotImplementedError raise NotImplementedError("Django doesn't provide a DB representation for AnonymousUser.")
def delete(self): def delete(self):
raise NotImplementedError raise NotImplementedError("Django doesn't provide a DB representation for AnonymousUser.")
def set_password(self, raw_password): def set_password(self, raw_password):
raise NotImplementedError raise NotImplementedError("Django doesn't provide a DB representation for AnonymousUser.")
def check_password(self, raw_password): def check_password(self, raw_password):
raise NotImplementedError raise NotImplementedError("Django doesn't provide a DB representation for AnonymousUser.")
def _get_groups(self): def _get_groups(self):
return self._groups return self._groups

View File

@ -112,7 +112,7 @@ class BaseCommentNode(six.with_metaclass(RenameBaseCommentNodeMethods, template.
def get_context_value_from_queryset(self, context, qs): def get_context_value_from_queryset(self, context, qs):
"""Subclasses should override this.""" """Subclasses should override this."""
raise NotImplementedError raise NotImplementedError('subclasses of BaseCommentNode must provide a get_context_value_from_queryset() method')
class CommentListNode(BaseCommentNode): class CommentListNode(BaseCommentNode):
"""Insert a list of comments into the context.""" """Insert a list of comments into the context."""
@ -338,4 +338,3 @@ def get_comment_permalink(comment, anchor_pattern=None):
if anchor_pattern: if anchor_pattern:
return comment.get_absolute_url(anchor_pattern) return comment.get_absolute_url(anchor_pattern)
return comment.get_absolute_url() return comment.get_absolute_url()

View File

@ -101,7 +101,7 @@ class BaseSpatialOperations(object):
Returns the database column type for the geometry field on Returns the database column type for the geometry field on
the spatial backend. the spatial backend.
""" """
raise NotImplementedError raise NotImplementedError('subclasses of BaseSpatialOperations must provide a geo_db_type() method')
def get_distance(self, f, value, lookup_type): def get_distance(self, f, value, lookup_type):
""" """
@ -117,7 +117,7 @@ class BaseSpatialOperations(object):
stored procedure call to the transformation function of the spatial stored procedure call to the transformation function of the spatial
backend. backend.
""" """
raise NotImplementedError raise NotImplementedError('subclasses of BaseSpatialOperations must provide a geo_db_placeholder() method')
def get_expression_column(self, evaluator): def get_expression_column(self, evaluator):
""" """
@ -134,14 +134,14 @@ class BaseSpatialOperations(object):
raise NotImplementedError('Aggregate support not implemented for this spatial backend.') raise NotImplementedError('Aggregate support not implemented for this spatial backend.')
def spatial_lookup_sql(self, lvalue, lookup_type, value, field): def spatial_lookup_sql(self, lvalue, lookup_type, value, field):
raise NotImplementedError raise NotImplementedError('subclasses of BaseSpatialOperations must a provide spatial_lookup_sql() method')
# Routines for getting the OGC-compliant models. # Routines for getting the OGC-compliant models.
def geometry_columns(self): def geometry_columns(self):
raise NotImplementedError raise NotImplementedError('subclasses of BaseSpatialOperations must a provide geometry_columns() method')
def spatial_ref_sys(self): def spatial_ref_sys(self):
raise NotImplementedError raise NotImplementedError('subclasses of BaseSpatialOperations must a provide spatial_ref_sys() method')
@python_2_unicode_compatible @python_2_unicode_compatible
class SpatialRefSysMixin(object): class SpatialRefSysMixin(object):

View File

@ -25,9 +25,10 @@ class SpatiaLiteIntrospection(DatabaseIntrospection):
cursor = self.connection.cursor() cursor = self.connection.cursor()
try: try:
# Querying the `geometry_columns` table to get additional metadata. # Querying the `geometry_columns` table to get additional metadata.
cursor.execute('SELECT "coord_dimension", "srid", "type" ' type_col = 'type' if self.connection.ops.spatial_version < (4, 0, 0) else 'geometry_type'
'FROM "geometry_columns" ' cursor.execute('SELECT coord_dimension, srid, %s '
'WHERE "f_table_name"=%s AND "f_geometry_column"=%s', 'FROM geometry_columns '
'WHERE f_table_name=%%s AND f_geometry_column=%%s' % type_col,
(table_name, geo_col)) (table_name, geo_col))
row = cursor.fetchone() row = cursor.fetchone()
if not row: if not row:

View File

@ -9,5 +9,6 @@ class AllOGRFields(models.Model):
f_datetime = models.DateTimeField() f_datetime = models.DateTimeField()
f_time = models.TimeField() f_time = models.TimeField()
geom = models.PolygonField() geom = models.PolygonField()
point = models.PointField()
objects = models.GeoManager() objects = models.GeoManager()

View File

@ -3,11 +3,13 @@ from __future__ import unicode_literals
import os import os
from unittest import skipUnless from unittest import skipUnless
from django.core.management import call_command
from django.db import connections from django.db import connections
from django.test import TestCase from django.test import TestCase
from django.contrib.gis.gdal import HAS_GDAL from django.contrib.gis.gdal import HAS_GDAL
from django.contrib.gis.geometry.test_data import TEST_DATA from django.contrib.gis.geometry.test_data import TEST_DATA
from django.contrib.gis.tests.utils import HAS_SPATIAL_DB from django.contrib.gis.tests.utils import HAS_SPATIAL_DB
from django.utils.six import StringIO
if HAS_GDAL: if HAS_GDAL:
from django.contrib.gis.gdal import Driver from django.contrib.gis.gdal import Driver
@ -16,6 +18,22 @@ if HAS_GDAL:
from .models import AllOGRFields from .models import AllOGRFields
@skipUnless(HAS_GDAL and HAS_SPATIAL_DB, "GDAL and spatial db are required.")
class InspectDbTests(TestCase):
def test_geom_columns(self):
"""
Test the geo-enabled inspectdb command.
"""
out = StringIO()
call_command('inspectdb',
table_name_filter=lambda tn:tn.startswith('inspectapp_'),
stdout=out)
output = out.getvalue()
self.assertIn('geom = models.PolygonField()', output)
self.assertIn('point = models.PointField()', output)
self.assertIn('objects = models.GeoManager()', output)
@skipUnless(HAS_GDAL and HAS_SPATIAL_DB, "GDAL and spatial db are required.") @skipUnless(HAS_GDAL and HAS_SPATIAL_DB, "GDAL and spatial db are required.")
class OGRInspectTest(TestCase): class OGRInspectTest(TestCase):
maxDiff = 1024 maxDiff = 1024

View File

@ -14,10 +14,9 @@ from django.template import Template, Context, defaultfilters
from django.test import TestCase from django.test import TestCase
from django.test.utils import override_settings from django.test.utils import override_settings
from django.utils.html import escape from django.utils.html import escape
from django.utils.timezone import utc from django.utils.timezone import utc, get_fixed_timezone
from django.utils import translation from django.utils import translation
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.utils import tzinfo
from i18n import TransRealMixin from i18n import TransRealMixin
@ -153,8 +152,8 @@ class HumanizeTests(TransRealMixin, TestCase):
def test_naturalday_tz(self): def test_naturalday_tz(self):
today = datetime.date.today() today = datetime.date.today()
tz_one = tzinfo.FixedOffset(datetime.timedelta(hours=-12)) tz_one = get_fixed_timezone(-720)
tz_two = tzinfo.FixedOffset(datetime.timedelta(hours=12)) tz_two = get_fixed_timezone(720)
# Can be today or yesterday # Can be today or yesterday
date_one = datetime.datetime(today.year, today.month, today.day, tzinfo=tz_one) date_one = datetime.datetime(today.year, today.month, today.day, tzinfo=tz_one)

View File

@ -105,7 +105,7 @@ class BaseStorage(object):
just containing no messages) then ``None`` should be returned in just containing no messages) then ``None`` should be returned in
place of ``messages``. place of ``messages``.
""" """
raise NotImplementedError() raise NotImplementedError('subclasses of BaseStorage must provide a _get() method')
def _store(self, messages, response, *args, **kwargs): def _store(self, messages, response, *args, **kwargs):
""" """
@ -116,7 +116,7 @@ class BaseStorage(object):
**This method must be implemented by a subclass.** **This method must be implemented by a subclass.**
""" """
raise NotImplementedError() raise NotImplementedError('subclasses of BaseStorage must provide a _store() method')
def _prepare_messages(self, messages): def _prepare_messages(self, messages):
""" """

View File

@ -284,7 +284,7 @@ class SessionBase(object):
""" """
Returns True if the given session_key already exists. Returns True if the given session_key already exists.
""" """
raise NotImplementedError raise NotImplementedError('subclasses of SessionBase must provide an exists() method')
def create(self): def create(self):
""" """
@ -292,7 +292,7 @@ class SessionBase(object):
a unique key and will have saved the result once (with empty data) a unique key and will have saved the result once (with empty data)
before the method returns. before the method returns.
""" """
raise NotImplementedError raise NotImplementedError('subclasses of SessionBase must provide a create() method')
def save(self, must_create=False): def save(self, must_create=False):
""" """
@ -300,20 +300,20 @@ class SessionBase(object):
is created (otherwise a CreateError exception is raised). Otherwise, is created (otherwise a CreateError exception is raised). Otherwise,
save() can update an existing object with the same key. save() can update an existing object with the same key.
""" """
raise NotImplementedError raise NotImplementedError('subclasses of SessionBase must provide a save() method')
def delete(self, session_key=None): def delete(self, session_key=None):
""" """
Deletes the session data under this key. If the key is None, the Deletes the session data under this key. If the key is None, the
current session key value is used. current session key value is used.
""" """
raise NotImplementedError raise NotImplementedError('subclasses of SessionBase must provide a delete() method')
def load(self): def load(self):
""" """
Loads the session data and returns a dictionary. Loads the session data and returns a dictionary.
""" """
raise NotImplementedError raise NotImplementedError('subclasses of SessionBase must provide a load() method')
@classmethod @classmethod
def clear_expired(cls): def clear_expired(cls):
@ -324,4 +324,4 @@ class SessionBase(object):
NotImplementedError. If it isn't necessary, because the backend has NotImplementedError. If it isn't necessary, because the backend has
a built-in expiration mechanism, it should be a no-op. a built-in expiration mechanism, it should be a no-op.
""" """
raise NotImplementedError raise NotImplementedError('This backend does not support clear_expired().')

View File

@ -28,7 +28,7 @@ class BaseFinder(object):
the first found file path will be returned; if set the first found file path will be returned; if set
to ``True`` a list of all found files paths is returned. to ``True`` a list of all found files paths is returned.
""" """
raise NotImplementedError() raise NotImplementedError('subclasses of BaseFinder must provide a find() method')
def list(self, ignore_patterns): def list(self, ignore_patterns):
""" """
@ -36,7 +36,7 @@ class BaseFinder(object):
a two item iterable consisting of the relative path and storage a two item iterable consisting of the relative path and storage
instance. instance.
""" """
raise NotImplementedError() raise NotImplementedError('subclasses of BaseFinder must provide a list() method')
class FileSystemFinder(BaseFinder): class FileSystemFinder(BaseFinder):

View File

@ -7,12 +7,12 @@ from django.contrib.sites.models import get_current_site
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
from django.http import HttpResponse, Http404 from django.http import HttpResponse, Http404
from django.template import loader, TemplateDoesNotExist, RequestContext from django.template import loader, TemplateDoesNotExist, RequestContext
from django.utils import feedgenerator, tzinfo from django.utils import feedgenerator
from django.utils.encoding import force_text, iri_to_uri, smart_text from django.utils.encoding import force_text, iri_to_uri, smart_text
from django.utils.html import escape from django.utils.html import escape
from django.utils.http import http_date from django.utils.http import http_date
from django.utils import six from django.utils import six
from django.utils.timezone import is_naive from django.utils.timezone import get_default_timezone, is_naive, make_aware
def add_domain(domain, url, secure=False): def add_domain(domain, url, secure=False):
@ -186,15 +186,15 @@ class Feed(object):
else: else:
author_email = author_link = None author_email = author_link = None
tz = get_default_timezone()
pubdate = self.__get_dynamic_attr('item_pubdate', item) pubdate = self.__get_dynamic_attr('item_pubdate', item)
if pubdate and is_naive(pubdate): if pubdate and is_naive(pubdate):
ltz = tzinfo.LocalTimezone(pubdate) pubdate = make_aware(pubdate, tz)
pubdate = pubdate.replace(tzinfo=ltz)
updateddate = self.__get_dynamic_attr('item_updateddate', item) updateddate = self.__get_dynamic_attr('item_updateddate', item)
if updateddate and is_naive(updateddate): if updateddate and is_naive(updateddate):
ltz = tzinfo.LocalTimezone(updateddate) updateddate = make_aware(updateddate, tz)
updateddate = updateddate.replace(tzinfo=ltz)
feed.add_item( feed.add_item(
title = title, title = title,

View File

@ -96,27 +96,27 @@ class BaseCache(object):
Returns True if the value was stored, False otherwise. Returns True if the value was stored, False otherwise.
""" """
raise NotImplementedError raise NotImplementedError('subclasses of BaseCache must provide an add() method')
def get(self, key, default=None, version=None): def get(self, key, default=None, version=None):
""" """
Fetch a given key from the cache. If the key does not exist, return Fetch a given key from the cache. If the key does not exist, return
default, which itself defaults to None. default, which itself defaults to None.
""" """
raise NotImplementedError raise NotImplementedError('subclasses of BaseCache must provide a get() method')
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None): def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
""" """
Set a value in the cache. If timeout is given, that timeout will be Set a value in the cache. If timeout is given, that timeout will be
used for the key; otherwise the default cache timeout will be used. used for the key; otherwise the default cache timeout will be used.
""" """
raise NotImplementedError raise NotImplementedError('subclasses of BaseCache must provide a set() method')
def delete(self, key, version=None): def delete(self, key, version=None):
""" """
Delete a key from the cache, failing silently. Delete a key from the cache, failing silently.
""" """
raise NotImplementedError raise NotImplementedError('subclasses of BaseCache must provide a delete() method')
def get_many(self, keys, version=None): def get_many(self, keys, version=None):
""" """
@ -190,7 +190,7 @@ class BaseCache(object):
def clear(self): def clear(self):
"""Remove *all* values from the cache at once.""" """Remove *all* values from the cache at once."""
raise NotImplementedError raise NotImplementedError('subclasses of BaseCache must provide a clear() method')
def validate_key(self, key): def validate_key(self, key):
""" """

View File

@ -92,55 +92,55 @@ class Storage(object):
""" """
Deletes the specified file from the storage system. Deletes the specified file from the storage system.
""" """
raise NotImplementedError() raise NotImplementedError('subclasses of Storage must provide a delete() method')
def exists(self, name): def exists(self, name):
""" """
Returns True if a file referened by the given name already exists in the Returns True if a file referened by the given name already exists in the
storage system, or False if the name is available for a new file. storage system, or False if the name is available for a new file.
""" """
raise NotImplementedError() raise NotImplementedError('subclasses of Storage must provide a exists() method')
def listdir(self, path): def listdir(self, path):
""" """
Lists the contents of the specified path, returning a 2-tuple of lists; Lists the contents of the specified path, returning a 2-tuple of lists;
the first item being directories, the second item being files. the first item being directories, the second item being files.
""" """
raise NotImplementedError() raise NotImplementedError('subclasses of Storage must provide a listdir() method')
def size(self, name): def size(self, name):
""" """
Returns the total size, in bytes, of the file specified by name. Returns the total size, in bytes, of the file specified by name.
""" """
raise NotImplementedError() raise NotImplementedError('subclasses of Storage must provide a size() method')
def url(self, name): def url(self, name):
""" """
Returns an absolute URL where the file's contents can be accessed Returns an absolute URL where the file's contents can be accessed
directly by a Web browser. directly by a Web browser.
""" """
raise NotImplementedError() raise NotImplementedError('subclasses of Storage must provide a url() method')
def accessed_time(self, name): def accessed_time(self, name):
""" """
Returns the last accessed time (as datetime object) of the file Returns the last accessed time (as datetime object) of the file
specified by name. specified by name.
""" """
raise NotImplementedError() raise NotImplementedError('subclasses of Storage must provide an accessed_time() method')
def created_time(self, name): def created_time(self, name):
""" """
Returns the creation time (as datetime object) of the file Returns the creation time (as datetime object) of the file
specified by name. specified by name.
""" """
raise NotImplementedError() raise NotImplementedError('subclasses of Storage must provide a created_time() method')
def modified_time(self, name): def modified_time(self, name):
""" """
Returns the last modified time (as datetime object) of the file Returns the last modified time (as datetime object) of the file
specified by name. specified by name.
""" """
raise NotImplementedError() raise NotImplementedError('subclasses of Storage must provide a modified_time() method')
class FileSystemStorage(Storage): class FileSystemStorage(Storage):
""" """
@ -215,6 +215,7 @@ class FileSystemStorage(Storage):
_file = os.fdopen(fd, mode) _file = os.fdopen(fd, mode)
_file.write(chunk) _file.write(chunk)
finally: finally:
content.close()
locks.unlock(fd) locks.unlock(fd)
if _file is not None: if _file is not None:
_file.close() _file.close()

View File

@ -46,6 +46,7 @@ class UploadedFile(File):
# File names longer than 255 characters can cause problems on older OSes. # File names longer than 255 characters can cause problems on older OSes.
if len(name) > 255: if len(name) > 255:
name, ext = os.path.splitext(name) name, ext = os.path.splitext(name)
ext = ext[:255]
name = name[:255 - len(ext)] + ext name = name[:255 - len(ext)] + ext
self._name = name self._name = name

View File

@ -104,7 +104,7 @@ class FileUploadHandler(object):
Receive data from the streamed upload parser. ``start`` is the position Receive data from the streamed upload parser. ``start`` is the position
in the file of the chunk. in the file of the chunk.
""" """
raise NotImplementedError() raise NotImplementedError('subclasses of FileUploadHandler must provide a recieve_data_chunk() method')
def file_complete(self, file_size): def file_complete(self, file_size):
""" """
@ -113,7 +113,7 @@ class FileUploadHandler(object):
Subclasses should return a valid ``UploadedFile`` object. Subclasses should return a valid ``UploadedFile`` object.
""" """
raise NotImplementedError() raise NotImplementedError('subclasses of FileUploadHandler must provide a file_complete() method')
def upload_complete(self): def upload_complete(self):
""" """

View File

@ -36,4 +36,4 @@ class BaseEmailBackend(object):
Sends one or more EmailMessage objects and returns the number of email Sends one or more EmailMessage objects and returns the number of email
messages sent. messages sent.
""" """
raise NotImplementedError raise NotImplementedError('subclasses of BaseEmailBackend must override send_messages() method')

View File

@ -325,7 +325,7 @@ class BaseCommand(object):
this method. this method.
""" """
raise NotImplementedError() raise NotImplementedError('subclasses of BaseCommand must provide a handle() method')
class AppCommand(BaseCommand): class AppCommand(BaseCommand):
@ -361,7 +361,7 @@ class AppCommand(BaseCommand):
the command line. the command line.
""" """
raise NotImplementedError() raise NotImplementedError('subclasses of AppCommand must provide a handle_app() method')
class LabelCommand(BaseCommand): class LabelCommand(BaseCommand):
@ -397,7 +397,7 @@ class LabelCommand(BaseCommand):
string as given on the command line. string as given on the command line.
""" """
raise NotImplementedError() raise NotImplementedError('subclasses of LabelCommand must provide a handle_label() method')
class NoArgsCommand(BaseCommand): class NoArgsCommand(BaseCommand):
@ -423,4 +423,4 @@ class NoArgsCommand(BaseCommand):
Perform this command's actions. Perform this command's actions.
""" """
raise NotImplementedError() raise NotImplementedError('subclasses of NoArgsCommand must provide a handle_noargs() method')

View File

@ -104,8 +104,11 @@ class Command(NoArgsCommand):
# Don't output 'id = meta.AutoField(primary_key=True)', because # Don't output 'id = meta.AutoField(primary_key=True)', because
# that's assumed if it doesn't exist. # that's assumed if it doesn't exist.
if att_name == 'id' and field_type == 'AutoField(' and extra_params == {'primary_key': True}: if att_name == 'id' and extra_params == {'primary_key': True}:
continue if field_type == 'AutoField(':
continue
elif field_type == 'IntegerField(' and not connection.features.can_introspect_autofield:
comment_notes.append('AutoField?')
# Add 'null' and 'blank', if the 'null_ok' flag was present in the # Add 'null' and 'blank', if the 'null_ok' flag was present in the
# table description. # table description.
@ -117,7 +120,12 @@ class Command(NoArgsCommand):
if not field_type in ('TextField(', 'CharField('): if not field_type in ('TextField(', 'CharField('):
extra_params['null'] = True extra_params['null'] = True
field_desc = '%s = models.%s' % (att_name, field_type) field_desc = '%s = %s%s' % (
att_name,
# Custom fields will have a dotted path
'' if '.' in field_type else 'models.',
field_type,
)
if extra_params: if extra_params:
if not field_desc.endswith('('): if not field_desc.endswith('('):
field_desc += ', ' field_desc += ', '

View File

@ -65,7 +65,7 @@ class Serializer(object):
""" """
Called when serializing of the queryset starts. Called when serializing of the queryset starts.
""" """
raise NotImplementedError raise NotImplementedError('subclasses of Serializer must provide a start_serialization() method')
def end_serialization(self): def end_serialization(self):
""" """
@ -77,7 +77,7 @@ class Serializer(object):
""" """
Called when serializing of an object starts. Called when serializing of an object starts.
""" """
raise NotImplementedError raise NotImplementedError('subclasses of Serializer must provide a start_object() method')
def end_object(self, obj): def end_object(self, obj):
""" """
@ -89,19 +89,19 @@ class Serializer(object):
""" """
Called to handle each individual (non-relational) field on an object. Called to handle each individual (non-relational) field on an object.
""" """
raise NotImplementedError raise NotImplementedError('subclasses of Serializer must provide an handle_field() method')
def handle_fk_field(self, obj, field): def handle_fk_field(self, obj, field):
""" """
Called to handle a ForeignKey field. Called to handle a ForeignKey field.
""" """
raise NotImplementedError raise NotImplementedError('subclasses of Serializer must provide an handle_fk_field() method')
def handle_m2m_field(self, obj, field): def handle_m2m_field(self, obj, field):
""" """
Called to handle a ManyToManyField. Called to handle a ManyToManyField.
""" """
raise NotImplementedError raise NotImplementedError('subclasses of Serializer must provide an handle_m2m_field() method')
def getvalue(self): def getvalue(self):
""" """
@ -135,7 +135,7 @@ class Deserializer(six.Iterator):
def __next__(self): def __next__(self):
"""Iteration iterface -- return the next item in the stream""" """Iteration iterface -- return the next item in the stream"""
raise NotImplementedError raise NotImplementedError('subclasses of Deserializer must provide a __next__() method')
class DeserializedObject(object): class DeserializedObject(object):
""" """

View File

@ -84,19 +84,19 @@ class BaseDatabaseWrapper(object):
def get_connection_params(self): def get_connection_params(self):
"""Returns a dict of parameters suitable for get_new_connection.""" """Returns a dict of parameters suitable for get_new_connection."""
raise NotImplementedError raise NotImplementedError('subclasses of BaseDatabaseWrapper may require a get_connection_params() method')
def get_new_connection(self, conn_params): def get_new_connection(self, conn_params):
"""Opens a connection to the database.""" """Opens a connection to the database."""
raise NotImplementedError raise NotImplementedError('subclasses of BaseDatabaseWrapper may require a get_new_connection() method')
def init_connection_state(self): def init_connection_state(self):
"""Initializes the database connection settings.""" """Initializes the database connection settings."""
raise NotImplementedError raise NotImplementedError('subclasses of BaseDatabaseWrapper may require an init_connection_state() method')
def create_cursor(self): def create_cursor(self):
"""Creates a cursor. Assumes that a connection is established.""" """Creates a cursor. Assumes that a connection is established."""
raise NotImplementedError raise NotImplementedError('subclasses of BaseDatabaseWrapper may require a create_cursor() method')
##### Backend-specific methods for creating connections ##### ##### Backend-specific methods for creating connections #####
@ -262,7 +262,7 @@ class BaseDatabaseWrapper(object):
""" """
Backend-specific implementation to enable or disable autocommit. Backend-specific implementation to enable or disable autocommit.
""" """
raise NotImplementedError raise NotImplementedError('subclasses of BaseDatabaseWrapper may require a _set_autocommit() method')
##### Generic transaction management methods ##### ##### Generic transaction management methods #####
@ -440,7 +440,7 @@ class BaseDatabaseWrapper(object):
Tests if the database connection is usable. Tests if the database connection is usable.
This function may assume that self.connection is not None. This function may assume that self.connection is not None.
""" """
raise NotImplementedError raise NotImplementedError('subclasses of BaseDatabaseWrapper may require an is_usable() method')
def close_if_unusable_or_obsolete(self): def close_if_unusable_or_obsolete(self):
""" """
@ -519,11 +519,11 @@ class BaseDatabaseWrapper(object):
""" """
Only required when autocommits_when_autocommit_is_off = True. Only required when autocommits_when_autocommit_is_off = True.
""" """
raise NotImplementedError raise NotImplementedError('subclasses of BaseDatabaseWrapper may require a _start_transaction_under_autocommit() method')
def schema_editor(self, *args, **kwargs): def schema_editor(self, *args, **kwargs):
"Returns a new instance of this backend's SchemaEditor" "Returns a new instance of this backend's SchemaEditor"
raise NotImplementedError() raise NotImplementedError('subclasses of BaseDatabaseWrapper may require a schema_editor() method')
class BaseDatabaseFeatures(object): class BaseDatabaseFeatures(object):
@ -627,6 +627,9 @@ class BaseDatabaseFeatures(object):
# which can't do it for MyISAM tables # which can't do it for MyISAM tables
can_introspect_foreign_keys = True can_introspect_foreign_keys = True
# Can the backend introspect an AutoField, instead of an IntegerField?
can_introspect_autofield = False
# Support for the DISTINCT ON clause # Support for the DISTINCT ON clause
can_distinct_on_fields = False can_distinct_on_fields = False
@ -741,13 +744,13 @@ class BaseDatabaseOperations(object):
Given a lookup_type of 'year', 'month' or 'day', returns the SQL that Given a lookup_type of 'year', 'month' or 'day', returns the SQL that
extracts a value from the given date field field_name. extracts a value from the given date field field_name.
""" """
raise NotImplementedError() raise NotImplementedError('subclasses of BaseDatabaseOperations may require a date_extract_sql() method')
def date_interval_sql(self, sql, connector, timedelta): def date_interval_sql(self, sql, connector, timedelta):
""" """
Implements the date interval functionality for expressions Implements the date interval functionality for expressions
""" """
raise NotImplementedError() raise NotImplementedError('subclasses of BaseDatabaseOperations may require a date_interval_sql() method')
def date_trunc_sql(self, lookup_type, field_name): def date_trunc_sql(self, lookup_type, field_name):
""" """
@ -755,7 +758,7 @@ class BaseDatabaseOperations(object):
truncates the given date field field_name to a date object with only truncates the given date field field_name to a date object with only
the given specificity. the given specificity.
""" """
raise NotImplementedError() raise NotImplementedError('subclasses of BaseDatabaseOperations may require a datetrunc_sql() method')
def datetime_cast_sql(self): def datetime_cast_sql(self):
""" """
@ -772,7 +775,7 @@ class BaseDatabaseOperations(object):
'second', returns the SQL that extracts a value from the given 'second', returns the SQL that extracts a value from the given
datetime field field_name, and a tuple of parameters. datetime field field_name, and a tuple of parameters.
""" """
raise NotImplementedError() raise NotImplementedError('subclasses of BaseDatabaseOperations may require a datetime_extract_sql() method')
def datetime_trunc_sql(self, lookup_type, field_name, tzname): def datetime_trunc_sql(self, lookup_type, field_name, tzname):
""" """
@ -781,7 +784,7 @@ class BaseDatabaseOperations(object):
field_name to a datetime object with only the given specificity, and field_name to a datetime object with only the given specificity, and
a tuple of parameters. a tuple of parameters.
""" """
raise NotImplementedError() raise NotImplementedError('subclasses of BaseDatabaseOperations may require a datetime_trunk_sql() method')
def deferrable_sql(self): def deferrable_sql(self):
""" """
@ -916,7 +919,7 @@ class BaseDatabaseOperations(object):
Returns the value to use for the LIMIT when we are wanting "LIMIT Returns the value to use for the LIMIT when we are wanting "LIMIT
infinity". Returns None if the limit clause can be omitted in this case. infinity". Returns None if the limit clause can be omitted in this case.
""" """
raise NotImplementedError raise NotImplementedError('subclasses of BaseDatabaseOperations may require a no_limit_value() method')
def pk_default_value(self): def pk_default_value(self):
""" """
@ -956,7 +959,7 @@ class BaseDatabaseOperations(object):
Returns a quoted version of the given table, index or column name. Does Returns a quoted version of the given table, index or column name. Does
not quote the given name if it's already been quoted. not quote the given name if it's already been quoted.
""" """
raise NotImplementedError() raise NotImplementedError('subclasses of BaseDatabaseOperations may require a quote_name() method')
def quote_parameter(self, value): def quote_parameter(self, value):
""" """
@ -982,7 +985,7 @@ class BaseDatabaseOperations(object):
If the feature is not supported (or part of it is not supported), a If the feature is not supported (or part of it is not supported), a
NotImplementedError exception can be raised. NotImplementedError exception can be raised.
""" """
raise NotImplementedError raise NotImplementedError('subclasses of BaseDatabaseOperations may require a regex_lookup() method')
def savepoint_create_sql(self, sid): def savepoint_create_sql(self, sid):
""" """
@ -1028,7 +1031,7 @@ class BaseDatabaseOperations(object):
to tables with foreign keys pointing the tables being truncated. to tables with foreign keys pointing the tables being truncated.
PostgreSQL requires a cascade even if these tables are empty. PostgreSQL requires a cascade even if these tables are empty.
""" """
raise NotImplementedError() raise NotImplementedError('subclasses of BaseDatabaseOperations must provide a sql_flush() method')
def sequence_reset_by_name_sql(self, style, sequences): def sequence_reset_by_name_sql(self, style, sequences):
""" """
@ -1245,7 +1248,7 @@ class BaseDatabaseIntrospection(object):
Returns an unsorted list of names of all tables that exist in the Returns an unsorted list of names of all tables that exist in the
database. database.
""" """
raise NotImplementedError raise NotImplementedError('subclasses of BaseDatabaseIntrospection may require a get_table_list() method')
def django_table_names(self, only_existing=False): def django_table_names(self, only_existing=False):
""" """
@ -1322,7 +1325,7 @@ class BaseDatabaseIntrospection(object):
Backends can override this to return a list of (column_name, referenced_table_name, Backends can override this to return a list of (column_name, referenced_table_name,
referenced_column_name) for all key columns in given table. referenced_column_name) for all key columns in given table.
""" """
raise NotImplementedError raise NotImplementedError('subclasses of BaseDatabaseIntrospection may require a get_key_columns() method')
def get_primary_key_column(self, cursor, table_name): def get_primary_key_column(self, cursor, table_name):
""" """
@ -1342,7 +1345,7 @@ class BaseDatabaseIntrospection(object):
Only single-column indexes are introspected. Only single-column indexes are introspected.
""" """
raise NotImplementedError raise NotImplementedError('subclasses of BaseDatabaseIntrospection may require a get_indexes() method')
def get_constraints(self, cursor, table_name): def get_constraints(self, cursor, table_name):
""" """
@ -1361,7 +1364,7 @@ class BaseDatabaseIntrospection(object):
Some backends may return special constraint names that don't exist Some backends may return special constraint names that don't exist
if they don't name constraints of a certain type (e.g. SQLite) if they don't name constraints of a certain type (e.g. SQLite)
""" """
raise NotImplementedError raise NotImplementedError('subclasses of BaseDatabaseIntrospection may require a get_constraints() method')
class BaseDatabaseClient(object): class BaseDatabaseClient(object):
@ -1378,7 +1381,7 @@ class BaseDatabaseClient(object):
self.connection = connection self.connection = connection
def runshell(self): def runshell(self):
raise NotImplementedError() raise NotImplementedError('subclasses of BaseDatabaseClient must provide a runshell() method')
class BaseDatabaseValidation(object): class BaseDatabaseValidation(object):

View File

@ -181,6 +181,7 @@ class DatabaseCreation(BaseDatabaseCreation):
IDENTIFIED BY %(password)s IDENTIFIED BY %(password)s
DEFAULT TABLESPACE %(tblspace)s DEFAULT TABLESPACE %(tblspace)s
TEMPORARY TABLESPACE %(tblspace_temp)s TEMPORARY TABLESPACE %(tblspace_temp)s
QUOTA UNLIMITED ON %(tblspace)s
""", """,
"""GRANT CONNECT, RESOURCE TO %(user)s""", """GRANT CONNECT, RESOURCE TO %(user)s""",
] ]

View File

@ -148,7 +148,7 @@ class BaseDatabaseSchemaEditor(object):
""" """
Only used for backends which have requires_literal_defaults feature Only used for backends which have requires_literal_defaults feature
""" """
raise NotImplementedError() raise NotImplementedError('subclasses of BaseDatabaseSchemaEditor for backends which have requires_literal_defaults must provide a prepare_default() method')
def effective_default(self, field): def effective_default(self, field):
""" """

View File

@ -38,14 +38,14 @@ class Operation(object):
Takes the state from the previous migration, and mutates it Takes the state from the previous migration, and mutates it
so that it matches what this migration would perform. so that it matches what this migration would perform.
""" """
raise NotImplementedError() raise NotImplementedError('subclasses of Operation must provide a state_forwards() method')
def database_forwards(self, app_label, schema_editor, from_state, to_state): def database_forwards(self, app_label, schema_editor, from_state, to_state):
""" """
Performs the mutation on the database schema in the normal Performs the mutation on the database schema in the normal
(forwards) direction. (forwards) direction.
""" """
raise NotImplementedError() raise NotImplementedError('subclasses of Operation must provide a database_forwards() method')
def database_backwards(self, app_label, schema_editor, from_state, to_state): def database_backwards(self, app_label, schema_editor, from_state, to_state):
""" """
@ -53,7 +53,7 @@ class Operation(object):
direction - e.g. if this were CreateModel, it would in fact direction - e.g. if this were CreateModel, it would in fact
drop the model's table. drop the model's table.
""" """
raise NotImplementedError() raise NotImplementedError('subclasses of Operation must provide a database_backwards() method')
def describe(self): def describe(self):
""" """

View File

@ -73,6 +73,26 @@ class MigrationWriter(object):
raise ImportError("Cannot open migrations module %s for app %s" % (migrations_module_name, self.migration.app_label)) raise ImportError("Cannot open migrations module %s for app %s" % (migrations_module_name, self.migration.app_label))
return os.path.join(basedir, self.filename) return os.path.join(basedir, self.filename)
@classmethod
def serialize_deconstructed(cls, path, args, kwargs):
module, name = path.rsplit(".", 1)
if module == "django.db.models":
imports = set(["from django.db import models"])
name = "models.%s" % name
else:
imports = set(["import %s" % module])
name = path
arg_strings = []
for arg in args:
arg_string, arg_imports = cls.serialize(arg)
arg_strings.append(arg_string)
imports.update(arg_imports)
for kw, arg in kwargs.items():
arg_string, arg_imports = cls.serialize(arg)
imports.update(arg_imports)
arg_strings.append("%s=%s" % (kw, arg_string))
return "%s(%s)" % (name, ", ".join(arg_strings)), imports
@classmethod @classmethod
def serialize(cls, value): def serialize(cls, value):
""" """
@ -119,23 +139,7 @@ class MigrationWriter(object):
# Django fields # Django fields
elif isinstance(value, models.Field): elif isinstance(value, models.Field):
attr_name, path, args, kwargs = value.deconstruct() attr_name, path, args, kwargs = value.deconstruct()
module, name = path.rsplit(".", 1) return cls.serialize_deconstructed(path, args, kwargs)
if module == "django.db.models":
imports = set(["from django.db import models"])
name = "models.%s" % name
else:
imports = set(["import %s" % module])
name = path
arg_strings = []
for arg in args:
arg_string, arg_imports = cls.serialize(arg)
arg_strings.append(arg_string)
imports.update(arg_imports)
for kw, arg in kwargs.items():
arg_string, arg_imports = cls.serialize(arg)
imports.update(arg_imports)
arg_strings.append("%s=%s" % (kw, arg_string))
return "%s(%s)" % (name, ", ".join(arg_strings)), imports
# Functions # Functions
elif isinstance(value, (types.FunctionType, types.BuiltinFunctionType)): elif isinstance(value, (types.FunctionType, types.BuiltinFunctionType)):
# Special-cases, as these don't have im_class # Special-cases, as these don't have im_class
@ -152,6 +156,8 @@ class MigrationWriter(object):
klass = value.im_class klass = value.im_class
module = klass.__module__ module = klass.__module__
return "%s.%s.%s" % (module, klass.__name__, value.__name__), set(["import %s" % module]) return "%s.%s.%s" % (module, klass.__name__, value.__name__), set(["import %s" % module])
elif hasattr(value, 'deconstruct'):
return cls.serialize_deconstructed(*value.deconstruct())
elif value.__name__ == '<lambda>': elif value.__name__ == '<lambda>':
raise ValueError("Cannot serialize function: lambda") raise ValueError("Cannot serialize function: lambda")
elif value.__module__ is None: elif value.__module__ is None:

View File

@ -35,10 +35,12 @@ def SET(value):
else: else:
def set_on_delete(collector, field, sub_objs, using): def set_on_delete(collector, field, sub_objs, using):
collector.add_field_update(field, value, sub_objs) collector.add_field_update(field, value, sub_objs)
set_on_delete.deconstruct = lambda: ('django.db.models.SET', (value,), {})
return set_on_delete return set_on_delete
SET_NULL = SET(None) def SET_NULL(collector, field, sub_objs, using):
collector.add_field_update(field, None, sub_objs)
def SET_DEFAULT(collector, field, sub_objs, using): def SET_DEFAULT(collector, field, sub_objs, using):

View File

@ -391,7 +391,7 @@ class SQLCompiler(object):
if not distinct or elt in select_aliases: if not distinct or elt in select_aliases:
result.append('%s %s' % (elt, order)) result.append('%s %s' % (elt, order))
group_by.append((elt, [])) group_by.append((elt, []))
elif get_order_dir(field)[0] not in self.query.extra: elif not self.query._extra or get_order_dir(field)[0] not in self.query._extra:
# 'col' is of the form 'field' or 'field1__field2' or # 'col' is of the form 'field' or 'field1__field2' or
# '-field1__field2__field', etc. # '-field1__field2__field', etc.
for table, cols, order in self.find_ordering_name(field, for table, cols, order in self.find_ordering_name(field,
@ -987,7 +987,7 @@ class SQLUpdateCompiler(SQLCompiler):
# We need to use a sub-select in the where clause to filter on things # We need to use a sub-select in the where clause to filter on things
# from other tables. # from other tables.
query = self.query.clone(klass=Query) query = self.query.clone(klass=Query)
query.extra = {} query._extra = {}
query.select = [] query.select = []
query.add_fields([query.get_meta().pk.name]) query.add_fields([query.get_meta().pk.name])
# Recheck the count - it is possible that fiddling with the select # Recheck the count - it is possible that fiddling with the select

View File

@ -143,7 +143,10 @@ class Query(object):
self.select_related = False self.select_related = False
# SQL aggregate-related attributes # SQL aggregate-related attributes
self.aggregates = OrderedDict() # Maps alias -> SQL aggregate function # The _aggregates will be an OrderedDict when used. Due to the cost
# of creating OrderedDict this attribute is created lazily (in
# self.aggregates property).
self._aggregates = None # Maps alias -> SQL aggregate function
self.aggregate_select_mask = None self.aggregate_select_mask = None
self._aggregate_select_cache = None self._aggregate_select_cache = None
@ -153,7 +156,9 @@ class Query(object):
# These are for extensions. The contents are more or less appended # These are for extensions. The contents are more or less appended
# verbatim to the appropriate clause. # verbatim to the appropriate clause.
self.extra = OrderedDict() # Maps col_alias -> (col_sql, params). # The _extra attribute is an OrderedDict, lazily created similarly to
# .aggregates
self._extra = None # Maps col_alias -> (col_sql, params).
self.extra_select_mask = None self.extra_select_mask = None
self._extra_select_cache = None self._extra_select_cache = None
@ -165,6 +170,18 @@ class Query(object):
# load. # load.
self.deferred_loading = (set(), True) self.deferred_loading = (set(), True)
@property
def extra(self):
if self._extra is None:
self._extra = OrderedDict()
return self._extra
@property
def aggregates(self):
if self._aggregates is None:
self._aggregates = OrderedDict()
return self._aggregates
def __str__(self): def __str__(self):
""" """
Returns the query as a string of SQL with the parameter values Returns the query as a string of SQL with the parameter values
@ -245,7 +262,7 @@ class Query(object):
obj.select_for_update_nowait = self.select_for_update_nowait obj.select_for_update_nowait = self.select_for_update_nowait
obj.select_related = self.select_related obj.select_related = self.select_related
obj.related_select_cols = [] obj.related_select_cols = []
obj.aggregates = self.aggregates.copy() obj._aggregates = self._aggregates.copy() if self._aggregates is not None else None
if self.aggregate_select_mask is None: if self.aggregate_select_mask is None:
obj.aggregate_select_mask = None obj.aggregate_select_mask = None
else: else:
@ -257,7 +274,7 @@ class Query(object):
# used. # used.
obj._aggregate_select_cache = None obj._aggregate_select_cache = None
obj.max_depth = self.max_depth obj.max_depth = self.max_depth
obj.extra = self.extra.copy() obj._extra = self._extra.copy() if self._extra is not None else None
if self.extra_select_mask is None: if self.extra_select_mask is None:
obj.extra_select_mask = None obj.extra_select_mask = None
else: else:
@ -344,7 +361,7 @@ class Query(object):
# and move them to the outer AggregateQuery. # and move them to the outer AggregateQuery.
for alias, aggregate in self.aggregate_select.items(): for alias, aggregate in self.aggregate_select.items():
if aggregate.is_summary: if aggregate.is_summary:
query.aggregate_select[alias] = aggregate.relabeled_clone(relabels) query.aggregates[alias] = aggregate.relabeled_clone(relabels)
del obj.aggregate_select[alias] del obj.aggregate_select[alias]
try: try:
@ -358,7 +375,7 @@ class Query(object):
query = self query = self
self.select = [] self.select = []
self.default_cols = False self.default_cols = False
self.extra = {} self._extra = {}
self.remove_inherited_models() self.remove_inherited_models()
query.clear_ordering(True) query.clear_ordering(True)
@ -527,7 +544,7 @@ class Query(object):
# It would be nice to be able to handle this, but the queries don't # It would be nice to be able to handle this, but the queries don't
# really make sense (or return consistent value sets). Not worth # really make sense (or return consistent value sets). Not worth
# the extra complexity when you can write a real query instead. # the extra complexity when you can write a real query instead.
if self.extra and rhs.extra: if self._extra and rhs._extra:
raise ValueError("When merging querysets using 'or', you " raise ValueError("When merging querysets using 'or', you "
"cannot have extra(select=...) on both sides.") "cannot have extra(select=...) on both sides.")
self.extra.update(rhs.extra) self.extra.update(rhs.extra)
@ -756,8 +773,9 @@ class Query(object):
self.group_by = [relabel_column(col) for col in self.group_by] self.group_by = [relabel_column(col) for col in self.group_by]
self.select = [SelectInfo(relabel_column(s.col), s.field) self.select = [SelectInfo(relabel_column(s.col), s.field)
for s in self.select] for s in self.select]
self.aggregates = OrderedDict( if self._aggregates:
(key, relabel_column(col)) for key, col in self.aggregates.items()) self._aggregates = OrderedDict(
(key, relabel_column(col)) for key, col in self._aggregates.items())
# 2. Rename the alias in the internal table/alias datastructures. # 2. Rename the alias in the internal table/alias datastructures.
for ident, aliases in self.join_map.items(): for ident, aliases in self.join_map.items():
@ -967,7 +985,7 @@ class Query(object):
""" """
opts = model._meta opts = model._meta
field_list = aggregate.lookup.split(LOOKUP_SEP) field_list = aggregate.lookup.split(LOOKUP_SEP)
if len(field_list) == 1 and aggregate.lookup in self.aggregates: if len(field_list) == 1 and self._aggregates and aggregate.lookup in self.aggregates:
# Aggregate is over an annotation # Aggregate is over an annotation
field_name = field_list[0] field_name = field_list[0]
col = field_name col = field_name
@ -1049,7 +1067,7 @@ class Query(object):
lookup_parts = lookup.split(LOOKUP_SEP) lookup_parts = lookup.split(LOOKUP_SEP)
num_parts = len(lookup_parts) num_parts = len(lookup_parts)
if (len(lookup_parts) > 1 and lookup_parts[-1] in self.query_terms if (len(lookup_parts) > 1 and lookup_parts[-1] in self.query_terms
and lookup not in self.aggregates): and (not self._aggregates or lookup not in self._aggregates)):
# Traverse the lookup query to distinguish related fields from # Traverse the lookup query to distinguish related fields from
# lookup types. # lookup types.
lookup_model = self.model lookup_model = self.model
@ -1108,10 +1126,11 @@ class Query(object):
value, lookup_type = self.prepare_lookup_value(value, lookup_type, can_reuse) value, lookup_type = self.prepare_lookup_value(value, lookup_type, can_reuse)
clause = self.where_class() clause = self.where_class()
for alias, aggregate in self.aggregates.items(): if self._aggregates:
if alias in (parts[0], LOOKUP_SEP.join(parts)): for alias, aggregate in self.aggregates.items():
clause.add((aggregate, lookup_type, value), AND) if alias in (parts[0], LOOKUP_SEP.join(parts)):
return clause clause.add((aggregate, lookup_type, value), AND)
return clause
opts = self.get_meta() opts = self.get_meta()
alias = self.get_initial_alias() alias = self.get_initial_alias()
@ -1170,6 +1189,8 @@ class Query(object):
Returns whether or not all elements of this q_object need to be put Returns whether or not all elements of this q_object need to be put
together in the HAVING clause. together in the HAVING clause.
""" """
if not self._aggregates:
return False
if not isinstance(obj, Node): if not isinstance(obj, Node):
return (refs_aggregate(obj[0].split(LOOKUP_SEP), self.aggregates) return (refs_aggregate(obj[0].split(LOOKUP_SEP), self.aggregates)
or (hasattr(obj[1], 'contains_aggregate') or (hasattr(obj[1], 'contains_aggregate')
@ -1632,7 +1653,7 @@ class Query(object):
# Set only aggregate to be the count column. # Set only aggregate to be the count column.
# Clear out the select cache to reflect the new unmasked aggregates. # Clear out the select cache to reflect the new unmasked aggregates.
self.aggregates = {None: count} self._aggregates = {None: count}
self.set_aggregate_mask(None) self.set_aggregate_mask(None)
self.group_by = None self.group_by = None
@ -1781,7 +1802,8 @@ class Query(object):
self.extra_select_mask = set(names) self.extra_select_mask = set(names)
self._extra_select_cache = None self._extra_select_cache = None
def _aggregate_select(self): @property
def aggregate_select(self):
"""The OrderedDict of aggregate columns that are not masked, and should """The OrderedDict of aggregate columns that are not masked, and should
be used in the SELECT clause. be used in the SELECT clause.
@ -1789,6 +1811,8 @@ class Query(object):
""" """
if self._aggregate_select_cache is not None: if self._aggregate_select_cache is not None:
return self._aggregate_select_cache return self._aggregate_select_cache
elif not self._aggregates:
return {}
elif self.aggregate_select_mask is not None: elif self.aggregate_select_mask is not None:
self._aggregate_select_cache = OrderedDict( self._aggregate_select_cache = OrderedDict(
(k, v) for k, v in self.aggregates.items() (k, v) for k, v in self.aggregates.items()
@ -1797,11 +1821,13 @@ class Query(object):
return self._aggregate_select_cache return self._aggregate_select_cache
else: else:
return self.aggregates return self.aggregates
aggregate_select = property(_aggregate_select)
def _extra_select(self): @property
def extra_select(self):
if self._extra_select_cache is not None: if self._extra_select_cache is not None:
return self._extra_select_cache return self._extra_select_cache
if not self._extra:
return {}
elif self.extra_select_mask is not None: elif self.extra_select_mask is not None:
self._extra_select_cache = OrderedDict( self._extra_select_cache = OrderedDict(
(k, v) for k, v in self.extra.items() (k, v) for k, v in self.extra.items()
@ -1810,7 +1836,6 @@ class Query(object):
return self._extra_select_cache return self._extra_select_cache
else: else:
return self.extra return self.extra
extra_select = property(_extra_select)
def trim_start(self, names_with_path): def trim_start(self, names_with_path):
""" """

View File

@ -977,6 +977,11 @@ class MultiValueField(Field):
f.required = False f.required = False
self.fields = fields self.fields = fields
def __deepcopy__(self, memo):
result = super(MultiValueField, self).__deepcopy__(memo)
result.fields = tuple([x.__deepcopy__(memo) for x in self.fields])
return result
def validate(self, value): def validate(self, value):
pass pass

View File

@ -185,7 +185,8 @@ class BaseForm(object):
'label': force_text(label), 'label': force_text(label),
'field': six.text_type(bf), 'field': six.text_type(bf),
'help_text': help_text, 'help_text': help_text,
'html_class_attr': html_class_attr 'html_class_attr': html_class_attr,
'field_name': bf.html_name,
}) })
if top_errors: if top_errors:

View File

@ -23,11 +23,18 @@ def flatatt(attrs):
The result is passed through 'mark_safe'. The result is passed through 'mark_safe'.
""" """
if [v for v in attrs.values() if v is True or v is False]: for attr_name, value in attrs.items():
warnings.warn( if type(value) is bool:
'The meaning of boolean values for widget attributes will change in Django 1.8', warnings.warn(
DeprecationWarning "In Django 1.8, widget attribute %(attr_name)s=%(bool_value)s "
) "will %(action)s. To preserve current behavior, use the "
"string '%(bool_value)s' instead of the boolean value." % {
'attr_name': attr_name,
'action': "be rendered as '%s'" % attr_name if value else "not be rendered",
'bool_value': value,
},
DeprecationWarning
)
return format_html_join('', ' {0}="{1}"', sorted(attrs.items())) return format_html_join('', ' {0}="{1}"', sorted(attrs.items()))
@python_2_unicode_compatible @python_2_unicode_compatible

View File

@ -190,7 +190,7 @@ class Widget(six.with_metaclass(MediaDefiningClass)):
The 'value' given is not guaranteed to be valid input, so subclass The 'value' given is not guaranteed to be valid input, so subclass
implementations should program defensively. implementations should program defensively.
""" """
raise NotImplementedError raise NotImplementedError('subclasses of Widget must provide a render() method')
def build_attrs(self, extra_attrs=None, **kwargs): def build_attrs(self, extra_attrs=None, **kwargs):
"Helper function for building an attribute dictionary." "Helper function for building an attribute dictionary."

View File

@ -64,6 +64,8 @@ else:
M.set(key, real_value, coded_value) M.set(key, real_value, coded_value)
dict.__setitem__(self, key, M) dict.__setitem__(self, key, M)
except http_cookies.CookieError: except http_cookies.CookieError:
if not hasattr(self, 'bad_cookies'):
self.bad_cookies = set()
self.bad_cookies.add(key) self.bad_cookies.add(key)
dict.__setitem__(self, key, http_cookies.Morsel()) dict.__setitem__(self, key, http_cookies.Morsel())

View File

@ -2,6 +2,7 @@ from __future__ import unicode_literals
import datetime import datetime
import time import time
import sys
from email.header import Header from email.header import Header
try: try:
from urllib.parse import urlparse from urllib.parse import urlparse
@ -160,7 +161,7 @@ class HttpResponseBase(six.Iterator):
except UnicodeError as e: except UnicodeError as e:
if mime_encode: if mime_encode:
# Wrapping in str() is a workaround for #12422 under Python 2. # Wrapping in str() is a workaround for #12422 under Python 2.
value = str(Header(value, 'utf-8').encode()) value = str(Header(value, 'utf-8', maxlinelen=sys.maxsize).encode())
else: else:
e.reason += ', HTTP response headers must be in %s format' % charset e.reason += ', HTTP response headers must be in %s format' % charset
raise raise

View File

@ -99,7 +99,7 @@ class Origin(object):
self.name = name self.name = name
def reload(self): def reload(self):
raise NotImplementedError raise NotImplementedError('subclasses of Origin must provide a reload() method')
def __str__(self): def __str__(self):
return self.name return self.name
@ -385,7 +385,7 @@ class TokenParser(object):
""" """
Overload this method to do the actual parsing and return the result. Overload this method to do the actual parsing and return the result.
""" """
raise NotImplementedError() raise NotImplementedError('subclasses of Tokenparser must provide a top() method')
def more(self): def more(self):
""" """
@ -622,34 +622,17 @@ class FilterExpression(object):
def args_check(name, func, provided): def args_check(name, func, provided):
provided = list(provided) provided = list(provided)
plen = len(provided) # First argument, filter input, is implied.
plen = len(provided) + 1
# Check to see if a decorator is providing the real function. # Check to see if a decorator is providing the real function.
func = getattr(func, '_decorated_function', func) func = getattr(func, '_decorated_function', func)
args, varargs, varkw, defaults = getargspec(func) args, varargs, varkw, defaults = getargspec(func)
# First argument is filter input. alen = len(args)
args.pop(0) dlen = len(defaults or [])
if defaults: # Not enough OR Too many
nondefs = args[:-len(defaults)] if plen < (alen - dlen) or plen > alen:
else:
nondefs = args
# Args without defaults must be provided.
try:
for arg in nondefs:
provided.pop(0)
except IndexError:
# Not enough
raise TemplateSyntaxError("%s requires %d arguments, %d provided" % raise TemplateSyntaxError("%s requires %d arguments, %d provided" %
(name, len(nondefs), plen)) (name, alen - dlen, plen))
# Defaults can be overridden.
defaults = list(defaults) if defaults else []
try:
for parg in provided:
defaults.pop(0)
except IndexError:
# Too many.
raise TemplateSyntaxError("%s requires %d arguments, %d provided" %
(name, len(nondefs), plen))
return True return True
args_check = staticmethod(args_check) args_check = staticmethod(args_check)

View File

@ -1,6 +1,7 @@
"""Default tags used by the template system, available to all templates.""" """Default tags used by the template system, available to all templates."""
from __future__ import unicode_literals from __future__ import unicode_literals
import os
import sys import sys
import re import re
from datetime import datetime from datetime import datetime
@ -328,6 +329,7 @@ class RegroupNode(Node):
return '' return ''
def include_is_allowed(filepath): def include_is_allowed(filepath):
filepath = os.path.abspath(filepath)
for root in settings.ALLOWED_INCLUDE_ROOTS: for root in settings.ALLOWED_INCLUDE_ROOTS:
if filepath.startswith(root): if filepath.startswith(root):
return True return True

View File

@ -61,7 +61,7 @@ class BaseLoader(object):
name. name.
""" """
raise NotImplementedError raise NotImplementedError('subclasses of BaseLoader must provide a load_template_source() method')
def reset(self): def reset(self):
""" """

View File

@ -7,4 +7,4 @@ from django.test.testcases import (TestCase, TransactionTestCase,
SimpleTestCase, LiveServerTestCase, skipIfDBFeature, SimpleTestCase, LiveServerTestCase, skipIfDBFeature,
skipUnlessDBFeature skipUnlessDBFeature
) )
from django.test.utils import Approximate from django.test.utils import override_settings

View File

@ -14,6 +14,8 @@ class DiscoverRunner(object):
A Django test runner that uses unittest2 test discovery. A Django test runner that uses unittest2 test discovery.
""" """
test_suite = TestSuite
test_runner = unittest.TextTestRunner
test_loader = defaultTestLoader test_loader = defaultTestLoader
reorder_by = (TestCase, ) reorder_by = (TestCase, )
option_list = ( option_list = (
@ -42,7 +44,7 @@ class DiscoverRunner(object):
unittest.installHandler() unittest.installHandler()
def build_suite(self, test_labels=None, extra_tests=None, **kwargs): def build_suite(self, test_labels=None, extra_tests=None, **kwargs):
suite = TestSuite() suite = self.test_suite()
test_labels = test_labels or ['.'] test_labels = test_labels or ['.']
extra_tests = extra_tests or [] extra_tests = extra_tests or []
@ -107,7 +109,7 @@ class DiscoverRunner(object):
return setup_databases(self.verbosity, self.interactive, **kwargs) return setup_databases(self.verbosity, self.interactive, **kwargs)
def run_suite(self, suite, **kwargs): def run_suite(self, suite, **kwargs):
return unittest.TextTestRunner( return self.test_runner(
verbosity=self.verbosity, verbosity=self.verbosity,
failfast=self.failfast, failfast=self.failfast,
).run(suite) ).run(suite)
@ -201,7 +203,8 @@ def reorder_suite(suite, classes):
classes[1], etc. Tests with no match in classes are placed last. classes[1], etc. Tests with no match in classes are placed last.
""" """
class_count = len(classes) class_count = len(classes)
bins = [unittest.TestSuite() for i in range(class_count+1)] suite_class = type(suite)
bins = [suite_class() for i in range(class_count+1)]
partition_suite(suite, classes, bins) partition_suite(suite, classes, bins)
for i in range(class_count): for i in range(class_count):
bins[0].addTests(bins[i+1]) bins[0].addTests(bins[i+1])
@ -218,8 +221,9 @@ def partition_suite(suite, classes, bins):
Tests of type classes[i] are added to bins[i], Tests of type classes[i] are added to bins[i],
tests with no match found in classes are place in bins[-1] tests with no match found in classes are place in bins[-1]
""" """
suite_class = type(suite)
for test in suite: for test in suite:
if isinstance(test, unittest.TestSuite): if isinstance(test, suite_class):
partition_suite(test, classes, bins) partition_suite(test, classes, bins)
else: else:
for i in range(len(classes)): for i in range(len(classes)):

View File

@ -225,12 +225,14 @@ class SimpleTestCase(unittest.TestCase):
return override_settings(**kwargs) return override_settings(**kwargs)
def assertRedirects(self, response, expected_url, status_code=302, def assertRedirects(self, response, expected_url, status_code=302,
target_status_code=200, host=None, msg_prefix=''): target_status_code=200, host=None, msg_prefix='',
fetch_redirect_response=True):
"""Asserts that a response redirected to a specific URL, and that the """Asserts that a response redirected to a specific URL, and that the
redirect URL can be loaded. redirect URL can be loaded.
Note that assertRedirects won't work for external links since it uses Note that assertRedirects won't work for external links since it uses
TestClient to do a request. TestClient to do a request (use fetch_redirect_response=False to check
such links without fetching thtem).
""" """
if msg_prefix: if msg_prefix:
msg_prefix += ": " msg_prefix += ": "
@ -264,14 +266,15 @@ class SimpleTestCase(unittest.TestCase):
url = response.url url = response.url
scheme, netloc, path, query, fragment = urlsplit(url) scheme, netloc, path, query, fragment = urlsplit(url)
redirect_response = response.client.get(path, QueryDict(query)) if fetch_redirect_response:
redirect_response = response.client.get(path, QueryDict(query))
# Get the redirection page, using the same client that was used # Get the redirection page, using the same client that was used
# to obtain the original response. # to obtain the original response.
self.assertEqual(redirect_response.status_code, target_status_code, self.assertEqual(redirect_response.status_code, target_status_code,
msg_prefix + "Couldn't retrieve redirection page '%s':" msg_prefix + "Couldn't retrieve redirection page '%s':"
" response code was %d (expected %d)" % " response code was %d (expected %d)" %
(path, redirect_response.status_code, target_status_code)) (path, redirect_response.status_code, target_status_code))
e_scheme, e_netloc, e_path, e_query, e_fragment = urlsplit( e_scheme, e_netloc, e_path, e_query, e_fragment = urlsplit(
expected_url) expected_url)
@ -696,6 +699,9 @@ class TransactionTestCase(SimpleTestCase):
# Subclasses can enable only a subset of apps for faster tests # Subclasses can enable only a subset of apps for faster tests
available_apps = None available_apps = None
# Subclasses can define fixtures which will be automatically installed.
fixtures = None
def _pre_setup(self): def _pre_setup(self):
"""Performs any pre-test setup. This includes: """Performs any pre-test setup. This includes:
@ -743,7 +749,7 @@ class TransactionTestCase(SimpleTestCase):
if self.reset_sequences: if self.reset_sequences:
self._reset_sequences(db_name) self._reset_sequences(db_name)
if hasattr(self, 'fixtures'): if self.fixtures:
# We have to use this slightly awkward syntax due to the fact # We have to use this slightly awkward syntax due to the fact
# that we're using *args and **kwargs together. # that we're using *args and **kwargs together.
call_command('loaddata', *self.fixtures, call_command('loaddata', *self.fixtures,
@ -835,7 +841,7 @@ class TestCase(TransactionTestCase):
disable_transaction_methods() disable_transaction_methods()
for db_name in self._databases_names(include_mirrors=False): for db_name in self._databases_names(include_mirrors=False):
if hasattr(self, 'fixtures'): if self.fixtures:
try: try:
call_command('loaddata', *self.fixtures, call_command('loaddata', *self.fixtures,
**{ **{

View File

@ -126,10 +126,10 @@ class BaseArchive(object):
return True return True
def extract(self): def extract(self):
raise NotImplementedError raise NotImplementedError('subclasses of BaseArchive must provide an extract() method')
def list(self): def list(self):
raise NotImplementedError raise NotImplementedError('subclasses of BaseArchive must provide a list() method')
class TarArchive(BaseArchive): class TarArchive(BaseArchive):

View File

@ -18,11 +18,10 @@ import calendar
import datetime import datetime
from django.utils.dates import MONTHS, MONTHS_3, MONTHS_ALT, MONTHS_AP, WEEKDAYS, WEEKDAYS_ABBR from django.utils.dates import MONTHS, MONTHS_3, MONTHS_ALT, MONTHS_AP, WEEKDAYS, WEEKDAYS_ABBR
from django.utils.tzinfo import LocalTimezone
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils import six from django.utils import six
from django.utils.timezone import is_aware, is_naive from django.utils.timezone import get_default_timezone, is_aware, is_naive
re_formatchars = re.compile(r'(?<!\\)([aAbBcdDeEfFgGhHiIjlLmMnNoOPrsStTUuwWyYzZ])') re_formatchars = re.compile(r'(?<!\\)([aAbBcdDeEfFgGhHiIjlLmMnNoOPrsStTUuwWyYzZ])')
re_escaped = re.compile(r'\\(.)') re_escaped = re.compile(r'\\(.)')
@ -48,7 +47,7 @@ class TimeFormat(Formatter):
# or time objects (against established django policy). # or time objects (against established django policy).
if isinstance(obj, datetime.datetime): if isinstance(obj, datetime.datetime):
if is_naive(obj): if is_naive(obj):
self.timezone = LocalTimezone(obj) self.timezone = get_default_timezone()
else: else:
self.timezone = obj.tzinfo self.timezone = obj.tzinfo
@ -66,7 +65,7 @@ class TimeFormat(Formatter):
def B(self): def B(self):
"Swatch Internet time" "Swatch Internet time"
raise NotImplementedError raise NotImplementedError('may be implemented in a future release')
def e(self): def e(self):
""" """

View File

@ -8,8 +8,8 @@
import datetime import datetime
import re import re
from django.utils import six from django.utils import six
from django.utils.timezone import utc from django.utils.timezone import utc, get_fixed_timezone
from django.utils.tzinfo import FixedOffset
date_re = re.compile( date_re = re.compile(
r'(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})$' r'(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})$'
@ -27,6 +27,7 @@ datetime_re = re.compile(
r'(?P<tzinfo>Z|[+-]\d{2}:?\d{2})?$' r'(?P<tzinfo>Z|[+-]\d{2}:?\d{2})?$'
) )
def parse_date(value): def parse_date(value):
"""Parses a string and return a datetime.date. """Parses a string and return a datetime.date.
@ -59,7 +60,7 @@ def parse_datetime(value):
"""Parses a string and return a datetime.datetime. """Parses a string and return a datetime.datetime.
This function supports time zone offsets. When the input contains one, This function supports time zone offsets. When the input contains one,
the output uses an instance of FixedOffset as tzinfo. the output uses a timezone with a fixed offset from UTC.
Raises ValueError if the input is well formatted but not a valid datetime. Raises ValueError if the input is well formatted but not a valid datetime.
Returns None if the input isn't well formatted. Returns None if the input isn't well formatted.
@ -76,7 +77,7 @@ def parse_datetime(value):
offset = 60 * int(tzinfo[1:3]) + int(tzinfo[-2:]) offset = 60 * int(tzinfo[1:3]) + int(tzinfo[-2:])
if tzinfo[0] == '-': if tzinfo[0] == '-':
offset = -offset offset = -offset
tzinfo = FixedOffset(offset) tzinfo = get_fixed_timezone(offset)
kw = dict((k, int(v)) for k, v in six.iteritems(kw) if v is not None) kw = dict((k, int(v)) for k, v in six.iteritems(kw) if v is not None)
kw['tzinfo'] = tzinfo kw['tzinfo'] = tzinfo
return datetime.datetime(**kw) return datetime.datetime(**kw)

View File

@ -177,7 +177,7 @@ class SyndicationFeed(object):
Outputs the feed in the given encoding to outfile, which is a file-like Outputs the feed in the given encoding to outfile, which is a file-like
object. Subclasses should override this. object. Subclasses should override this.
""" """
raise NotImplementedError raise NotImplementedError('subclasses of SyndicationFeed must provide a write() method')
def writeString(self, encoding): def writeString(self, encoding):
""" """

View File

@ -257,7 +257,7 @@ class LazyObject(object):
""" """
Must be implemented by subclasses to initialise the wrapped object. Must be implemented by subclasses to initialise the wrapped object.
""" """
raise NotImplementedError raise NotImplementedError('subclasses of LazyObject must provide a _setup() method')
# Introspection support # Introspection support
__dir__ = new_method_proxy(dir) __dir__ = new_method_proxy(dir)

View File

@ -84,24 +84,22 @@ class AdminEmailHandler(logging.Handler):
record.getMessage() record.getMessage()
) )
filter = get_exception_reporter_filter(request) filter = get_exception_reporter_filter(request)
request_repr = filter.get_request_repr(request) request_repr = '\n{0}'.format(filter.get_request_repr(request))
except Exception: except Exception:
subject = '%s: %s' % ( subject = '%s: %s' % (
record.levelname, record.levelname,
record.getMessage() record.getMessage()
) )
request = None request = None
request_repr = "Request repr() unavailable." request_repr = "unavailable"
subject = self.format_subject(subject) subject = self.format_subject(subject)
if record.exc_info: if record.exc_info:
exc_info = record.exc_info exc_info = record.exc_info
stack_trace = '\n'.join(traceback.format_exception(*record.exc_info))
else: else:
exc_info = (None, record.getMessage(), None) exc_info = (None, record.getMessage(), None)
stack_trace = 'No stack trace available'
message = "%s\n\n%s" % (stack_trace, request_repr) message = "%s\n\nRequest repr(): %s" % (self.format(record), request_repr)
reporter = ExceptionReporter(request, is_email=True, *exc_info) reporter = ExceptionReporter(request, is_email=True, *exc_info)
html_message = reporter.get_traceback_html() if self.include_html else None html_message = reporter.get_traceback_html() if self.include_html else None
mail.mail_admins(subject, message, fail_silently=True, mail.mail_admins(subject, message, fail_silently=True,

View File

@ -1,5 +1,6 @@
from __future__ import absolute_import # Avoid importing `importlib` from this package. from __future__ import absolute_import # Avoid importing `importlib` from this package.
import copy
import imp import imp
from importlib import import_module from importlib import import_module
import os import os
@ -34,6 +35,43 @@ def import_by_path(dotted_path, error_prefix=''):
return attr return attr
def autodiscover_modules(*args, **kwargs):
"""
Auto-discover INSTALLED_APPS modules and fail silently when
not present. This forces an import on them to register any admin bits they
may want.
You may provide a register_to keyword parameter as a way to access a
registry. This register_to object must have a _registry instance variable
to access it.
"""
from django.conf import settings
register_to = kwargs.get('register_to')
for app in settings.INSTALLED_APPS:
mod = import_module(app)
# Attempt to import the app's module.
try:
if register_to:
before_import_registry = copy.copy(register_to._registry)
for module_to_search in args:
import_module('%s.%s' % (app, module_to_search))
except:
# Reset the model registry to the state before the last import as
# this import will have to reoccur on the next request and this
# could raise NotRegistered and AlreadyRegistered exceptions
# (see #8245).
if register_to:
register_to._registry = before_import_registry
# Decide whether to bubble up this error. If the app just
# doesn't have an admin module, we can ignore the error
# attempting to import it, otherwise we want it to bubble up.
if module_has_submodule(mod, module_to_search):
raise
def module_has_submodule(package, module_name): def module_has_submodule(package, module_name):
"""See if 'module' is in 'package'.""" """See if 'module' is in 'package'."""
name = ".".join([package.__name__, module_name]) name = ".".join([package.__name__, module_name])

View File

@ -92,7 +92,7 @@ def normalize(pattern):
result.append(".") result.append(".")
elif ch == '|': elif ch == '|':
# FIXME: One day we'll should do this, but not in 1.0. # FIXME: One day we'll should do this, but not in 1.0.
raise NotImplementedError raise NotImplementedError('Awaiting Implementation')
elif ch == "^": elif ch == "^":
pass pass
elif ch == '$': elif ch == '$':

View File

@ -18,7 +18,8 @@ from django.conf import settings
from django.utils import six from django.utils import six
__all__ = [ __all__ = [
'utc', 'get_default_timezone', 'get_current_timezone', 'utc', 'get_fixed_timezone',
'get_default_timezone', 'get_current_timezone',
'activate', 'deactivate', 'override', 'activate', 'deactivate', 'override',
'is_naive', 'is_aware', 'make_aware', 'make_naive', 'is_naive', 'is_aware', 'make_aware', 'make_naive',
] ]
@ -47,19 +48,45 @@ class UTC(tzinfo):
def dst(self, dt): def dst(self, dt):
return ZERO return ZERO
class FixedOffset(tzinfo):
"""
Fixed offset in minutes east from UTC. Taken from Python's docs.
Kept as close as possible to the reference version. __init__ was changed
to make its arguments optional, according to Python's requirement that
tzinfo subclasses can be instantiated without arguments.
"""
def __init__(self, offset=None, name=None):
if offset is not None:
self.__offset = timedelta(minutes=offset)
if name is not None:
self.__name = name
def utcoffset(self, dt):
return self.__offset
def tzname(self, dt):
return self.__name
def dst(self, dt):
return ZERO
class ReferenceLocalTimezone(tzinfo): class ReferenceLocalTimezone(tzinfo):
""" """
Local time implementation taken from Python's docs. Local time. Taken from Python's docs.
Used only when pytz isn't available, and most likely inaccurate. If you're Used only when pytz isn't available, and most likely inaccurate. If you're
having trouble with this class, don't waste your time, just install pytz. having trouble with this class, don't waste your time, just install pytz.
Kept identical to the reference version. Subclasses contain improvements. Kept as close as possible to the reference version. __init__ was added to
delay the computation of STDOFFSET, DSTOFFSET and DSTDIFF which is
performed at import time in the example.
Subclasses contain further improvements.
""" """
def __init__(self): def __init__(self):
# This code is moved in __init__ to execute it as late as possible
# See get_default_timezone().
self.STDOFFSET = timedelta(seconds=-_time.timezone) self.STDOFFSET = timedelta(seconds=-_time.timezone)
if _time.daylight: if _time.daylight:
self.DSTOFFSET = timedelta(seconds=-_time.altzone) self.DSTOFFSET = timedelta(seconds=-_time.altzone)
@ -68,9 +95,6 @@ class ReferenceLocalTimezone(tzinfo):
self.DSTDIFF = self.DSTOFFSET - self.STDOFFSET self.DSTDIFF = self.DSTOFFSET - self.STDOFFSET
tzinfo.__init__(self) tzinfo.__init__(self)
def __repr__(self):
return "<LocalTimezone>"
def utcoffset(self, dt): def utcoffset(self, dt):
if self._isdst(dt): if self._isdst(dt):
return self.DSTOFFSET return self.DSTOFFSET
@ -84,8 +108,7 @@ class ReferenceLocalTimezone(tzinfo):
return ZERO return ZERO
def tzname(self, dt): def tzname(self, dt):
is_dst = False if dt is None else self._isdst(dt) return _time.tzname[self._isdst(dt)]
return _time.tzname[is_dst]
def _isdst(self, dt): def _isdst(self, dt):
tt = (dt.year, dt.month, dt.day, tt = (dt.year, dt.month, dt.day,
@ -103,6 +126,10 @@ class LocalTimezone(ReferenceLocalTimezone):
error message is helpful. error message is helpful.
""" """
def tzname(self, dt):
is_dst = False if dt is None else self._isdst(dt)
return _time.tzname[is_dst]
def _isdst(self, dt): def _isdst(self, dt):
try: try:
return super(LocalTimezone, self)._isdst(dt) return super(LocalTimezone, self)._isdst(dt)
@ -116,6 +143,17 @@ class LocalTimezone(ReferenceLocalTimezone):
utc = pytz.utc if pytz else UTC() utc = pytz.utc if pytz else UTC()
"""UTC time zone as a tzinfo instance.""" """UTC time zone as a tzinfo instance."""
def get_fixed_timezone(offset):
"""
Returns a tzinfo instance with a fixed offset from UTC.
"""
if isinstance(offset, timedelta):
offset = offset.seconds // 60
sign = '-' if offset < 0 else '+'
hhmm = '%02d%02d' % divmod(abs(offset), 60)
name = sign + hhmm
return FixedOffset(offset, name)
# In order to avoid accessing the settings at compile time, # In order to avoid accessing the settings at compile time,
# wrap the expression in a function and cache the result. # wrap the expression in a function and cache the result.
_localtime = None _localtime = None
@ -125,8 +163,6 @@ def get_default_timezone():
Returns the default time zone as a tzinfo instance. Returns the default time zone as a tzinfo instance.
This is the time zone defined by settings.TIME_ZONE. This is the time zone defined by settings.TIME_ZONE.
See also :func:`get_current_timezone`.
""" """
global _localtime global _localtime
if _localtime is None: if _localtime is None:

View File

@ -668,7 +668,10 @@ def parse_accept_lang_header(lang_string):
if first: if first:
return [] return []
if priority: if priority:
priority = float(priority) try:
priority = float(priority)
except ValueError:
return []
if not priority: # if priority is 0.0 at this point make it 1.0 if not priority: # if priority is 0.0 at this point make it 1.0
priority = 1.0 priority = 1.0
result.append((lang, priority)) result.append((lang, priority))

View File

@ -2,11 +2,18 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import time
from datetime import timedelta, tzinfo from datetime import timedelta, tzinfo
import time
import warnings
from django.utils.encoding import force_str, force_text, DEFAULT_LOCALE_ENCODING from django.utils.encoding import force_str, force_text, DEFAULT_LOCALE_ENCODING
warnings.warn(
"django.utils.tzinfo will be removed in Django 1.9. "
"Use django.utils.timezone instead.",
PendingDeprecationWarning)
# Python's doc say: "A tzinfo subclass must have an __init__() method that can # Python's doc say: "A tzinfo subclass must have an __init__() method that can
# be called with no arguments". FixedOffset and LocalTimezone don't honor this # be called with no arguments". FixedOffset and LocalTimezone don't honor this
# requirement. Defining __getinitargs__ is sufficient to fix copy/deepcopy as # requirement. Defining __getinitargs__ is sufficient to fix copy/deepcopy as
@ -15,6 +22,10 @@ from django.utils.encoding import force_str, force_text, DEFAULT_LOCALE_ENCODING
class FixedOffset(tzinfo): class FixedOffset(tzinfo):
"Fixed offset in minutes east from UTC." "Fixed offset in minutes east from UTC."
def __init__(self, offset): def __init__(self, offset):
warnings.warn(
"django.utils.tzinfo.FixedOffset will be removed in Django 1.9. "
"Use django.utils.timezone.get_fixed_timezone instead.",
PendingDeprecationWarning)
if isinstance(offset, timedelta): if isinstance(offset, timedelta):
self.__offset = offset self.__offset = offset
offset = self.__offset.seconds // 60 offset = self.__offset.seconds // 60
@ -48,6 +59,10 @@ class FixedOffset(tzinfo):
class LocalTimezone(tzinfo): class LocalTimezone(tzinfo):
"Proxy timezone information from time module." "Proxy timezone information from time module."
def __init__(self, dt): def __init__(self, dt):
warnings.warn(
"django.utils.tzinfo.LocalTimezone will be removed in Django 1.9. "
"Use django.utils.timezone.get_default_timezone instead.",
PendingDeprecationWarning)
tzinfo.__init__(self) tzinfo.__init__(self)
self.__dt = dt self.__dt = dt
self._tzname = self.tzname(dt) self._tzname = self.tzname(dt)

View File

@ -106,7 +106,7 @@ this by adding the following snippet to your urls.py::
Also this helper function only serves the actual :setting:`STATIC_ROOT` Also this helper function only serves the actual :setting:`STATIC_ROOT`
folder; it doesn't perform static files discovery like folder; it doesn't perform static files discovery like
`:mod:`django.contrib.staticfiles`. :mod:`django.contrib.staticfiles`.
Serving files uploaded by a user during development. Serving files uploaded by a user during development.
==================================================== ====================================================

View File

@ -34,6 +34,8 @@ Decisions on new committers will follow the process explained in
existing committer privately. Public requests for commit access are potential existing committer privately. Public requests for commit access are potential
flame-war starters, and will simply be ignored. flame-war starters, and will simply be ignored.
.. _handling-pull-requests:
Handling pull requests Handling pull requests
---------------------- ----------------------

View File

@ -212,7 +212,7 @@ This way your branch will contain only commits related to its topic, which
makes squashing easier. makes squashing easier.
After review After review
------------ ~~~~~~~~~~~~
It is unusual to get any non-trivial amount of code into core without changes It is unusual to get any non-trivial amount of code into core without changes
requested by reviewers. In this case, it is often a good idea to add the requested by reviewers. In this case, it is often a good idea to add the
@ -225,7 +225,8 @@ commits, you would run::
git rebase -i HEAD~2 git rebase -i HEAD~2
Squash the second commit into the first. Write a commit message along the lines of:: Squash the second commit into the first. Write a commit message along the lines
of::
Made changes asked in review by <reviewer> Made changes asked in review by <reviewer>
@ -239,8 +240,25 @@ the public commits during the rebase, you should not need to force-push::
Your pull request should now contain the new commit too. Your pull request should now contain the new commit too.
Note that the committer is likely to squash the review commit into the previous commit Note that the committer is likely to squash the review commit into the previous
when committing the code. commit when committing the code.
Working on a patch
------------------
One of the ways that developers can contribute to Django is by reviewing
patches. Those patches will typically exist as pull requests on GitHub and
can be easily integrated into your local repository::
git checkout -b pull_xxxxx upstream/master
curl https://github.com/django/django/pull/xxxxx.patch | git am
This will create a new branch and then apply the changes from the pull request
to it. At this point you can run the tests or do anything else you need to
do to investigate the quality of the patch.
For more detail on working with pull requests see the
:ref:`guidelines for committers <handling-pull-requests>`.
Summary Summary
------- -------

View File

@ -117,9 +117,9 @@ these changes.
* The ``mod_python`` request handler will be removed. The ``mod_wsgi`` * The ``mod_python`` request handler will be removed. The ``mod_wsgi``
handler should be used instead. handler should be used instead.
* The ``template`` attribute on :class:`~django.test.client.Response` * The ``template`` attribute on :class:`~django.test.Response`
objects returned by the :ref:`test client <test-client>` will be removed. objects returned by the :ref:`test client <test-client>` will be removed.
The :attr:`~django.test.client.Response.templates` attribute should be The :attr:`~django.test.Response.templates` attribute should be
used instead. used instead.
* The ``django.test.simple.DjangoTestRunner`` will be removed. * The ``django.test.simple.DjangoTestRunner`` will be removed.
@ -417,6 +417,8 @@ these changes.
* ``django.utils.importlib`` will be removed. * ``django.utils.importlib`` will be removed.
* ``django.utils.tzinfo`` will be removed.
* ``django.utils.unittest`` will be removed. * ``django.utils.unittest`` will be removed.
* The ``syncdb`` command will be removed. * The ``syncdb`` command will be removed.

View File

@ -102,7 +102,7 @@ Installing some prerequisites
The current state of Python packaging is a bit muddled with various tools. For The current state of Python packaging is a bit muddled with various tools. For
this tutorial, we're going to use distribute_ to build our package. It's a this tutorial, we're going to use distribute_ to build our package. It's a
community-maintained fork of the older ``setuptools`` project. We'll also be community-maintained fork of the older ``setuptools`` project. We'll also be
using `pip`_ to uninstall it after we're finished. You should install these using `pip`_ to install and uninstall it. You should install these
two packages now. If you need help, you can refer to :ref:`how to install two packages now. If you need help, you can refer to :ref:`how to install
Django with pip<installing-official-release>`. You can install ``distribute`` Django with pip<installing-official-release>`. You can install ``distribute``
the same way. the same way.
@ -262,28 +262,18 @@ working. We'll now fix this by installing our new ``django-polls`` package.
tools that run as that user, so ``virtualenv`` is a more robust solution tools that run as that user, so ``virtualenv`` is a more robust solution
(see below). (see below).
1. Inside ``django-polls/dist``, untar the new package 1. To install the package, use pip (you already :ref:`installed it
``django-polls-0.1.tar.gz`` (e.g. ``tar xzvf django-polls-0.1.tar.gz``). If <installing-reusable-apps-prerequisites>`, right?)::
you're using Windows, you can download the command-line tool bsdtar_ to do
this, or you can use a GUI-based tool such as 7-zip_.
2. Change into the directory created in step 1 (e.g. ``cd django-polls-0.1``). pip install --user django-polls/dist/django-polls-0.1.tar.gz
3. If you're using GNU/Linux, Mac OS X or some other flavor of Unix, enter the 2. With luck, your Django project should now work correctly again. Run the
command ``python setup.py install --user`` at the shell prompt. If you're
using Windows, start up a command shell and run the command
``setup.py install --user``.
With luck, your Django project should now work correctly again. Run the
server again to confirm this. server again to confirm this.
4. To uninstall the package, use pip (you already :ref:`installed it 3. To uninstall the package, use pip::
<installing-reusable-apps-prerequisites>`, right?)::
pip uninstall django-polls pip uninstall django-polls
.. _bsdtar: http://gnuwin32.sourceforge.net/packages/bsdtar.htm
.. _7-zip: http://www.7-zip.org/
.. _pip: http://pypi.python.org/pypi/pip .. _pip: http://pypi.python.org/pypi/pip
Publishing your app Publishing your app

View File

@ -319,7 +319,7 @@ Before we try to fix anything, let's have a look at the tools at our disposal.
The Django test client The Django test client
---------------------- ----------------------
Django provides a test :class:`~django.test.client.Client` to simulate a user Django provides a test :class:`~django.test.Client` to simulate a user
interacting with the code at the view level. We can use it in ``tests.py`` interacting with the code at the view level. We can use it in ``tests.py``
or even in the shell. or even in the shell.
@ -341,7 +341,7 @@ Next we need to import the test client class (later in ``tests.py`` we will use
the :class:`django.test.TestCase` class, which comes with its own client, so the :class:`django.test.TestCase` class, which comes with its own client, so
this won't be required):: this won't be required)::
>>> from django.test.client import Client >>> from django.test import Client
>>> # create an instance of the client for our use >>> # create an instance of the client for our use
>>> client = Client() >>> client = Client()
@ -494,7 +494,7 @@ class::
""" """
The questions index page may display multiple questions. The questions index page may display multiple questions.
""" """
create_question(question_text="Past quesiton 1.", days=-30) create_question(question_text="Past question 1.", days=-30)
create_question(question_text="Past question 2.", days=-5) create_question(question_text="Past question 2.", days=-5)
response = self.client.get(reverse('polls:index')) response = self.client.get(reverse('polls:index'))
self.assertQuerysetEqual( self.assertQuerysetEqual(

View File

@ -142,7 +142,7 @@ Methods
.. versionchanged:: 1.6 .. versionchanged:: 1.6
In Django 1.4 and 1.5, a blank string was unintentionally stored In Django 1.4 and 1.5, a blank string was unintentionally stored
as an unsable password. as an unusable password.
.. method:: check_password(raw_password) .. method:: check_password(raw_password)

View File

@ -56,7 +56,7 @@ needs to be customized so that SQLite knows to build the R*Tree module::
__ http://www.sqlite.org/rtree.html __ http://www.sqlite.org/rtree.html
__ http://www.sqlite.org/download.html __ http://www.sqlite.org/download.html
.. _spatialitebuild : .. _spatialitebuild:
SpatiaLite library (``libspatialite``) and tools (``spatialite``) SpatiaLite library (``libspatialite``) and tools (``spatialite``)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -74,15 +74,17 @@ customization of the ``configure`` command is necessary. If not, then run the
``configure`` script, make, and install for the SpatiaLite library:: ``configure`` script, make, and install for the SpatiaLite library::
$ cd libspatialite-amalgamation-2.3.1 $ cd libspatialite-amalgamation-2.3.1
$ ./configure # May need to modified, see notes below. $ ./configure # May need to be modified, see notes below.
$ make $ make
$ sudo make install $ sudo make install
$ cd .... _spatialite $ cd ..
.. _spatialite_tools:
Finally, do the same for the SpatiaLite tools:: Finally, do the same for the SpatiaLite tools::
$ cd spatialite-tools-2.3.1 $ cd spatialite-tools-2.3.1
$ ./configure # May need to modified, see notes below. $ ./configure # May need to be modified, see notes below.
$ make $ make
$ sudo make install $ sudo make install
$ cd .. $ cd ..

View File

@ -1638,6 +1638,15 @@ Examples::
management.call_command('flush', verbosity=0, interactive=False) management.call_command('flush', verbosity=0, interactive=False)
management.call_command('loaddata', 'test_data', verbosity=0) management.call_command('loaddata', 'test_data', verbosity=0)
Note that command options that take no arguments are passed as keywords
with ``True`` or ``False``::
management.call_command('dumpdata', use_natural_keys=True)
Command options which take multiple options are passed a list::
management.call_command('dumpdata', exclude=['contenttypes', 'auth'])
Output redirection Output redirection
================== ==================

View File

@ -79,6 +79,20 @@ GZip middleware
.. class:: GZipMiddleware .. class:: GZipMiddleware
.. warning::
Security researchers recently revealed that when compression techniques
(including ``GZipMiddleware``) are used on a website, the site becomes
exposed to a number of possible attacks. These approaches can be used to
compromise, amongst other things, Django's CSRF protection. Before using
``GZipMiddleware`` on your site, you should consider very carefully whether
you are subject to these attacks. If you're in *any* doubt about whether
you're affected, you should avoid using ``GZipMiddleware``. For more
details, see the `the BREACH paper (PDF)`_ and `breachattack.com`_.
.. _the BREACH paper (PDF): http://breachattack.com/resources/BREACH%20-%20SSL,%20gone%20in%2030%20seconds.pdf
.. _breachattack.com: http://breachattack.com
Compresses content for browsers that understand GZip compression (all modern Compresses content for browsers that understand GZip compression (all modern
browsers). browsers).

View File

@ -221,6 +221,12 @@ Django quotes column and table names behind the scenes.
ordering = ['-pub_date', 'author'] ordering = ['-pub_date', 'author']
.. warning::
Ordering is not a free operation. Each field you add to the ordering
incurs a cost to your database. Each foreign key you add will
implicitly include all of its default orderings as well.
``permissions`` ``permissions``
--------------- ---------------

View File

@ -334,6 +334,12 @@ You can tell if a query is ordered or not by checking the
:attr:`.QuerySet.ordered` attribute, which will be ``True`` if the :attr:`.QuerySet.ordered` attribute, which will be ``True`` if the
``QuerySet`` has been ordered in any way. ``QuerySet`` has been ordered in any way.
.. warning::
Ordering is not a free operation. Each field you add to the ordering
incurs a cost to your database. Each foreign key you add will
implicitly include all of its default orderings as well.
reverse reverse
~~~~~~~ ~~~~~~~
@ -2268,7 +2274,8 @@ SQL equivalent::
(The exact SQL syntax varies for each database engine.) (The exact SQL syntax varies for each database engine.)
When :setting:`USE_TZ` is ``True``, datetime fields are converted to the When :setting:`USE_TZ` is ``True``, datetime fields are converted to the
current time zone before filtering. current time zone before filtering. This requires :ref:`time zone definitions
in the database <database-time-zone-definitions>`.
.. fieldlookup:: day .. fieldlookup:: day
@ -2291,7 +2298,8 @@ Note this will match any record with a pub_date on the third day of the month,
such as January 3, July 3, etc. such as January 3, July 3, etc.
When :setting:`USE_TZ` is ``True``, datetime fields are converted to the When :setting:`USE_TZ` is ``True``, datetime fields are converted to the
current time zone before filtering. current time zone before filtering. This requires :ref:`time zone definitions
in the database <database-time-zone-definitions>`.
.. fieldlookup:: week_day .. fieldlookup:: week_day
@ -2315,7 +2323,8 @@ Note this will match any record with a ``pub_date`` that falls on a Monday (day
are indexed with day 1 being Sunday and day 7 being Saturday. are indexed with day 1 being Sunday and day 7 being Saturday.
When :setting:`USE_TZ` is ``True``, datetime fields are converted to the When :setting:`USE_TZ` is ``True``, datetime fields are converted to the
current time zone before filtering. current time zone before filtering. This requires :ref:`time zone definitions
in the database <database-time-zone-definitions>`.
.. fieldlookup:: hour .. fieldlookup:: hour

View File

@ -565,7 +565,7 @@ setting_changed
This signal is sent when the value of a setting is changed through the This signal is sent when the value of a setting is changed through the
``django.test.TestCase.settings()`` context manager or the ``django.test.TestCase.settings()`` context manager or the
:func:`django.test.utils.override_settings` decorator/context manager. :func:`django.test.override_settings` decorator/context manager.
It's actually sent twice: when the new value is applied ("setup") and when the It's actually sent twice: when the new value is applied ("setup") and when the
original value is restored ("teardown"). Use the ``enter`` argument to original value is restored ("teardown"). Use the ``enter`` argument to

View File

@ -2226,7 +2226,7 @@ If ``value`` is ``"http://www.example.org/"``, the output will be
urlize urlize
^^^^^^ ^^^^^^
Converts URLs in text into clickable links. Converts URLs and email addresses in text into clickable links.
This template tag works on links prefixed with ``http://``, ``https://``, or This template tag works on links prefixed with ``http://``, ``https://``, or
``www.``. For example, ``http://goo.gl/aia1t`` will get converted but ``www.``. For example, ``http://goo.gl/aia1t`` will get converted but
@ -2250,6 +2250,11 @@ If ``value`` is ``"Check out www.djangoproject.com"``, the output will be
``"Check out <a href="http://www.djangoproject.com" ``"Check out <a href="http://www.djangoproject.com"
rel="nofollow">www.djangoproject.com</a>"``. rel="nofollow">www.djangoproject.com</a>"``.
In addition to web links, ``urlize`` also converts email addresses into
``mailto:`` links. If ``value`` is
``"Send questions to foo@example.com"``, the output will be
``"Send questions to <a href="mailto:foo@example.com">foo@example</a>"``.
The ``urlize`` filter also takes an optional parameter ``autoescape``. If The ``urlize`` filter also takes an optional parameter ``autoescape``. If
``autoescape`` is ``True``, the link text and URLs will be escaped using ``autoescape`` is ``True``, the link text and URLs will be escaped using
Django's built-in :tfilter:`escape` filter. The default value for Django's built-in :tfilter:`escape` filter. The default value for
@ -2265,7 +2270,7 @@ Django's built-in :tfilter:`escape` filter. The default value for
urlizetrunc urlizetrunc
^^^^^^^^^^^ ^^^^^^^^^^^
Converts URLs into clickable links just like urlize_, but truncates URLs Converts URLs and email addresses into clickable links just like urlize_, but truncates URLs
longer than the given character limit. longer than the given character limit.
**Argument:** Number of characters that link text should be truncated to, **Argument:** Number of characters that link text should be truncated to,

View File

@ -927,6 +927,17 @@ For a complete discussion on the usage of the following see the
:class:`~datetime.tzinfo` instance that represents UTC. :class:`~datetime.tzinfo` instance that represents UTC.
.. function:: get_fixed_timezone(offset)
.. versionadded:: 1.7
Returns a :class:`~datetime.tzinfo` instance that represents a time zone
with a fixed offset from UTC.
``offset`` is a :class:`datetime.timedelta` or an integer number of
minutes. Use positive values for time zones east of UTC and negative
values for west of UTC.
.. function:: get_default_timezone() .. function:: get_default_timezone()
Returns a :class:`~datetime.tzinfo` instance that represents the Returns a :class:`~datetime.tzinfo` instance that represents the
@ -1021,6 +1032,9 @@ For a complete discussion on the usage of the following see the
``django.utils.tzinfo`` ``django.utils.tzinfo``
======================= =======================
.. deprecated:: 1.7
Use :mod:`~django.utils.timezone` instead.
.. module:: django.utils.tzinfo .. module:: django.utils.tzinfo
:synopsis: Implementation of ``tzinfo`` classes for use with ``datetime.datetime``. :synopsis: Implementation of ``tzinfo`` classes for use with ``datetime.datetime``.
@ -1028,6 +1042,12 @@ For a complete discussion on the usage of the following see the
Fixed offset in minutes east from UTC. Fixed offset in minutes east from UTC.
.. deprecated:: 1.7
Use :func:`~django.utils.timezone.get_fixed_timezone` instead.
.. class:: LocalTimezone .. class:: LocalTimezone
Proxy timezone information from time module. Proxy timezone information from time module.
.. deprecated:: 1.7
Use :func:`~django.utils.timezone.get_default_timezone` instead.

View File

@ -643,7 +643,7 @@ The generic relation classes -- ``GenericForeignKey`` and ``GenericRelation``
Testing Testing
------- -------
:meth:`django.test.client.Client.login` has changed :meth:`django.test.Client.login` has changed
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Old (0.96):: Old (0.96)::

View File

@ -99,7 +99,7 @@ one fell swoop.
Testing improvements Testing improvements
-------------------- --------------------
.. currentmodule:: django.test.client .. currentmodule:: django.test
A couple of small but very useful improvements have been made to the A couple of small but very useful improvements have been made to the
:doc:`testing framework </topics/testing/index>`: :doc:`testing framework </topics/testing/index>`:

View File

@ -285,8 +285,6 @@ full description, and some important notes on database support.
Test client improvements Test client improvements
------------------------ ------------------------
.. currentmodule:: django.test.client
A couple of small -- but highly useful -- improvements have been made to the A couple of small -- but highly useful -- improvements have been made to the
test client: test client:

View File

@ -23,7 +23,7 @@ case of Django 1.2.2, we have made an exception to this rule.
In order to test a bug fix that forms part of the 1.2.2 release, it In order to test a bug fix that forms part of the 1.2.2 release, it
was necessary to add a feature -- the ``enforce_csrf_checks`` flag -- was necessary to add a feature -- the ``enforce_csrf_checks`` flag --
to the :mod:`test client <django.test.client>`. This flag forces to the :ref:`test client <test-client>`. This flag forces
the test client to perform full CSRF checks on forms. The default the test client to perform full CSRF checks on forms. The default
behavior of the test client hasn't changed, but if you want to do behavior of the test client hasn't changed, but if you want to do
CSRF checks with the test client, it is now possible to do so. CSRF checks with the test client, it is now possible to do so.

View File

@ -150,7 +150,7 @@ requests. These include:
* Improved tools for accessing and manipulating the current Site via * Improved tools for accessing and manipulating the current Site via
``django.contrib.sites.models.get_current_site()``. ``django.contrib.sites.models.get_current_site()``.
* A :class:`~django.test.client.RequestFactory` for mocking * A :class:`~django.test.RequestFactory` for mocking
requests in tests. requests in tests.
* A new test assertion -- * A new test assertion --
@ -318,7 +318,7 @@ Test client response ``template`` attribute
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Django's :ref:`test client <test-client>` returns Django's :ref:`test client <test-client>` returns
:class:`~django.test.client.Response` objects annotated with extra testing :class:`~django.test.Response` objects annotated with extra testing
information. In Django versions prior to 1.3, this included a ``template`` information. In Django versions prior to 1.3, this included a ``template``
attribute containing information about templates rendered in generating the attribute containing information about templates rendered in generating the
response: either None, a single :class:`~django.template.Template` object, or a response: either None, a single :class:`~django.template.Template` object, or a
@ -327,7 +327,7 @@ return values (sometimes a list, sometimes not) made the attribute difficult
to work with. to work with.
In Django 1.3 the ``template`` attribute is deprecated in favor of a new In Django 1.3 the ``template`` attribute is deprecated in favor of a new
:attr:`~django.test.client.Response.templates` attribute, which is always a :attr:`~django.test.Response.templates` attribute, which is always a
list, even if it has only a single element or no elements. list, even if it has only a single element or no elements.
``DjangoTestRunner`` ``DjangoTestRunner``

View File

@ -295,7 +295,7 @@ requests. These include:
:class:`~django.contrib.sites.models.Site` object in :class:`~django.contrib.sites.models.Site` object in
:doc:`the sites framework </ref/contrib/sites>`. :doc:`the sites framework </ref/contrib/sites>`.
* A :class:`~django.test.client.RequestFactory` for mocking requests * A :class:`~django.test.RequestFactory` for mocking requests
in tests. in tests.
* A new test assertion -- * A new test assertion --
@ -715,7 +715,7 @@ Test client response ``template`` attribute
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Django's :ref:`test client <test-client>` returns Django's :ref:`test client <test-client>` returns
:class:`~django.test.client.Response` objects annotated with extra testing :class:`~django.test.Response` objects annotated with extra testing
information. In Django versions prior to 1.3, this included a ``template`` information. In Django versions prior to 1.3, this included a ``template``
attribute containing information about templates rendered in generating the attribute containing information about templates rendered in generating the
response: either None, a single :class:`~django.template.Template` object, or a response: either None, a single :class:`~django.template.Template` object, or a
@ -724,7 +724,7 @@ return values (sometimes a list, sometimes not) made the attribute difficult
to work with. to work with.
In Django 1.3 the ``template`` attribute is deprecated in favor of a new In Django 1.3 the ``template`` attribute is deprecated in favor of a new
:attr:`~django.test.client.Response.templates` attribute, which is always a :attr:`~django.test.Response.templates` attribute, which is always a
list, even if it has only a single element or no elements. list, even if it has only a single element or no elements.
``DjangoTestRunner`` ``DjangoTestRunner``

View File

@ -26,6 +26,6 @@ header and browsers seem to ignore JavaScript there.
Bugfixes Bugfixes
======== ========
* Fixed an obscure bug with the :func:`~django.test.utils.override_settings` * Fixed an obscure bug with the :func:`~django.test.override_settings`
decorator. If you hit an ``AttributeError: 'Settings' object has no attribute decorator. If you hit an ``AttributeError: 'Settings' object has no attribute
'_original_allowed_hosts'`` exception, it's probably fixed (#20636). '_original_allowed_hosts'`` exception, it's probably fixed (#20636).

25
docs/releases/1.4.7.txt Normal file
View File

@ -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.

View File

@ -57,6 +57,6 @@ Bugfixes
* Ensured that the WSGI request's path is correctly based on the * Ensured that the WSGI request's path is correctly based on the
``SCRIPT_NAME`` environment variable or the :setting:`FORCE_SCRIPT_NAME` ``SCRIPT_NAME`` environment variable or the :setting:`FORCE_SCRIPT_NAME`
setting, regardless of whether or not either has a trailing slash (#20169). setting, regardless of whether or not either has a trailing slash (#20169).
* Fixed an obscure bug with the :func:`~django.test.utils.override_settings` * Fixed an obscure bug with the :func:`~django.test.override_settings`
decorator. If you hit an ``AttributeError: 'Settings' object has no attribute decorator. If you hit an ``AttributeError: 'Settings' object has no attribute
'_original_allowed_hosts'`` exception, it's probably fixed (#20636). '_original_allowed_hosts'`` exception, it's probably fixed (#20636).

50
docs/releases/1.5.3.txt Normal file
View File

@ -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.

View File

@ -435,6 +435,21 @@ but will not be removed from Django until version 1.8.
.. _recommendations in the Python documentation: http://docs.python.org/2/library/doctest.html#unittest-api .. _recommendations in the Python documentation: http://docs.python.org/2/library/doctest.html#unittest-api
Time zone-aware ``day``, ``month``, and ``week_day`` lookups
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Django 1.6 introduces time zone support for :lookup:`day`, :lookup:`month`,
and :lookup:`week_day` lookups when :setting:`USE_TZ` is ``True``. These
lookups were previously performed in UTC regardless of the current time zone.
This requires :ref:`time zone definitions in the database
<database-time-zone-definitions>`. If you're using SQLite, you must install
pytz_. If you're using MySQL, you must install pytz_ and load the time zone
tables with `mysql_tzinfo_to_sql`_.
.. _pytz: http://pytz.sourceforge.net/
.. _mysql_tzinfo_to_sql: http://dev.mysql.com/doc/refman/5.5/en/mysql-tzinfo-to-sql.html
Addition of ``QuerySet.datetimes()`` Addition of ``QuerySet.datetimes()``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -814,7 +829,7 @@ Miscellaneous
``{% url %}`` tag, it causes template rendering to fail like always when ``{% url %}`` tag, it causes template rendering to fail like always when
``NoReverseMatch`` is raised. ``NoReverseMatch`` is raised.
* :meth:`django.test.client.Client.logout` now calls * :meth:`django.test.Client.logout` now calls
:meth:`django.contrib.auth.logout` which will send the :meth:`django.contrib.auth.logout` which will send the
:func:`~django.contrib.auth.signals.user_logged_out` signal. :func:`~django.contrib.auth.signals.user_logged_out` signal.
@ -1046,12 +1061,12 @@ security problem described in the section above, because they can automatically
create a ``ModelForm`` that uses all fields for a model. create a ``ModelForm`` that uses all fields for a model.
For this reason, if you use these views for editing models, you must also supply For this reason, if you use these views for editing models, you must also supply
the ``fields`` attribute, which is a list of model fields and works in the same the ``fields`` attribute (new in Django 1.6), which is a list of model fields
way as the :class:`~django.forms.ModelForm` ``Meta.fields`` attribute. Alternatively, and works in the same way as the :class:`~django.forms.ModelForm`
you can set set the ``form_class`` attribute to a ``ModelForm`` that explicitly ``Meta.fields`` attribute. Alternatively, you can set set the ``form_class``
defines the fields to be used. Defining an ``UpdateView`` or ``CreateView`` attribute to a ``ModelForm`` that explicitly defines the fields to be used.
subclass to be used with a model but without an explicit list of fields is Defining an ``UpdateView`` or ``CreateView`` subclass to be used with a model
deprecated. but without an explicit list of fields is deprecated.
.. _m2m-help_text-deprecation: .. _m2m-help_text-deprecation:

View File

@ -47,7 +47,7 @@ but a few of the key features are:
* A new ``makemigrations`` command provides an easy way to autodetect changes * A new ``makemigrations`` command provides an easy way to autodetect changes
to your models and make migrations for them. to your models and make migrations for them.
* :data:`~django.db.models.signals.post_syncdb` and * :data:`~django.db.models.signals.pre_syncdb` and
:data:`~django.db.models.signals.post_syncdb` have been renamed to :data:`~django.db.models.signals.post_syncdb` have been renamed to
:data:`~django.db.models.signals.pre_migrate` and :data:`~django.db.models.signals.pre_migrate` and
:data:`~django.db.models.signals.post_migrate` respectively. The :data:`~django.db.models.signals.post_migrate` respectively. The
@ -285,6 +285,19 @@ Templates
* ``TypeError`` exceptions are not longer silenced when raised during the * ``TypeError`` exceptions are not longer silenced when raised during the
rendering of a template. rendering of a template.
Tests
^^^^^
* :class:`~django.test.runner.DiscoverRunner` has two new attributes,
:attr:`~django.test.runner.DiscoverRunner.test_suite` and
:attr:`~django.test.runner.DiscoverRunner.test_runner`, which facilitate
overriding the way tests are collected and run.
* The ``fetch_redirect_response`` argument was added to
:meth:`~django.test.SimpleTestCase.assertRedirects`. Since the test
client can't fetch externals URLs, this allows you to use ``assertRedirects``
with redirects that aren't part of your Django app.
Backwards incompatible changes in 1.7 Backwards incompatible changes in 1.7
===================================== =====================================
@ -308,6 +321,16 @@ For apps with migrations, ``allow_migrate`` will now get passed
without custom attributes, methods or managers. Make sure your ``allow_migrate`` without custom attributes, methods or managers. Make sure your ``allow_migrate``
methods are only referring to fields or other items in ``model._meta``. methods are only referring to fields or other items in ``model._meta``.
pytz may be required
~~~~~~~~~~~~~~~~~~~~
If your project handles datetimes before 1970 or after 2037 and Django raises
a :exc:`~exceptions.ValueError` when encountering them, you will have to
install pytz_. You may be affected by this problem if you use Django's time
zone-related date formats or :mod:`django.contrib.syndication`.
.. _pytz: https://pypi.python.org/pypi/pytz/
Miscellaneous Miscellaneous
~~~~~~~~~~~~~ ~~~~~~~~~~~~~
@ -345,13 +368,18 @@ Miscellaneous
when called on an instance without a primary key value. This is done to when called on an instance without a primary key value. This is done to
avoid mutable ``__hash__`` values in containers. avoid mutable ``__hash__`` values in containers.
* The :meth:`django.db.backends.sqlite3.DatabaseCreation.sql_create_model` * :class:`~django.db.models.AutoField` columns in SQLite databases will now be
will now create :class:`~django.db.models.AutoField` columns in SQLite created using the ``AUTOINCREMENT`` option, which guarantees monotonic
databases using the ``AUTOINCREMENT`` option, which guarantees monotonic
increments. This will cause primary key numbering behavior to change on increments. This will cause primary key numbering behavior to change on
SQLite, becoming consistent with most other SQL databases. If you have a SQLite, becoming consistent with most other SQL databases. This will only
database created with an older version of Django, you will need to migrate apply to newly created tables. If you have a database created with an older
it to take advantage of this feature. See ticket #10164 for details. version of Django, you will need to migrate it to take advantage of this
feature. For example, you could do the following:
#) Use :djadmin:`dumpdata` to save your data.
#) Rename the existing database file (keep it as a backup).
#) Run :djadmin:`migrate` to create the updated schema.
#) Use :djadmin:`loaddata` to import the fixtures you exported in (1).
* ``django.contrib.auth.models.AbstractUser`` no longer defines a * ``django.contrib.auth.models.AbstractUser`` no longer defines a
:meth:`~django.db.models.Model.get_absolute_url()` method. The old definition :meth:`~django.db.models.Model.get_absolute_url()` method. The old definition
@ -384,6 +412,15 @@ Features deprecated in 1.7
respectively :mod:`logging.config` and :mod:`importlib` provided for Python respectively :mod:`logging.config` and :mod:`importlib` provided for Python
versions prior to 2.7. They have been deprecated. versions prior to 2.7. They have been deprecated.
``django.utils.tzinfo``
~~~~~~~~~~~~~~~~~~~~~~~
``django.utils.tzinfo`` provided two :class:`~datetime.tzinfo` subclasses,
``LocalTimezone`` and ``FixedOffset``. They've been deprecated in favor of
more correct alternatives provided by :mod:`django.utils.timezone`,
:func:`django.utils.timezone.get_default_timezone` and
:func:`django.utils.timezone.get_fixed_timezone`.
``django.utils.unittest`` ``django.utils.unittest``
~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -36,6 +36,7 @@ Final releases
.. toctree:: .. toctree::
:maxdepth: 1 :maxdepth: 1
1.5.3
1.5.2 1.5.2
1.5.1 1.5.1
1.5 1.5
@ -45,6 +46,7 @@ Final releases
.. toctree:: .. toctree::
:maxdepth: 1 :maxdepth: 1
1.4.7
1.4.6 1.4.6
1.4.5 1.4.5
1.4.4 1.4.4

View File

@ -869,8 +869,7 @@ would test three possible User models -- the default, plus the two User
models provided by ``auth`` app:: models provided by ``auth`` app::
from django.contrib.auth.tests.utils import skipIfCustomUser from django.contrib.auth.tests.utils import skipIfCustomUser
from django.test import TestCase from django.test import TestCase, override_settings
from django.test.utils import override_settings
class ApplicationTestCase(TestCase): class ApplicationTestCase(TestCase):

View File

@ -341,40 +341,37 @@ been well-tested and are easy to use.
Cache arguments Cache arguments
--------------- ---------------
In addition to the defining the engine and name of the each cache Each cache backend can be given additional arguments to control caching
backend, each cache backend can be given additional arguments to behavior. These arguments are provided as additional keys in the
control caching behavior. These arguments are provided as additional :setting:`CACHES` setting. Valid arguments are as follows:
keys in the :setting:`CACHES` setting. Valid arguments are as follows:
* :setting:`TIMEOUT <CACHES-TIMEOUT>`: The default timeout, in * :setting:`TIMEOUT <CACHES-TIMEOUT>`: The default timeout, in
seconds, to use for the cache. This argument defaults to 300 seconds, to use for the cache. This argument defaults to ``300``
seconds (5 minutes). seconds (5 minutes).
* :setting:`OPTIONS <CACHES-OPTIONS>`: Any options that should be * :setting:`OPTIONS <CACHES-OPTIONS>`: Any options that should be
passed to cache backend. The list options understood by each passed to the cache backend. The list of valid options will vary
backend vary with each backend. with each backend, and cache backends backed by a third-party library
will pass their options directly to the underlying cache library.
Cache backends that implement their own culling strategy (i.e., Cache backends that implement their own culling strategy (i.e.,
the ``locmem``, ``filesystem`` and ``database`` backends) will the ``locmem``, ``filesystem`` and ``database`` backends) will
honor the following options: honor the following options:
* ``MAX_ENTRIES``: the maximum number of entries allowed in * ``MAX_ENTRIES``: The maximum number of entries allowed in
the cache before old values are deleted. This argument the cache before old values are deleted. This argument
defaults to ``300``. defaults to ``300``.
* ``CULL_FREQUENCY``: The fraction of entries that are culled * ``CULL_FREQUENCY``: The fraction of entries that are culled
when ``MAX_ENTRIES`` is reached. The actual ratio is when ``MAX_ENTRIES`` is reached. The actual ratio is
``1/CULL_FREQUENCY``, so set ``CULL_FREQUENCY``: to ``2`` to ``1 / CULL_FREQUENCY``, so set ``CULL_FREQUENCY`` to ``2`` to
cull half of the entries when ``MAX_ENTRIES`` is reached. cull half the entries when ``MAX_ENTRIES`` is reached. This argument
should be an integer and defaults to ``3``.
A value of ``0`` for ``CULL_FREQUENCY`` means that the A value of ``0`` for ``CULL_FREQUENCY`` means that the
entire cache will be dumped when ``MAX_ENTRIES`` is reached. entire cache will be dumped when ``MAX_ENTRIES`` is reached.
This makes culling *much* faster at the expense of more On some backends (``database`` in particular) this makes culling *much*
cache misses. faster at the expense of more cache misses.
Cache backends backed by a third-party library will pass their
options directly to the underlying cache library. As a result,
the list of valid options depends on the library in use.
* :setting:`KEY_PREFIX <CACHES-KEY_PREFIX>`: A string that will be * :setting:`KEY_PREFIX <CACHES-KEY_PREFIX>`: A string that will be
automatically included (prepended by default) to all cache keys automatically included (prepended by default) to all cache keys
@ -1176,7 +1173,10 @@ site's performance:
and ``Last-Modified`` headers. and ``Last-Modified`` headers.
* :class:`django.middleware.gzip.GZipMiddleware` compresses responses for all * :class:`django.middleware.gzip.GZipMiddleware` compresses responses for all
modern browsers, saving bandwidth and transfer time. modern browsers, saving bandwidth and transfer time. Be warned, however,
that compression techniques like ``GZipMiddleware`` are subject to attacks.
See the warning in :class:`~django.middleware.gzip.GZipMiddleware` for
details.
Order of MIDDLEWARE_CLASSES Order of MIDDLEWARE_CLASSES
=========================== ===========================

View File

@ -306,6 +306,17 @@ instead of::
entry.blog.id entry.blog.id
Don't order results if you don't care
-------------------------------------
Ordering is not free; each field to order by is an operation the database must
perform. If a model has a default ordering (:attr:`Meta.ordering
<django.db.models.Options.ordering>`) and you don't need it, remove
it on a ``QuerySet`` by calling
:meth:`~django.db.models.query.QuerySet.order_by()` with no parameters.
Adding an index to your database may help to improve ordering performance.
Insert in bulk Insert in bulk
============== ==============

View File

@ -5,18 +5,18 @@ Advanced testing topics
The request factory The request factory
=================== ===================
.. module:: django.test.client .. currentmodule:: django.test
.. class:: RequestFactory .. class:: RequestFactory
The :class:`~django.test.client.RequestFactory` shares the same API as The :class:`~django.test.RequestFactory` shares the same API as
the test client. However, instead of behaving like a browser, the the test client. However, instead of behaving like a browser, the
RequestFactory provides a way to generate a request instance that can RequestFactory provides a way to generate a request instance that can
be used as the first argument to any view. This means you can test a be used as the first argument to any view. This means you can test a
view function the same way as you would test any other function -- as view function the same way as you would test any other function -- as
a black box, with exactly known inputs, testing for specific outputs. a black box, with exactly known inputs, testing for specific outputs.
The API for the :class:`~django.test.client.RequestFactory` is a slightly The API for the :class:`~django.test.RequestFactory` is a slightly
restricted subset of the test client API: restricted subset of the test client API:
* It only has access to the HTTP methods :meth:`~Client.get()`, * It only has access to the HTTP methods :meth:`~Client.get()`,
@ -38,8 +38,7 @@ Example
The following is a simple unit test using the request factory:: The following is a simple unit test using the request factory::
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.test import TestCase from django.test import TestCase, RequestFactory
from django.test.client import RequestFactory
class SimpleTest(TestCase): class SimpleTest(TestCase):
def setUp(self): def setUp(self):
@ -165,8 +164,6 @@ exception will be raised.
Advanced features of ``TransactionTestCase`` Advanced features of ``TransactionTestCase``
============================================ ============================================
.. currentmodule:: django.test
.. attribute:: TransactionTestCase.available_apps .. attribute:: TransactionTestCase.available_apps
.. versionadded:: 1.6 .. versionadded:: 1.6
@ -341,6 +338,25 @@ execute and tear down the test suite.
Attributes Attributes
~~~~~~~~~~ ~~~~~~~~~~
.. attribute:: DiscoverRunner.test_suite
.. versionadded:: 1.7
The class used to build the test suite. By default it is set to
``unittest.TestSuite``. This can be overridden if you wish to implement
different logic for collecting tests.
.. attribute:: DiscoverRunner.test_runner
.. versionadded:: 1.7
This is the class of the low-level test runner which is used to execute
the individual tests and format the results. By default it is set to
``unittest.TextTestRunner``. Despite the unfortunate similarity in
naming conventions, this is not the same type of class as
``DiscoverRunner``, which covers a broader set of responsibilites. You
can override this attribute to modify the way tests are run and reported.
.. attribute:: DiscoverRunner.test_loader .. attribute:: DiscoverRunner.test_loader
This is the class that loads tests, whether from TestCases or modules or This is the class that loads tests, whether from TestCases or modules or

View File

@ -313,9 +313,6 @@ Django provides a small set of tools that come in handy when writing tests.
The test client The test client
--------------- ---------------
.. module:: django.test.client
:synopsis: Django's test client.
The test client is a Python class that acts as a dummy Web browser, allowing The test client is a Python class that acts as a dummy Web browser, allowing
you to test your views and interact with your Django-powered application you to test your views and interact with your Django-powered application
programmatically. programmatically.
@ -349,10 +346,10 @@ A comprehensive test suite should use a combination of both test types.
Overview and a quick example Overview and a quick example
~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To use the test client, instantiate ``django.test.client.Client`` and retrieve To use the test client, instantiate ``django.test.Client`` and retrieve
Web pages:: Web pages::
>>> from django.test.client import Client >>> from django.test import Client
>>> c = Client() >>> c = Client()
>>> response = c.post('/login/', {'username': 'john', 'password': 'smith'}) >>> response = c.post('/login/', {'username': 'john', 'password': 'smith'})
>>> response.status_code >>> response.status_code
@ -413,7 +410,7 @@ Note a few important things about how the test client works:
Making requests Making requests
~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~
Use the ``django.test.client.Client`` class to make requests. Use the ``django.test.Client`` class to make requests.
.. class:: Client(enforce_csrf_checks=False, **defaults) .. class:: Client(enforce_csrf_checks=False, **defaults)
@ -424,8 +421,8 @@ Use the ``django.test.client.Client`` class to make requests.
>>> c = Client(HTTP_USER_AGENT='Mozilla/5.0') >>> c = Client(HTTP_USER_AGENT='Mozilla/5.0')
The values from the ``extra`` keywords arguments passed to The values from the ``extra`` keywords arguments passed to
:meth:`~django.test.client.Client.get()`, :meth:`~django.test.Client.get()`,
:meth:`~django.test.client.Client.post()`, etc. have precedence over :meth:`~django.test.Client.post()`, etc. have precedence over
the defaults passed to the class constructor. the defaults passed to the class constructor.
The ``enforce_csrf_checks`` argument can be used to test CSRF The ``enforce_csrf_checks`` argument can be used to test CSRF
@ -778,7 +775,7 @@ Example
The following is a simple unit test using the test client:: The following is a simple unit test using the test client::
import unittest import unittest
from django.test.client import Client from django.test import Client
class SimpleTest(unittest.TestCase): class SimpleTest(unittest.TestCase):
def setUp(self): def setUp(self):
@ -797,15 +794,13 @@ The following is a simple unit test using the test client::
.. seealso:: .. seealso::
:class:`django.test.client.RequestFactory` :class:`django.test.RequestFactory`
.. _django-testcase-subclasses: .. _django-testcase-subclasses:
Provided test case classes Provided test case classes
-------------------------- --------------------------
.. currentmodule:: django.test
Normal Python unit test classes extend a base class of Normal Python unit test classes extend a base class of
:class:`unittest.TestCase`. Django provides a few extensions of this base class: :class:`unittest.TestCase`. Django provides a few extensions of this base class:
@ -847,7 +842,7 @@ functionality like:
for equality. for equality.
* The ability to run tests with :ref:`modified settings <overriding-settings>`. * The ability to run tests with :ref:`modified settings <overriding-settings>`.
* Using the :attr:`~SimpleTestCase.client` :class:`~django.test.client.Client`. * Using the :attr:`~SimpleTestCase.client` :class:`~django.test.Client`.
* Custom test-time :attr:`URL maps <SimpleTestCase.urls>`. * Custom test-time :attr:`URL maps <SimpleTestCase.urls>`.
.. versionchanged:: 1.6 .. versionchanged:: 1.6
@ -1111,7 +1106,7 @@ worry about state (such as cookies) carrying over from one test to another.
This means, instead of instantiating a ``Client`` in each test:: This means, instead of instantiating a ``Client`` in each test::
import unittest import unittest
from django.test.client import Client from django.test import Client
class SimpleTest(unittest.TestCase): class SimpleTest(unittest.TestCase):
def test_details(self): def test_details(self):
@ -1146,8 +1141,7 @@ If you want to use a different ``Client`` class (for example, a subclass
with customized behavior), use the :attr:`~SimpleTestCase.client_class` class with customized behavior), use the :attr:`~SimpleTestCase.client_class` class
attribute:: attribute::
from django.test import TestCase from django.test import TestCase, Client
from django.test.client import Client
class MyTestClient(Client): class MyTestClient(Client):
# Specialized methods for your environment... # Specialized methods for your environment...
@ -1330,17 +1324,14 @@ Django provides a standard Python context manager (see :pep:`343`)
This example will override the :setting:`LOGIN_URL` setting for the code This example will override the :setting:`LOGIN_URL` setting for the code
in the ``with`` block and reset its value to the previous state afterwards. in the ``with`` block and reset its value to the previous state afterwards.
.. currentmodule:: django.test.utils
.. function:: override_settings .. function:: override_settings
In case you want to override a setting for just one test method or even the In case you want to override a setting for just one test method or even the
whole :class:`~django.test.TestCase` class, Django provides the whole :class:`~django.test.TestCase` class, Django provides the
:func:`~django.test.utils.override_settings` decorator (see :pep:`318`). It's :func:`~django.test.override_settings` decorator (see :pep:`318`). It's
used like this:: used like this::
from django.test import TestCase from django.test import TestCase, override_settings
from django.test.utils import override_settings
class LoginTestCase(TestCase): class LoginTestCase(TestCase):
@ -1351,8 +1342,7 @@ used like this::
The decorator can also be applied to test case classes:: The decorator can also be applied to test case classes::
from django.test import TestCase from django.test import TestCase, override_settings
from django.test.utils import override_settings
@override_settings(LOGIN_URL='/other/login/') @override_settings(LOGIN_URL='/other/login/')
class LoginTestCase(TestCase): class LoginTestCase(TestCase):
@ -1361,6 +1351,11 @@ The decorator can also be applied to test case classes::
response = self.client.get('/sekrit/') response = self.client.get('/sekrit/')
self.assertRedirects(response, '/other/login/?next=/sekrit/') self.assertRedirects(response, '/other/login/?next=/sekrit/')
.. versionchanged:: 1.7
Previously, ``override_settings`` was imported from
``django.test.utils``.
.. note:: .. note::
When given a class, the decorator modifies the class directly and When given a class, the decorator modifies the class directly and
@ -1427,8 +1422,6 @@ For more detail on email services during tests, see `Email services`_ below.
Assertions Assertions
~~~~~~~~~~ ~~~~~~~~~~
.. currentmodule:: django.test
As Python's normal :class:`unittest.TestCase` class implements assertion methods As Python's normal :class:`unittest.TestCase` class implements assertion methods
such as :meth:`~unittest.TestCase.assertTrue` and such as :meth:`~unittest.TestCase.assertTrue` and
:meth:`~unittest.TestCase.assertEqual`, Django's custom :class:`TestCase` class :meth:`~unittest.TestCase.assertEqual`, Django's custom :class:`TestCase` class
@ -1549,7 +1542,7 @@ your test suite.
You can use this as a context manager in the same way as You can use this as a context manager in the same way as
:meth:`~SimpleTestCase.assertTemplateUsed`. :meth:`~SimpleTestCase.assertTemplateUsed`.
.. method:: SimpleTestCase.assertRedirects(response, expected_url, status_code=302, target_status_code=200, msg_prefix='') .. method:: SimpleTestCase.assertRedirects(response, expected_url, status_code=302, target_status_code=200, msg_prefix='', fetch_redirect_response=True)
Asserts that the response return a ``status_code`` redirect status, it Asserts that the response return a ``status_code`` redirect status, it
redirected to ``expected_url`` (including any GET data), and the final redirected to ``expected_url`` (including any GET data), and the final
@ -1559,6 +1552,12 @@ your test suite.
``target_status_code`` will be the url and status code for the final ``target_status_code`` will be the url and status code for the final
point of the redirect chain. point of the redirect chain.
.. versionadded:: 1.7
If ``fetch_redirect_response`` is ``False``, the final page won't be
loaded. Since the test client can't fetch externals URLs, this is
particularly useful if ``expected_url`` isn't part of your Django app.
.. method:: SimpleTestCase.assertHTMLEqual(html1, html2, msg=None) .. method:: SimpleTestCase.assertHTMLEqual(html1, html2, msg=None)
Asserts that the strings ``html1`` and ``html2`` are equal. The comparison Asserts that the strings ``html1`` and ``html2`` are equal. The comparison

View File

@ -1 +0,0 @@
#

View File

@ -9,7 +9,7 @@ import unittest
from django.conf import settings, global_settings from django.conf import settings, global_settings
from django.core import mail from django.core import mail
from django.core.files import temp as tempfile from django.core.files import temp as tempfile
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse, NoReverseMatch
# Register auth models with the admin. # Register auth models with the admin.
from django.contrib.auth import get_permission_codename from django.contrib.auth import get_permission_codename
from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
@ -640,6 +640,20 @@ class AdminViewBasicTest(AdminViewBasicTestCase):
# Check the format of the shown object -- shouldn't contain a change link # Check the format of the shown object -- shouldn't contain a change link
self.assertContains(response, '<th class="field-__str__">UnchangeableObject object</th>', html=True) self.assertContains(response, '<th class="field-__str__">UnchangeableObject object</th>', html=True)
def test_invalid_appindex_url(self):
"""
#21056 -- URL reversing shouldn't work for nonexistent apps.
"""
good_url = '/test_admin/admin/admin_views/'
confirm_good_url = reverse('admin:app_list',
kwargs={'app_label': 'admin_views'})
self.assertEqual(good_url, confirm_good_url)
with self.assertRaises(NoReverseMatch):
reverse('admin:app_list', kwargs={'app_label': 'this_should_fail'})
with self.assertRaises(NoReverseMatch):
reverse('admin:app_list', args=('admin_views2',))
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class AdminViewFormUrlTest(TestCase): class AdminViewFormUrlTest(TestCase):
@ -1296,6 +1310,40 @@ class AdminViewPermissionsTest(TestCase):
response = self.client.get('/test_admin/admin/secure-view/') response = self.client.get('/test_admin/admin/secure-view/')
self.assertContains(response, 'id="login-form"') self.assertContains(response, 'id="login-form"')
def testDisabledStaffPermissionsWhenLoggedIn(self):
self.client.login(username='super', password='secret')
superuser = User.objects.get(username='super')
superuser.is_staff = False
superuser.save()
response = self.client.get('/test_admin/admin/')
self.assertContains(response, 'id="login-form"')
self.assertNotContains(response, 'Log out')
response = self.client.get('/test_admin/admin/secure-view/')
self.assertContains(response, 'id="login-form"')
def testAppIndexFailEarly(self):
"""
If a user has no module perms, avoid iterating over all the modeladmins
in the registry.
"""
opts = Article._meta
change_user = User.objects.get(username='changeuser')
permission = get_perm(Article, get_permission_codename('change', opts))
self.client.post('/test_admin/admin/', self.changeuser_login)
# the user has no module permissions, because this module doesn't exist
change_user.user_permissions.remove(permission)
response = self.client.get('/test_admin/admin/admin_views/')
self.assertEqual(response.status_code, 403)
# the user now has module permissions
change_user.user_permissions.add(permission)
response = self.client.get('/test_admin/admin/admin_views/')
self.assertEqual(response.status_code, 200)
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class AdminViewsNoUrlTest(TestCase): class AdminViewsNoUrlTest(TestCase):

View File

@ -549,6 +549,79 @@ class DateTimePickerSeleniumFirefoxTests(AdminSeleniumWebDriverTestCase):
self.assertEqual( self.assertEqual(
self.get_css_value('#clockbox0', 'display'), 'none') self.get_css_value('#clockbox0', 'display'), 'none')
def test_calendar_nonday_class(self):
"""
Ensure cells that are not days of the month have the `nonday` CSS class.
Refs #4574.
"""
self.admin_login(username='super', password='secret', login_url='/')
# Open a page that has a date and time picker widgets
self.selenium.get('%s%s' % (self.live_server_url,
'/admin_widgets/member/add/'))
# fill in the birth date.
self.selenium.find_element_by_id('id_birthdate_0').send_keys('2013-06-01')
# Click the calendar icon
self.selenium.find_element_by_id('calendarlink0').click()
# get all the tds within the calendar
calendar0 = self.selenium.find_element_by_id('calendarin0')
tds = calendar0.find_elements_by_tag_name('td')
# make sure the first and last 6 cells have class nonday
for td in tds[:6] + tds[-6:]:
self.assertEqual(td.get_attribute('class'), 'nonday')
def test_calendar_selected_class(self):
"""
Ensure cell for the day in the input has the `selected` CSS class.
Refs #4574.
"""
self.admin_login(username='super', password='secret', login_url='/')
# Open a page that has a date and time picker widgets
self.selenium.get('%s%s' % (self.live_server_url,
'/admin_widgets/member/add/'))
# fill in the birth date.
self.selenium.find_element_by_id('id_birthdate_0').send_keys('2013-06-01')
# Click the calendar icon
self.selenium.find_element_by_id('calendarlink0').click()
# get all the tds within the calendar
calendar0 = self.selenium.find_element_by_id('calendarin0')
tds = calendar0.find_elements_by_tag_name('td')
# verify the selected cell
selected = tds[6]
self.assertEqual(selected.get_attribute('class'), 'selected')
self.assertEqual(selected.text, '1')
def test_calendar_no_selected_class(self):
"""
Ensure no cells are given the selected class when the field is empty.
Refs #4574.
"""
self.admin_login(username='super', password='secret', login_url='/')
# Open a page that has a date and time picker widgets
self.selenium.get('%s%s' % (self.live_server_url,
'/admin_widgets/member/add/'))
# Click the calendar icon
self.selenium.find_element_by_id('calendarlink0').click()
# get all the tds within the calendar
calendar0 = self.selenium.find_element_by_id('calendarin0')
tds = calendar0.find_elements_by_tag_name('td')
# verify there are no cells with the selected class
selected = [td for td in tds if td.get_attribute('class') == 'selected']
self.assertEqual(len(selected), 0)
class DateTimePickerSeleniumChromeTests(DateTimePickerSeleniumFirefoxTests): class DateTimePickerSeleniumChromeTests(DateTimePickerSeleniumFirefoxTests):
webdriver_class = 'selenium.webdriver.chrome.webdriver.WebDriver' webdriver_class = 'selenium.webdriver.chrome.webdriver.WebDriver'

View File

@ -6,7 +6,8 @@ import re
from django.db import connection from django.db import connection
from django.db.models import Avg, Sum, Count, Max, Min from django.db.models import Avg, Sum, Count, Max, Min
from django.test import TestCase, Approximate from django.test import TestCase
from django.test.utils import Approximate
from django.test.utils import CaptureQueriesContext from django.test.utils import CaptureQueriesContext
from .models import Author, Publisher, Book, Store from .models import Author, Publisher, Book, Store

View File

@ -8,7 +8,8 @@ from operator import attrgetter
from django.core.exceptions import FieldError from django.core.exceptions import FieldError
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.db.models import Count, Max, Avg, Sum, StdDev, Variance, F, Q from django.db.models import Count, Max, Avg, Sum, StdDev, Variance, F, Q
from django.test import TestCase, Approximate, skipUnlessDBFeature from django.test import TestCase, skipUnlessDBFeature
from django.test.utils import Approximate
from django.utils import six from django.utils import six
from .models import (Author, Book, Publisher, Clues, Entries, HardbackBook, from .models import (Author, Book, Publisher, Clues, Entries, HardbackBook,

View File

@ -1 +0,0 @@

View File

@ -83,11 +83,3 @@ class CommentTestCase(TestCase):
d.update(f.initial) d.update(f.initial)
return d return d
from comment_tests.tests.test_app_api import *
from comment_tests.tests.test_feeds import *
from comment_tests.tests.test_models import *
from comment_tests.tests.test_comment_form import *
from comment_tests.tests.test_templatetags import *
from comment_tests.tests.test_comment_view import *
from comment_tests.tests.test_comment_utils_moderators import *
from comment_tests.tests.test_moderation_views import *

Some files were not shown because too many files have changed in this diff Show More