diff --git a/AUTHORS b/AUTHORS index 5fa957885c..0a3699d516 100644 --- a/AUTHORS +++ b/AUTHORS @@ -506,6 +506,7 @@ answer newbie questions, and generally made Django that much better: Johan C. Stöver Nowell Strite Thomas Stromberg + Travis Swicegood Pascal Varet SuperJared Radek Švarz diff --git a/django/conf/__init__.py b/django/conf/__init__.py index f4d17ca9f3..6272f4ed5d 100644 --- a/django/conf/__init__.py +++ b/django/conf/__init__.py @@ -7,7 +7,6 @@ a list of all possible variables. """ import os -import re import time # Needed for Windows import warnings @@ -26,7 +25,7 @@ class LazySettings(LazyObject): The user can manually configure settings prior to using them. Otherwise, Django uses the settings module pointed to by DJANGO_SETTINGS_MODULE. """ - def _setup(self): + def _setup(self, name): """ Load the settings module pointed to by the environment variable. This is used the first time we need any settings at all, if the user has not @@ -37,12 +36,21 @@ class LazySettings(LazyObject): if not settings_module: # If it's set but is an empty string. raise KeyError except KeyError: - # NOTE: This is arguably an EnvironmentError, but that causes - # problems with Python's interactive help. - raise ImportError("Settings cannot be imported, because environment variable %s is undefined." % ENVIRONMENT_VARIABLE) + raise ImproperlyConfigured( + "Requested setting %s, but settings are not configured. " + "You must either define the environment variable %s " + "or call settings.configure() before accessing settings." + % (name, ENVIRONMENT_VARIABLE)) self._wrapped = Settings(settings_module) + + def __getattr__(self, name): + if self._wrapped is empty: + self._setup(name) + return getattr(self._wrapped, name) + + def configure(self, default_settings=global_settings, **options): """ Called to manually configure the settings. The 'default_settings' diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 081d00121b..f4205f2ce7 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -14,9 +14,10 @@ from django.core.exceptions import PermissionDenied, ValidationError from django.core.paginator import Paginator from django.core.urlresolvers import reverse from django.db import models, transaction, router +from django.db.models.constants import LOOKUP_SEP from django.db.models.related import RelatedObject from django.db.models.fields import BLANK_CHOICE_DASH, FieldDoesNotExist -from django.db.models.sql.constants import LOOKUP_SEP, QUERY_TERMS +from django.db.models.sql.constants import QUERY_TERMS from django.http import Http404, HttpResponse, HttpResponseRedirect from django.shortcuts import get_object_or_404 from django.template.response import SimpleTemplateResponse, TemplateResponse @@ -1456,8 +1457,10 @@ class InlineModelAdmin(BaseModelAdmin): return request.user.has_perm( self.opts.app_label + '.' + self.opts.get_delete_permission()) + class StackedInline(InlineModelAdmin): template = 'admin/edit_inline/stacked.html' + class TabularInline(InlineModelAdmin): template = 'admin/edit_inline/tabular.html' diff --git a/django/contrib/admin/static/admin/js/inlines.js b/django/contrib/admin/static/admin/js/inlines.js index c11af82f76..4dc9459ff3 100644 --- a/django/contrib/admin/static/admin/js/inlines.js +++ b/django/contrib/admin/static/admin/js/inlines.js @@ -9,128 +9,264 @@ * All rights reserved. * * Spiced up with Code from Zain Memon's GSoC project 2009 - * and modified for Django by Jannis Leidel + * and modified for Django by Jannis Leidel, Travis Swicegood and Julien Phalip. * * Licensed under the New BSD License * See: http://www.opensource.org/licenses/bsd-license.php */ (function($) { - $.fn.formset = function(opts) { - var options = $.extend({}, $.fn.formset.defaults, opts); - var updateElementIndex = function(el, prefix, ndx) { - var id_regex = new RegExp("(" + prefix + "-(\\d+|__prefix__))"); - var replacement = prefix + "-" + ndx; - if ($(el).attr("for")) { - $(el).attr("for", $(el).attr("for").replace(id_regex, replacement)); - } - if (el.id) { - el.id = el.id.replace(id_regex, replacement); - } - if (el.name) { - el.name = el.name.replace(id_regex, replacement); - } - }; - var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS").attr("autocomplete", "off"); - var nextIndex = parseInt(totalForms.val(), 10); - var maxForms = $("#id_" + options.prefix + "-MAX_NUM_FORMS").attr("autocomplete", "off"); - // only show the add button if we are allowed to add more items, + $.fn.formset = function(opts) { + var options = $.extend({}, $.fn.formset.defaults, opts); + var $this = $(this); + var $parent = $this.parent(); + var updateElementIndex = function(el, prefix, ndx) { + var id_regex = new RegExp("(" + prefix + "-(\\d+|__prefix__))"); + var replacement = prefix + "-" + ndx; + if ($(el).attr("for")) { + $(el).attr("for", $(el).attr("for").replace(id_regex, replacement)); + } + if (el.id) { + el.id = el.id.replace(id_regex, replacement); + } + if (el.name) { + el.name = el.name.replace(id_regex, replacement); + } + }; + var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS").attr("autocomplete", "off"); + var nextIndex = parseInt(totalForms.val(), 10); + var maxForms = $("#id_" + options.prefix + "-MAX_NUM_FORMS").attr("autocomplete", "off"); + // only show the add button if we are allowed to add more items, // note that max_num = None translates to a blank string. - var showAddButton = maxForms.val() === '' || (maxForms.val()-totalForms.val()) > 0; - $(this).each(function(i) { - $(this).not("." + options.emptyCssClass).addClass(options.formCssClass); - }); - if ($(this).length && showAddButton) { - var addButton; - if ($(this).attr("tagName") == "TR") { - // If forms are laid out as table rows, insert the - // "add" button in a new table row: - var numCols = this.eq(-1).children().length; - $(this).parent().append('' + options.addText + ""); - addButton = $(this).parent().find("tr:last a"); - } else { - // Otherwise, insert it immediately after the last form: - $(this).filter(":last").after('"); - addButton = $(this).filter(":last").next().find("a"); - } - addButton.click(function(e) { - e.preventDefault(); - var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS"); - var template = $("#" + options.prefix + "-empty"); - var row = template.clone(true); - row.removeClass(options.emptyCssClass) - .addClass(options.formCssClass) - .attr("id", options.prefix + "-" + nextIndex); - if (row.is("tr")) { - // If the forms are laid out in table rows, insert - // the remove button into the last table cell: - row.children(":last").append('"); - } else if (row.is("ul") || row.is("ol")) { - // If they're laid out as an ordered/unordered list, - // insert an
  • after the last list item: - row.append('
  • ' + options.deleteText + "
  • "); - } else { - // Otherwise, just insert the remove button as the - // last child element of the form's container: - row.children(":first").append('' + options.deleteText + ""); - } - row.find("*").each(function() { - updateElementIndex(this, options.prefix, totalForms.val()); - }); - // Insert the new form when it has been fully edited - row.insertBefore($(template)); - // Update number of total forms - $(totalForms).val(parseInt(totalForms.val(), 10) + 1); - nextIndex += 1; - // Hide add button in case we've hit the max, except we want to add infinitely - if ((maxForms.val() !== '') && (maxForms.val()-totalForms.val()) <= 0) { - addButton.parent().hide(); - } - // The delete button of each row triggers a bunch of other things - row.find("a." + options.deleteCssClass).click(function(e) { - e.preventDefault(); - // Remove the parent form containing this button: - var row = $(this).parents("." + options.formCssClass); - row.remove(); - nextIndex -= 1; - // If a post-delete callback was provided, call it with the deleted form: - if (options.removed) { - options.removed(row); - } - // Update the TOTAL_FORMS form count. - var forms = $("." + options.formCssClass); - $("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length); - // Show add button again once we drop below max - if ((maxForms.val() === '') || (maxForms.val()-forms.length) > 0) { - addButton.parent().show(); - } - // Also, update names and ids for all remaining form controls - // so they remain in sequence: - for (var i=0, formCount=forms.length; i 0; + $this.each(function(i) { + $(this).not("." + options.emptyCssClass).addClass(options.formCssClass); + }); + if ($this.length && showAddButton) { + var addButton; + if ($this.attr("tagName") == "TR") { + // If forms are laid out as table rows, insert the + // "add" button in a new table row: + var numCols = this.eq(-1).children().length; + $parent.append('' + options.addText + ""); + addButton = $parent.find("tr:last a"); + } else { + // Otherwise, insert it immediately after the last form: + $this.filter(":last").after('"); + addButton = $this.filter(":last").next().find("a"); + } + addButton.click(function(e) { + e.preventDefault(); + var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS"); + var template = $("#" + options.prefix + "-empty"); + var row = template.clone(true); + row.removeClass(options.emptyCssClass) + .addClass(options.formCssClass) + .attr("id", options.prefix + "-" + nextIndex); + if (row.is("tr")) { + // If the forms are laid out in table rows, insert + // the remove button into the last table cell: + row.children(":last").append('"); + } else if (row.is("ul") || row.is("ol")) { + // If they're laid out as an ordered/unordered list, + // insert an
  • after the last list item: + row.append('
  • ' + options.deleteText + "
  • "); + } else { + // Otherwise, just insert the remove button as the + // last child element of the form's container: + row.children(":first").append('' + options.deleteText + ""); + } + row.find("*").each(function() { + updateElementIndex(this, options.prefix, totalForms.val()); + }); + // Insert the new form when it has been fully edited + row.insertBefore($(template)); + // Update number of total forms + $(totalForms).val(parseInt(totalForms.val(), 10) + 1); + nextIndex += 1; + // Hide add button in case we've hit the max, except we want to add infinitely + if ((maxForms.val() !== '') && (maxForms.val()-totalForms.val()) <= 0) { + addButton.parent().hide(); + } + // The delete button of each row triggers a bunch of other things + row.find("a." + options.deleteCssClass).click(function(e) { + e.preventDefault(); + // Remove the parent form containing this button: + var row = $(this).parents("." + options.formCssClass); + row.remove(); + nextIndex -= 1; + // If a post-delete callback was provided, call it with the deleted form: + if (options.removed) { + options.removed(row); + } + // Update the TOTAL_FORMS form count. + var forms = $("." + options.formCssClass); + $("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length); + // Show add button again once we drop below max + if ((maxForms.val() === '') || (maxForms.val()-forms.length) > 0) { + addButton.parent().show(); + } + // Also, update names and ids for all remaining form controls + // so they remain in sequence: + for (var i=0, formCount=forms.length; i0;b(this).each(function(){b(this).not("."+ -a.emptyCssClass).addClass(a.formCssClass)});if(b(this).length&&g){var j;if(b(this).attr("tagName")=="TR"){g=this.eq(-1).children().length;b(this).parent().append(''+a.addText+"");j=b(this).parent().find("tr:last a")}else{b(this).filter(":last").after('");j=b(this).filter(":last").next().find("a")}j.click(function(c){c.preventDefault(); -var f=b("#id_"+a.prefix+"-TOTAL_FORMS");c=b("#"+a.prefix+"-empty");var e=c.clone(true);e.removeClass(a.emptyCssClass).addClass(a.formCssClass).attr("id",a.prefix+"-"+l);if(e.is("tr"))e.children(":last").append('");else e.is("ul")||e.is("ol")?e.append('
  • '+a.deleteText+"
  • "):e.children(":first").append(''+ -a.deleteText+"");e.find("*").each(function(){k(this,a.prefix,f.val())});e.insertBefore(b(c));b(f).val(parseInt(f.val(),10)+1);l+=1;h.val()!==""&&h.val()-f.val()<=0&&j.parent().hide();e.find("a."+a.deleteCssClass).click(function(d){d.preventDefault();d=b(this).parents("."+a.formCssClass);d.remove();l-=1;a.removed&&a.removed(d);d=b("."+a.formCssClass);b("#id_"+a.prefix+"-TOTAL_FORMS").val(d.length);if(h.val()===""||h.val()-d.length>0)j.parent().show();for(var i=0,m=d.length;i'+a.addText+""),h=d.find("tr:last a")):(c.filter(":last").after('"),h=c.filter(":last").next().find("a"));h.click(function(d){d.preventDefault();var f=b("#id_"+a.prefix+"-TOTAL_FORMS"),d=b("#"+a.prefix+ +"-empty"),c=d.clone(true);c.removeClass(a.emptyCssClass).addClass(a.formCssClass).attr("id",a.prefix+"-"+g);c.is("tr")?c.children(":last").append('"):c.is("ul")||c.is("ol")?c.append('
  • '+a.deleteText+"
  • "):c.children(":first").append(''+a.deleteText+"");c.find("*").each(function(){i(this, +a.prefix,f.val())});c.insertBefore(b(d));b(f).val(parseInt(f.val(),10)+1);g=g+1;e.val()!==""&&e.val()-f.val()<=0&&h.parent().hide();c.find("a."+a.deleteCssClass).click(function(d){d.preventDefault();d=b(this).parents("."+a.formCssClass);d.remove();g=g-1;a.removed&&a.removed(d);d=b("."+a.formCssClass);b("#id_"+a.prefix+"-TOTAL_FORMS").val(d.length);(e.val()===""||e.val()-d.length>0)&&h.parent().show();for(var c=0,f=d.length;c (function($) { - $(document).ready(function() { - var rows = "#{{ inline_admin_formset.formset.prefix }}-group .inline-related"; - var updateInlineLabel = function(row) { - $(rows).find(".inline_label").each(function(i) { - var count = i + 1; - $(this).html($(this).html().replace(/(#\d+)/g, "#" + count)); - }); - }; - var reinitDateTimeShortCuts = function() { - // Reinitialize the calendar and clock widgets by force, yuck. - if (typeof DateTimeShortcuts != "undefined") { - $(".datetimeshortcuts").remove(); - DateTimeShortcuts.init(); - } - }; - var updateSelectFilter = function() { - // If any SelectFilter widgets were added, instantiate a new instance. - if (typeof SelectFilter != "undefined"){ - $(".selectfilter").each(function(index, value){ - var namearr = value.name.split('-'); - SelectFilter.init(value.id, namearr[namearr.length-1], false, "{% static "admin/" %}"); - }); - $(".selectfilterstacked").each(function(index, value){ - var namearr = value.name.split('-'); - SelectFilter.init(value.id, namearr[namearr.length-1], true, "{% static "admin/" %}"); - }); - } - }; - var initPrepopulatedFields = function(row) { - row.find('.prepopulated_field').each(function() { - var field = $(this); - var input = field.find('input, select, textarea'); - var dependency_list = input.data('dependency_list') || []; - var dependencies = []; - $.each(dependency_list, function(i, field_name) { - dependencies.push('#' + row.find('.form-row .field-' + field_name).find('input, select, textarea').attr('id')); - }); - if (dependencies.length) { - input.prepopulate(dependencies, input.attr('maxlength')); - } - }); - }; - $(rows).formset({ - prefix: "{{ inline_admin_formset.formset.prefix }}", - addText: "{% blocktrans with verbose_name=inline_admin_formset.opts.verbose_name|title %}Add another {{ verbose_name }}{% endblocktrans %}", - formCssClass: "dynamic-{{ inline_admin_formset.formset.prefix }}", - deleteCssClass: "inline-deletelink", - deleteText: "{% trans "Remove" %}", - emptyCssClass: "empty-form", - removed: updateInlineLabel, - added: (function(row) { - initPrepopulatedFields(row); - reinitDateTimeShortCuts(); - updateSelectFilter(); - updateInlineLabel(row); - }) - }); - }); + $("#{{ inline_admin_formset.formset.prefix }}-group .inline-related").stackedFormset({ + prefix: '{{ inline_admin_formset.formset.prefix }}', + adminStaticPrefix: '{% static "admin/" %}', + deleteText: "{% trans "Remove" %}", + addText: "{% blocktrans with verbose_name=inline_admin_formset.opts.verbose_name|title %}Add another {{ verbose_name }}{% endblocktrans %}" + }); })(django.jQuery); diff --git a/django/contrib/admin/templates/admin/edit_inline/tabular.html b/django/contrib/admin/templates/admin/edit_inline/tabular.html index 4f49153819..f2757ede48 100644 --- a/django/contrib/admin/templates/admin/edit_inline/tabular.html +++ b/django/contrib/admin/templates/admin/edit_inline/tabular.html @@ -67,64 +67,13 @@ diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py index 1873d44989..ce435dea81 100644 --- a/django/contrib/admin/templatetags/admin_list.py +++ b/django/contrib/admin/templatetags/admin_list.py @@ -182,7 +182,7 @@ def items_for_result(cl, result, form): row_class = '' try: f, attr, value = lookup_field(field_name, result, cl.model_admin) - except (AttributeError, ObjectDoesNotExist): + except ObjectDoesNotExist: result_repr = EMPTY_CHANGELIST_VALUE else: if f is None: diff --git a/django/contrib/admin/util.py b/django/contrib/admin/util.py index 889f692ac3..f95fe53de1 100644 --- a/django/contrib/admin/util.py +++ b/django/contrib/admin/util.py @@ -4,7 +4,7 @@ import datetime import decimal from django.db import models -from django.db.models.sql.constants import LOOKUP_SEP +from django.db.models.constants import LOOKUP_SEP from django.db.models.deletion import Collector from django.db.models.related import RelatedObject from django.forms.forms import pretty_name diff --git a/django/contrib/auth/decorators.py b/django/contrib/auth/decorators.py index beeb284998..0fc9f3754a 100644 --- a/django/contrib/auth/decorators.py +++ b/django/contrib/auth/decorators.py @@ -8,6 +8,7 @@ from django.contrib.auth import REDIRECT_FIELD_NAME from django.core.exceptions import PermissionDenied from django.utils.decorators import available_attrs from django.utils.encoding import force_str +from django.shortcuts import resolve_url def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME): @@ -23,11 +24,10 @@ def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIE if test_func(request.user): return view_func(request, *args, **kwargs) path = request.build_absolute_uri() - # urlparse chokes on lazy objects in Python 3 - login_url_as_str = force_str(login_url or settings.LOGIN_URL) + resolved_login_url = resolve_url(login_url or settings.LOGIN_URL) # If the login url is the same scheme and net location then just # use the path as the "next" url. - login_scheme, login_netloc = urlparse(login_url_as_str)[:2] + login_scheme, login_netloc = urlparse(resolved_login_url)[:2] current_scheme, current_netloc = urlparse(path)[:2] if ((not login_scheme or login_scheme == current_scheme) and (not login_netloc or login_netloc == current_netloc)): diff --git a/django/contrib/auth/tests/decorators.py b/django/contrib/auth/tests/decorators.py index 2ef043fdbc..e75baa251e 100644 --- a/django/contrib/auth/tests/decorators.py +++ b/django/contrib/auth/tests/decorators.py @@ -28,7 +28,7 @@ class LoginRequiredTestCase(AuthViewsTestCase): pass login_required(normal_view) - def testLoginRequired(self, view_url='/login_required/', login_url=settings.LOGIN_URL): + def testLoginRequired(self, view_url='/login_required/', login_url='/login/'): """ Check that login_required works on a simple view wrapped in a login_required decorator. diff --git a/django/contrib/auth/tests/models.py b/django/contrib/auth/tests/models.py index f0e6c0368d..7595dbce34 100644 --- a/django/contrib/auth/tests/models.py +++ b/django/contrib/auth/tests/models.py @@ -1,8 +1,9 @@ from django.conf import settings +from django.contrib.auth.models import (Group, User, SiteProfileNotAvailable, + UserManager) from django.test import TestCase, skipIfCustomUser from django.test.utils import override_settings -from django.contrib.auth.models import (Group, User, - SiteProfileNotAvailable, UserManager) +from django.utils import six @skipIfCustomUser @@ -14,19 +15,19 @@ class ProfileTestCase(TestCase): # calling get_profile without AUTH_PROFILE_MODULE set del settings.AUTH_PROFILE_MODULE - with self.assertRaisesRegexp(SiteProfileNotAvailable, + with six.assertRaisesRegex(self, SiteProfileNotAvailable, "You need to set AUTH_PROFILE_MODULE in your project"): user.get_profile() # Bad syntax in AUTH_PROFILE_MODULE: settings.AUTH_PROFILE_MODULE = 'foobar' - with self.assertRaisesRegexp(SiteProfileNotAvailable, + with six.assertRaisesRegex(self, SiteProfileNotAvailable, "app_label and model_name should be separated by a dot"): user.get_profile() # module that doesn't exist settings.AUTH_PROFILE_MODULE = 'foo.bar' - with self.assertRaisesRegexp(SiteProfileNotAvailable, + with six.assertRaisesRegex(self, SiteProfileNotAvailable, "Unable to load the profile model"): user.get_profile() diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py index 0e79213934..f75f0ef7e4 100644 --- a/django/contrib/auth/views.py +++ b/django/contrib/auth/views.py @@ -7,9 +7,9 @@ from django.conf import settings from django.core.urlresolvers import reverse from django.http import HttpResponseRedirect, QueryDict from django.template.response import TemplateResponse -from django.utils.encoding import force_str from django.utils.http import base36_to_int from django.utils.translation import ugettext as _ +from django.shortcuts import resolve_url from django.views.decorators.debug import sensitive_post_parameters from django.views.decorators.cache import never_cache from django.views.decorators.csrf import csrf_protect @@ -38,16 +38,16 @@ def login(request, template_name='registration/login.html', if request.method == "POST": form = authentication_form(data=request.POST) if form.is_valid(): - netloc = urlparse(redirect_to)[1] - # Use default setting if redirect_to is empty if not redirect_to: redirect_to = settings.LOGIN_REDIRECT_URL + redirect_to = resolve_url(redirect_to) + netloc = urlparse(redirect_to)[1] # Heavier security check -- don't allow redirection to a different # host. - elif netloc and netloc != request.get_host(): - redirect_to = settings.LOGIN_REDIRECT_URL + if netloc and netloc != request.get_host(): + redirect_to = resolve_url(settings.LOGIN_REDIRECT_URL) # Okay, security checks complete. Log the user in. auth_login(request, form.get_user()) @@ -112,6 +112,7 @@ def logout_then_login(request, login_url=None, current_app=None, extra_context=N """ if not login_url: login_url = settings.LOGIN_URL + login_url = resolve_url(login_url) return logout(request, login_url, current_app=current_app, extra_context=extra_context) @@ -120,10 +121,9 @@ def redirect_to_login(next, login_url=None, """ Redirects the user to the login page, passing the given 'next' page """ - # urlparse chokes on lazy objects in Python 3 - login_url_as_str = force_str(login_url or settings.LOGIN_URL) + resolved_url = resolve_url(login_url or settings.LOGIN_URL) - login_url_parts = list(urlparse(login_url_as_str)) + login_url_parts = list(urlparse(resolved_url)) if redirect_field_name: querystring = QueryDict(login_url_parts[4], mutable=True) querystring[redirect_field_name] = next @@ -236,7 +236,7 @@ def password_reset_complete(request, template_name='registration/password_reset_complete.html', current_app=None, extra_context=None): context = { - 'login_url': settings.LOGIN_URL + 'login_url': resolve_url(settings.LOGIN_URL) } if extra_context is not None: context.update(extra_context) diff --git a/django/contrib/gis/db/models/sql/where.py b/django/contrib/gis/db/models/sql/where.py index 0e152221ac..ec078aebed 100644 --- a/django/contrib/gis/db/models/sql/where.py +++ b/django/contrib/gis/db/models/sql/where.py @@ -1,5 +1,5 @@ +from django.db.models.constants import LOOKUP_SEP from django.db.models.fields import FieldDoesNotExist -from django.db.models.sql.constants import LOOKUP_SEP from django.db.models.sql.expressions import SQLEvaluator from django.db.models.sql.where import Constraint, WhereNode from django.contrib.gis.db.models.fields import GeometryField diff --git a/django/contrib/localflavor/hr/forms.py b/django/contrib/localflavor/hr/forms.py index b935fd8a3a..083b61c49e 100644 --- a/django/contrib/localflavor/hr/forms.py +++ b/django/contrib/localflavor/hr/forms.py @@ -4,6 +4,7 @@ HR-specific Form helpers """ from __future__ import absolute_import, unicode_literals +import datetime import re from django.contrib.localflavor.hr.hr_choices import ( @@ -91,17 +92,16 @@ class HRJMBGField(Field): dd = int(matches.group('dd')) mm = int(matches.group('mm')) yyy = int(matches.group('yyy')) - import datetime try: - datetime.date(yyy,mm,dd) - except: + datetime.date(yyy, mm, dd) + except ValueError: raise ValidationError(self.error_messages['date']) # Validate checksum. k = matches.group('k') checksum = 0 - for i,j in zip(range(7,1,-1),range(6)): - checksum+=i*(int(value[j])+int(value[13-i])) + for i, j in zip(range(7, 1, -1), range(6)): + checksum += i * (int(value[j]) + int(value[13 - i])) m = 11 - checksum % 11 if m == 10: raise ValidationError(self.error_messages['invalid']) diff --git a/django/contrib/localflavor/ro/forms.py b/django/contrib/localflavor/ro/forms.py index bdbed5c476..f6de1534c9 100644 --- a/django/contrib/localflavor/ro/forms.py +++ b/django/contrib/localflavor/ro/forms.py @@ -4,6 +4,8 @@ Romanian specific form helpers. """ from __future__ import absolute_import, unicode_literals +import datetime + from django.contrib.localflavor.ro.ro_counties import COUNTIES_CHOICES from django.core.validators import EMPTY_VALUES from django.forms import ValidationError, Field, RegexField, Select @@ -69,10 +71,9 @@ class ROCNPField(RegexField): if value in EMPTY_VALUES: return '' # check birthdate digits - import datetime try: - datetime.date(int(value[1:3]),int(value[3:5]),int(value[5:7])) - except: + datetime.date(int(value[1:3]), int(value[3:5]), int(value[5:7])) + except ValueError: raise ValidationError(self.error_messages['invalid']) # checksum key = '279146358279' @@ -118,7 +119,7 @@ class ROCountyField(Field): # search for county name normalized_CC = [] for entry in COUNTIES_CHOICES: - normalized_CC.append((entry[0],entry[1].upper())) + normalized_CC.append((entry[0], entry[1].upper())) for entry in normalized_CC: if entry[1] == value: return entry[0] @@ -153,8 +154,8 @@ class ROIBANField(RegexField): value = super(ROIBANField, self).clean(value) if value in EMPTY_VALUES: return '' - value = value.replace('-','') - value = value.replace(' ','') + value = value.replace('-', '') + value = value.replace(' ', '') value = value.upper() if value[0:2] != 'RO': raise ValidationError(self.error_messages['invalid']) @@ -185,10 +186,10 @@ class ROPhoneNumberField(RegexField): value = super(ROPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: return '' - value = value.replace('-','') - value = value.replace('(','') - value = value.replace(')','') - value = value.replace(' ','') + value = value.replace('-', '') + value = value.replace('(', '') + value = value.replace(')', '') + value = value.replace(' ', '') if len(value) != 10: raise ValidationError(self.error_messages['invalid']) return value @@ -202,4 +203,3 @@ class ROPostalCodeField(RegexField): def __init__(self, max_length=6, min_length=6, *args, **kwargs): super(ROPostalCodeField, self).__init__(r'^[0-9][0-8][0-9]{4}$', max_length, min_length, *args, **kwargs) - diff --git a/django/contrib/markup/templatetags/markup.py b/django/contrib/markup/templatetags/markup.py index 18b7475ca7..389c919c07 100644 --- a/django/contrib/markup/templatetags/markup.py +++ b/django/contrib/markup/templatetags/markup.py @@ -47,6 +47,9 @@ def markdown(value, arg=''): they will be silently ignored. """ + import warnings + warnings.warn('The markdown filter has been deprecated', + category=DeprecationWarning) try: import markdown except ImportError: @@ -72,6 +75,9 @@ def markdown(value, arg=''): @register.filter(is_safe=True) def restructuredtext(value): + import warnings + warnings.warn('The restructuredtext filter has been deprecated', + category=DeprecationWarning) try: from docutils.core import publish_parts except ImportError: diff --git a/django/contrib/markup/tests.py b/django/contrib/markup/tests.py index 7b050ace82..19a3b7e9d0 100644 --- a/django/contrib/markup/tests.py +++ b/django/contrib/markup/tests.py @@ -1,7 +1,9 @@ # Quick tests for the markup templatetags (django.contrib.markup) import re +import warnings from django.template import Template, Context +from django import test from django.utils import unittest from django.utils.html import escape @@ -21,7 +23,7 @@ try: except ImportError: docutils = None -class Templates(unittest.TestCase): +class Templates(test.TestCase): textile_content = """Paragraph 1 @@ -37,6 +39,13 @@ Paragraph 2 with a link_ .. _link: http://www.example.com/""" + def setUp(self): + self.save_warnings_state() + warnings.filterwarnings('ignore', category=DeprecationWarning, module='django.contrib.markup') + + def tearDown(self): + self.restore_warnings_state() + @unittest.skipUnless(textile, 'textile not installed') def test_textile(self): t = Template("{% load markup %}{{ textile_content|textile }}") diff --git a/django/contrib/messages/storage/cookie.py b/django/contrib/messages/storage/cookie.py index 5f64ccd0c5..6b5b016234 100644 --- a/django/contrib/messages/storage/cookie.py +++ b/django/contrib/messages/storage/cookie.py @@ -46,10 +46,10 @@ class CookieStorage(BaseStorage): Stores messages in a cookie. """ cookie_name = 'messages' - # We should be able to store 4K in a cookie, but Internet Explorer - # imposes 4K as the *total* limit for a domain. To allow other - # cookies, we go for 3/4 of 4K. - max_cookie_size = 3072 + # uwsgi's default configuration enforces a maximum size of 4kb for all the + # HTTP headers. In order to leave some room for other cookies and headers, + # restrict the session cookie to 1/2 of 4kb. See #18781. + max_cookie_size = 2048 not_finished = '__messagesnotfinished__' def _get(self, *args, **kwargs): diff --git a/django/contrib/messages/tests/base.py b/django/contrib/messages/tests/base.py index e9a67b0500..b3ced12773 100644 --- a/django/contrib/messages/tests/base.py +++ b/django/contrib/messages/tests/base.py @@ -152,7 +152,7 @@ class BaseTest(TestCase): cycle. """ data = { - 'messages': ['Test message %d' % x for x in range(10)], + 'messages': ['Test message %d' % x for x in range(5)], } show_url = reverse('django.contrib.messages.tests.urls.show') for level in ('debug', 'info', 'success', 'warning', 'error'): @@ -170,7 +170,7 @@ class BaseTest(TestCase): @override_settings(MESSAGE_LEVEL=constants.DEBUG) def test_with_template_response(self): data = { - 'messages': ['Test message %d' % x for x in range(10)], + 'messages': ['Test message %d' % x for x in range(5)], } show_url = reverse('django.contrib.messages.tests.urls.show_template_response') for level in self.levels.keys(): @@ -194,7 +194,7 @@ class BaseTest(TestCase): before a GET. """ data = { - 'messages': ['Test message %d' % x for x in range(10)], + 'messages': ['Test message %d' % x for x in range(5)], } show_url = reverse('django.contrib.messages.tests.urls.show') messages = [] @@ -226,7 +226,7 @@ class BaseTest(TestCase): when one attempts to store a message. """ data = { - 'messages': ['Test message %d' % x for x in range(10)], + 'messages': ['Test message %d' % x for x in range(5)], } show_url = reverse('django.contrib.messages.tests.urls.show') for level in ('debug', 'info', 'success', 'warning', 'error'): @@ -251,7 +251,7 @@ class BaseTest(TestCase): raised if 'fail_silently' = True """ data = { - 'messages': ['Test message %d' % x for x in range(10)], + 'messages': ['Test message %d' % x for x in range(5)], 'fail_silently': True, } show_url = reverse('django.contrib.messages.tests.urls.show') diff --git a/django/contrib/sessions/tests.py b/django/contrib/sessions/tests.py index 7de2941122..dbc68652ac 100644 --- a/django/contrib/sessions/tests.py +++ b/django/contrib/sessions/tests.py @@ -1,4 +1,4 @@ -from datetime import datetime, timedelta +from datetime import timedelta import shutil import string import tempfile @@ -302,11 +302,10 @@ class CacheDBSessionTests(SessionTestsMixin, TestCase): self.assertTrue(self.session.exists(self.session.session_key)) def test_load_overlong_key(self): - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") + # Some backends might issue a warning + with warnings.catch_warnings(): self.session._session_key = (string.ascii_letters + string.digits) * 20 self.assertEqual(self.session.load(), {}) - self.assertEqual(len(w), 1) @override_settings(USE_TZ=True) @@ -352,11 +351,10 @@ class CacheSessionTests(SessionTestsMixin, unittest.TestCase): backend = CacheSession def test_load_overlong_key(self): - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") + # Some backends might issue a warning + with warnings.catch_warnings(): self.session._session_key = (string.ascii_letters + string.digits) * 20 self.assertEqual(self.session.load(), {}) - self.assertEqual(len(w), 1) class SessionMiddlewareTests(unittest.TestCase): diff --git a/django/core/cache/backends/memcached.py b/django/core/cache/backends/memcached.py index 426a0a15c0..9bb47c8344 100644 --- a/django/core/cache/backends/memcached.py +++ b/django/core/cache/backends/memcached.py @@ -141,7 +141,7 @@ class CacheClass(BaseMemcachedCache): ) try: import memcache - except: + except ImportError: raise InvalidCacheBackendError( "Memcached cache backend requires either the 'memcache' or 'cmemcache' library" ) diff --git a/django/core/mail/backends/console.py b/django/core/mail/backends/console.py index 0baae0c01e..ea0cb5d9ad 100644 --- a/django/core/mail/backends/console.py +++ b/django/core/mail/backends/console.py @@ -21,7 +21,7 @@ class EmailBackend(BaseEmailBackend): stream_created = self.open() for message in email_messages: self.stream.write('%s\n' % message.message().as_string()) - self.stream.write('-'*79) + self.stream.write('-' * 79) self.stream.write('\n') self.stream.flush() # flush after each message if stream_created: diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py index 98f75e0310..b40570efc9 100644 --- a/django/core/management/__init__.py +++ b/django/core/management/__init__.py @@ -5,6 +5,7 @@ from optparse import OptionParser, NO_DEFAULT import imp import warnings +from django.core.exceptions import ImproperlyConfigured from django.core.management.base import BaseCommand, CommandError, handle_default_options from django.core.management.color import color_style from django.utils.importlib import import_module @@ -105,7 +106,7 @@ def get_commands(): try: from django.conf import settings apps = settings.INSTALLED_APPS - except (AttributeError, EnvironmentError, ImportError): + except (AttributeError, ImproperlyConfigured): apps = [] # Find and load the management module for each installed app. diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index f0fd2f56d2..9b0f495749 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -1034,9 +1034,12 @@ class BaseDatabaseIntrospection(object): def get_primary_key_column(self, cursor, table_name): """ - Backends can override this to return the column name of the primary key for the given table. + Returns the name of the primary key column for the given table. """ - raise NotImplementedError + for column in six.iteritems(self.get_indexes(cursor, table_name)): + if column[1]['primary_key']: + return column[0] + return None def get_indexes(self, cursor, table_name): """ diff --git a/django/db/backends/creation.py b/django/db/backends/creation.py index b68fa8f795..3262a8922f 100644 --- a/django/db/backends/creation.py +++ b/django/db/backends/creation.py @@ -1,8 +1,10 @@ +import hashlib import sys import time from django.conf import settings from django.db.utils import load_backend +from django.utils.encoding import force_bytes from django.utils.six.moves import input # The prefix to put on the default database name when creating @@ -27,7 +29,10 @@ class BaseDatabaseCreation(object): Generates a 32-bit digest of a set of arguments that can be used to shorten identifying names. """ - return '%x' % (abs(hash(args)) % 4294967296) # 2**32 + h = hashlib.md5() + for arg in args: + h.update(force_bytes(arg)) + return h.hexdigest()[:8] def sql_create_model(self, model, style, known_models=set()): """ diff --git a/django/db/backends/mysql/introspection.py b/django/db/backends/mysql/introspection.py index 3b5a6fecd3..c552263e5e 100644 --- a/django/db/backends/mysql/introspection.py +++ b/django/db/backends/mysql/introspection.py @@ -2,7 +2,6 @@ import re from .base import FIELD_TYPE from django.db.backends import BaseDatabaseIntrospection -from django.utils import six foreign_key_re = re.compile(r"\sCONSTRAINT `[^`]*` FOREIGN KEY \(`([^`]*)`\) REFERENCES `([^`]*)` \(`([^`]*)`\)") @@ -88,15 +87,6 @@ class DatabaseIntrospection(BaseDatabaseIntrospection): key_columns.extend(cursor.fetchall()) return key_columns - def get_primary_key_column(self, cursor, table_name): - """ - Returns the name of the primary key column for the given table - """ - for column in six.iteritems(self.get_indexes(cursor, table_name)): - if column[1]['primary_key']: - return column[0] - return None - def get_indexes(self, cursor, table_name): cursor.execute("SHOW INDEX FROM %s" % self.connection.ops.quote_name(table_name)) # Do a two-pass search for indexes: on first pass check which indexes diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index 31a16b6a2b..0f0b6b74e3 100644 --- a/django/db/backends/sqlite3/base.py +++ b/django/db/backends/sqlite3/base.py @@ -412,7 +412,4 @@ def _sqlite_format_dtdelta(dt, conn, days, secs, usecs): return str(dt) def _sqlite_regexp(re_pattern, re_string): - try: - return bool(re.search(re_pattern, re_string)) - except: - return False + return bool(re.search(re_pattern, re_string)) diff --git a/django/db/models/constants.py b/django/db/models/constants.py new file mode 100644 index 0000000000..629497eb3d --- /dev/null +++ b/django/db/models/constants.py @@ -0,0 +1,7 @@ +""" +Constants used across the ORM in general. +""" + +# Separator used to split filter strings apart. +LOOKUP_SEP = '__' + diff --git a/django/db/models/query.py b/django/db/models/query.py index 05c049b31f..8bf08b7a93 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -8,6 +8,7 @@ import sys from django.core import exceptions from django.db import connections, router, transaction, IntegrityError +from django.db.models.constants import LOOKUP_SEP from django.db.models.fields import AutoField from django.db.models.query_utils import (Q, select_related_descend, deferred_class_factory, InvalidQuery) @@ -1613,8 +1614,6 @@ def prefetch_related_objects(result_cache, related_lookups): Populates prefetched objects caches for a list of results from a QuerySet """ - from django.db.models.sql.constants import LOOKUP_SEP - if len(result_cache) == 0: return # nothing to do diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index cf7cad29bd..28d2404858 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -3,8 +3,10 @@ from django.utils.six.moves import zip from django.core.exceptions import FieldError from django.db import transaction from django.db.backends.util import truncate_name +from django.db.models.constants import LOOKUP_SEP from django.db.models.query_utils import select_related_descend -from django.db.models.sql.constants import * +from django.db.models.sql.constants import (SINGLE, MULTI, ORDER_DIR, + GET_ITERATOR_CHUNK_SIZE) from django.db.models.sql.datastructures import EmptyResultSet from django.db.models.sql.expressions import SQLEvaluator from django.db.models.sql.query import get_order_dir, Query @@ -811,7 +813,7 @@ class SQLCompiler(object): raise EmptyResultSet except EmptyResultSet: if result_type == MULTI: - return empty_iter() + return iter([]) else: return @@ -1088,13 +1090,6 @@ class SQLDateCompiler(SQLCompiler): yield date -def empty_iter(): - """ - Returns an iterator containing no results. - """ - yield next(iter([])) - - def order_modified_iter(cursor, trim, sentinel): """ Yields blocks of rows from a cursor. We use this iterator in the special diff --git a/django/db/models/sql/constants.py b/django/db/models/sql/constants.py index b9cf2c96fd..f750310624 100644 --- a/django/db/models/sql/constants.py +++ b/django/db/models/sql/constants.py @@ -1,7 +1,13 @@ +""" +Constants specific to the SQL storage portion of the ORM. +""" + from collections import namedtuple import re -# Valid query types (a set is used for speedy lookups). +# Valid query types (a set is used for speedy lookups). These are (currently) +# considered SQL-specific; other storage systems may choose to use different +# lookup types. QUERY_TERMS = set([ 'exact', 'iexact', 'contains', 'icontains', 'gt', 'gte', 'lt', 'lte', 'in', 'startswith', 'istartswith', 'endswith', 'iendswith', 'range', 'year', @@ -12,9 +18,6 @@ QUERY_TERMS = set([ # Larger values are slightly faster at the expense of more storage space. GET_ITERATOR_CHUNK_SIZE = 100 -# Separator used to split filter strings apart. -LOOKUP_SEP = '__' - # Constants to make looking up tuple values clearer. # Join lists (indexes into the tuples that are values in the alias_map # dictionary in the Query class). diff --git a/django/db/models/sql/expressions.py b/django/db/models/sql/expressions.py index 1bbf742b5c..ac8fea6da3 100644 --- a/django/db/models/sql/expressions.py +++ b/django/db/models/sql/expressions.py @@ -1,6 +1,6 @@ from django.core.exceptions import FieldError +from django.db.models.constants import LOOKUP_SEP from django.db.models.fields import FieldDoesNotExist -from django.db.models.sql.constants import LOOKUP_SEP class SQLEvaluator(object): def __init__(self, expression, query, allow_joins=True): diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 1915efa7b9..77f24fcf24 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -15,11 +15,12 @@ from django.utils.tree import Node from django.utils import six from django.db import connections, DEFAULT_DB_ALIAS from django.db.models import signals +from django.db.models.constants import LOOKUP_SEP from django.db.models.expressions import ExpressionNode from django.db.models.fields import FieldDoesNotExist -from django.db.models.query_utils import InvalidQuery from django.db.models.sql import aggregates as base_aggregates_module -from django.db.models.sql.constants import * +from django.db.models.sql.constants import (QUERY_TERMS, ORDER_DIR, SINGLE, + ORDER_PATTERN, JoinInfo) from django.db.models.sql.datastructures import EmptyResultSet, Empty, MultiJoin from django.db.models.sql.expressions import SQLEvaluator from django.db.models.sql.where import (WhereNode, Constraint, EverythingNode, @@ -28,6 +29,7 @@ from django.core.exceptions import FieldError __all__ = ['Query', 'RawQuery'] + class RawQuery(object): """ A single raw SQL query diff --git a/django/db/models/sql/subqueries.py b/django/db/models/sql/subqueries.py index 937505b9b0..c6995c6abb 100644 --- a/django/db/models/sql/subqueries.py +++ b/django/db/models/sql/subqueries.py @@ -3,6 +3,7 @@ Query subclasses which provide extra functionality beyond simple data retrieval. """ from django.core.exceptions import FieldError +from django.db.models.constants import LOOKUP_SEP from django.db.models.fields import DateField, FieldDoesNotExist from django.db.models.sql.constants import * from django.db.models.sql.datastructures import Date diff --git a/django/forms/widgets.py b/django/forms/widgets.py index fdb9f50688..7651efccd0 100644 --- a/django/forms/widgets.py +++ b/django/forms/widgets.py @@ -507,11 +507,7 @@ class CheckboxInput(Widget): def render(self, name, value, attrs=None): final_attrs = self.build_attrs(attrs, type='checkbox', name=name) - try: - result = self.check_test(value) - except: # Silently catch exceptions - result = False - if result: + if self.check_test(value): final_attrs['checked'] = 'checked' if not (value is True or value is False or value is None or value == ''): # Only add the 'value' attribute if a value is non-empty. @@ -525,7 +521,7 @@ class CheckboxInput(Widget): return False value = data.get(name) # Translate true and false strings to boolean values. - values = {'true': True, 'false': False} + values = {'true': True, 'false': False} if isinstance(value, six.string_types): value = values.get(value.lower(), value) return value diff --git a/django/http/__init__.py b/django/http/__init__.py index 2198f38cbb..ecb39129ad 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -2,6 +2,7 @@ from __future__ import absolute_import, unicode_literals import copy import datetime +from email.header import Header import os import re import sys @@ -560,31 +561,44 @@ class HttpResponse(object): else: __str__ = serialize - def _convert_to_ascii(self, *values): - """Converts all values to ascii strings.""" - for value in values: - if not isinstance(value, six.string_types): - value = str(value) - try: - if six.PY3: - # Ensure string only contains ASCII - value.encode('us-ascii') + def _convert_to_charset(self, value, charset, mime_encode=False): + """Converts headers key/value to ascii/latin1 native strings. + + `charset` must be 'ascii' or 'latin-1'. If `mime_encode` is True and + `value` value can't be represented in the given charset, MIME-encoding + is applied. + """ + if not isinstance(value, (bytes, six.text_type)): + value = str(value) + try: + if six.PY3: + if isinstance(value, str): + # Ensure string is valid in given charset + value.encode(charset) else: - if isinstance(value, str): - # Ensure string only contains ASCII - value.decode('us-ascii') - else: - # Convert unicode to an ASCII string - value = value.encode('us-ascii') - except UnicodeError as e: - e.reason += ', HTTP response headers must be in US-ASCII format' + # Convert bytestring using given charset + value = value.decode(charset) + else: + if isinstance(value, str): + # Ensure string is valid in given charset + value.decode(charset) + else: + # Convert unicode string to given charset + value = value.encode(charset) + except UnicodeError as e: + if mime_encode: + # Wrapping in str() is a workaround for #12422 under Python 2. + value = str(Header(value, 'utf-8').encode()) + else: + e.reason += ', HTTP response headers must be in %s format' % charset raise - if '\n' in value or '\r' in value: - raise BadHeaderError("Header values can't contain newlines (got %r)" % value) - yield value + if str('\n') in value or str('\r') in value: + raise BadHeaderError("Header values can't contain newlines (got %r)" % value) + return value def __setitem__(self, header, value): - header, value = self._convert_to_ascii(header, value) + header = self._convert_to_charset(header, 'ascii') + value = self._convert_to_charset(value, 'latin1', mime_encode=True) self._headers[header.lower()] = (header, value) def __delitem__(self, header): diff --git a/django/http/multipartparser.py b/django/http/multipartparser.py index e76a4f4208..40aefd6e9d 100644 --- a/django/http/multipartparser.py +++ b/django/http/multipartparser.py @@ -68,11 +68,10 @@ class MultiPartParser(object): if not boundary or not cgi.valid_boundary(boundary): raise MultiPartParserError('Invalid boundary in multipart: %s' % boundary) - # Content-Length should contain the length of the body we are about # to receive. try: - content_length = int(META.get('HTTP_CONTENT_LENGTH', META.get('CONTENT_LENGTH',0))) + content_length = int(META.get('HTTP_CONTENT_LENGTH', META.get('CONTENT_LENGTH', 0))) except (ValueError, TypeError): content_length = 0 @@ -178,7 +177,7 @@ class MultiPartParser(object): content_type = meta_data.get('content-type', ('',))[0].strip() try: - charset = meta_data.get('content-type', (0,{}))[1].get('charset', None) + charset = meta_data.get('content-type', (0, {}))[1].get('charset', None) except: charset = None diff --git a/django/shortcuts/__init__.py b/django/shortcuts/__init__.py index 154f224671..a824446b7e 100644 --- a/django/shortcuts/__init__.py +++ b/django/shortcuts/__init__.py @@ -66,23 +66,7 @@ def redirect(to, *args, **kwargs): else: redirect_class = HttpResponseRedirect - # If it's a model, use get_absolute_url() - if hasattr(to, 'get_absolute_url'): - return redirect_class(to.get_absolute_url()) - - # Next try a reverse URL resolution. - try: - return redirect_class(urlresolvers.reverse(to, args=args, kwargs=kwargs)) - except urlresolvers.NoReverseMatch: - # If this is a callable, re-raise. - if callable(to): - raise - # If this doesn't "feel" like a URL, re-raise. - if '/' not in to and '.' not in to: - raise - - # Finally, fall back and assume it's a URL - return redirect_class(to) + return redirect_class(resolve_url(to, *args, **kwargs)) def _get_queryset(klass): """ @@ -128,3 +112,34 @@ def get_list_or_404(klass, *args, **kwargs): raise Http404('No %s matches the given query.' % queryset.model._meta.object_name) return obj_list +def resolve_url(to, *args, **kwargs): + """ + Return a URL appropriate for the arguments passed. + + The arguments could be: + + * A model: the model's `get_absolute_url()` function will be called. + + * A view name, possibly with arguments: `urlresolvers.reverse()` will + be used to reverse-resolve the name. + + * A URL, which will be returned as-is. + + """ + # If it's a model, use get_absolute_url() + if hasattr(to, 'get_absolute_url'): + return to.get_absolute_url() + + # Next try a reverse URL resolution. + try: + return urlresolvers.reverse(to, args=args, kwargs=kwargs) + except urlresolvers.NoReverseMatch: + # If this is a callable, re-raise. + if callable(to): + raise + # If this doesn't "feel" like a URL, re-raise. + if '/' not in to and '.' not in to: + raise + + # Finally, fall back and assume it's a URL + return to diff --git a/django/test/testcases.py b/django/test/testcases.py index ec08a28a9d..0e7c59cd49 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -360,7 +360,7 @@ class SimpleTestCase(ut2.TestCase): args: Extra args. kwargs: Extra kwargs. """ - return self.assertRaisesRegexp(expected_exception, + return six.assertRaisesRegex(self, expected_exception, re.escape(expected_message), callable_obj, *args, **kwargs) def assertFieldOutput(self, fieldclass, valid, invalid, field_args=None, diff --git a/django/utils/_os.py b/django/utils/_os.py index 9eb5e5e8ea..1ea12aed8a 100644 --- a/django/utils/_os.py +++ b/django/utils/_os.py @@ -1,6 +1,6 @@ import os import stat -from os.path import join, normcase, normpath, abspath, isabs, sep +from os.path import join, normcase, normpath, abspath, isabs, sep, dirname from django.utils.encoding import force_text from django.utils import six @@ -41,13 +41,16 @@ def safe_join(base, *paths): paths = [force_text(p) for p in paths] final_path = abspathu(join(base, *paths)) base_path = abspathu(base) - base_path_len = len(base_path) # Ensure final_path starts with base_path (using normcase to ensure we - # don't false-negative on case insensitive operating systems like Windows) - # and that the next character after the final path is os.sep (or nothing, - # in which case final_path must be equal to base_path). - if not normcase(final_path).startswith(normcase(base_path)) \ - or final_path[base_path_len:base_path_len+1] not in ('', sep): + # don't false-negative on case insensitive operating systems like Windows), + # further, one of the following conditions must be true: + # a) The next character is the path separator (to prevent conditions like + # safe_join("/dir", "/../d")) + # b) The final path must be the same as the base path. + # c) The base path must be the most root path (meaning either "/" or "C:\\") + if (not normcase(final_path).startswith(normcase(base_path + sep)) and + normcase(final_path) != normcase(base_path) and + dirname(normcase(base_path)) != normcase(base_path)): raise ValueError('The joined path (%s) is located outside of the base ' 'path component (%s)' % (final_path, base_path)) return final_path diff --git a/django/utils/six.py b/django/utils/six.py index e4ce939844..9e3823128f 100644 --- a/django/utils/six.py +++ b/django/utils/six.py @@ -370,13 +370,20 @@ def with_metaclass(meta, base=object): if PY3: _iterlists = "lists" + _assertRaisesRegex = "assertRaisesRegex" else: _iterlists = "iterlists" + _assertRaisesRegex = "assertRaisesRegexp" + def iterlists(d): """Return an iterator over the values of a MultiValueDict.""" return getattr(d, _iterlists)() +def assertRaisesRegex(self, *args, **kwargs): + return getattr(self, _assertRaisesRegex)(*args, **kwargs) + + add_move(MovedModule("_dummy_thread", "dummy_thread")) add_move(MovedModule("_thread", "thread")) diff --git a/django/views/generic/dates.py b/django/views/generic/dates.py index d44246f0b7..52e13a4533 100644 --- a/django/views/generic/dates.py +++ b/django/views/generic/dates.py @@ -372,6 +372,9 @@ class BaseDateListView(MultipleObjectMixin, DateMixin, View): return qs def get_date_list_period(self): + """ + Get the aggregation period for the list of dates: 'year', 'month', or 'day'. + """ return self.date_list_period def get_date_list(self, queryset, date_type=None): diff --git a/docs/README b/docs/README index b02499d738..05133d8917 100644 --- a/docs/README +++ b/docs/README @@ -1,9 +1,8 @@ The documentation in this tree is in plain text files and can be viewed using any text file viewer. -Technically speaking, it uses ReST (reStructuredText) [1], and the Sphinx -documentation system [2]. This allows it to be built into other forms for -easier viewing and browsing. +It uses ReST (reStructuredText) [1], and the Sphinx documentation system [2]. +This allows it to be built into other forms for easier viewing and browsing. To create an HTML version of the docs: diff --git a/docs/faq/install.txt b/docs/faq/install.txt index a14615e47c..a772a379d5 100644 --- a/docs/faq/install.txt +++ b/docs/faq/install.txt @@ -16,8 +16,9 @@ How do I get started? What are Django's prerequisites? -------------------------------- -Django requires Python_, specifically Python 2.6.5 - 2.7.x. No other Python -libraries are required for basic Django usage. +Django requires Python, specifically Python 2.6.5 - 2.7.x. No other Python +libraries are required for basic Django usage. Django 1.5 also has +experimental support for Python 3.2 and above. For a development environment -- if you just want to experiment with Django -- you don't need to have a separate Web server installed; Django comes with its @@ -50,15 +51,12 @@ aren't available under older versions of Python. Third-party applications for use with Django are, of course, free to set their own version requirements. -Over the next year or two Django will begin dropping support for older Python -versions as part of a migration which will end with Django running on Python 3 -(see below for details). - All else being equal, we recommend that you use the latest 2.x release (currently Python 2.7). This will let you take advantage of the numerous -improvements and optimizations to the Python language since version 2.6, and -will help ease the process of dropping support for older Python versions on -the road to Python 3. +improvements and optimizations to the Python language since version 2.6. + +Generally speaking, we don't recommend running Django on Python 3 yet; see +below for more. What Python version can I use with Django? ------------------------------------------ @@ -71,25 +69,21 @@ Django version Python versions 1.2 2.4, 2.5, 2.6, 2.7 1.3 2.4, 2.5, 2.6, 2.7 **1.4** **2.5, 2.6, 2.7** -*1.5 (future)* *2.6, 2.7, 3.x (experimental)* +*1.5 (future)* *2.6, 2.7* and *3.2, 3.3 (experimental)* ============== =============== Can I use Django with Python 3? ------------------------------- -Not at the moment. Python 3.0 introduced a number of -backwards-incompatible changes to the Python language, and although -these changes are generally a good thing for Python's future, it will -be a while before most Python software catches up and is able to run -on Python 3.0. For larger Python-based software like Django, the -transition is expected to take at least a year or two (since it -involves dropping support for older Python releases and so must be -done gradually). +Django 1.5 introduces experimental support for Python 3.2 and 3.3. However, we +don't yet suggest that you use Django and Python 3 in production. -In the meantime, Python 2.x releases will be supported and provided -with bug fixes and security updates by the Python development team, so -continuing to use a Python 2.x release during the transition should -not present any risk. +Python 3 support should be considered a "preview". It's offered to bootstrap +the transition of the Django ecosystem to Python 3, and to help you start +porting your apps for future Python 3 compatibility. But we're not yet +confident enough to promise stability in production. + +Our current plan is to make Django 1.6 suitable for general use with Python 3. Will Django run under shared hosting (like TextDrive or Dreamhost)? ------------------------------------------------------------------- diff --git a/docs/faq/models.txt b/docs/faq/models.txt index 4a83aa9f2c..69965b66e1 100644 --- a/docs/faq/models.txt +++ b/docs/faq/models.txt @@ -11,7 +11,7 @@ Then, just do this:: >>> from django.db import connection >>> connection.queries - [{'sql': 'SELECT polls_polls.id,polls_polls.question,polls_polls.pub_date FROM polls_polls', + [{'sql': 'SELECT polls_polls.id, polls_polls.question, polls_polls.pub_date FROM polls_polls', 'time': '0.002'}] ``connection.queries`` is only available if :setting:`DEBUG` is ``True``. diff --git a/docs/howto/custom-model-fields.txt b/docs/howto/custom-model-fields.txt index e73ef9aa42..9ff06479c6 100644 --- a/docs/howto/custom-model-fields.txt +++ b/docs/howto/custom-model-fields.txt @@ -181,10 +181,10 @@ card values plus their suits; 104 characters in total. Many of Django's model fields accept options that they don't do anything with. For example, you can pass both :attr:`~django.db.models.Field.editable` and - :attr:`~django.db.models.Field.auto_now` to a + :attr:`~django.db.models.DateField.auto_now` to a :class:`django.db.models.DateField` and it will simply ignore the :attr:`~django.db.models.Field.editable` parameter - (:attr:`~django.db.models.Field.auto_now` being set implies + (:attr:`~django.db.models.DateField.auto_now` being set implies ``editable=False``). No error is raised in this case. This behavior simplifies the field classes, because they don't need to @@ -516,8 +516,8 @@ for the first time, the ``add`` parameter will be ``True``, otherwise it will be You only need to override this method if you want to preprocess the value somehow, just before saving. For example, Django's :class:`~django.db.models.DateTimeField` uses this method to set the attribute -correctly in the case of :attr:`~django.db.models.Field.auto_now` or -:attr:`~django.db.models.Field.auto_now_add`. +correctly in the case of :attr:`~django.db.models.DateField.auto_now` or +:attr:`~django.db.models.DateField.auto_now_add`. If you do override this method, you must return the value of the attribute at the end. You should also update the model's attribute if you make any changes diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 9359c82e46..4add751912 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -264,6 +264,9 @@ these changes. in 1.4. The backward compatibility will be removed -- ``HttpRequest.raw_post_data`` will no longer work. +* ``django.contrib.markup`` will be removed following an accelerated + deprecation. + 1.7 --- diff --git a/docs/intro/install.txt b/docs/intro/install.txt index 70c8034c5d..f9b122e62d 100644 --- a/docs/intro/install.txt +++ b/docs/intro/install.txt @@ -10,11 +10,9 @@ Install Python -------------- Being a Python Web framework, Django requires Python. It works with any Python -version from 2.6.5 to 2.7 (due to backwards incompatibilities in Python 3.0, -Django does not currently work with Python 3.0; see :doc:`the Django FAQ -` for more information on supported Python versions and the 3.0 -transition), these versions of Python include a lightweight database called -SQLite_ so you won't need to set up a database just yet. +version from 2.6.5 to 2.7. It also features experimental support for versions +3.2 and 3.3. All these versions of Python include a lightweight database +called SQLite_ so you won't need to set up a database just yet. .. _sqlite: http://sqlite.org/ diff --git a/docs/intro/overview.txt b/docs/intro/overview.txt index 49233fb8a7..4d5a86f82b 100644 --- a/docs/intro/overview.txt +++ b/docs/intro/overview.txt @@ -31,7 +31,7 @@ the file ``mysite/news/models.py``:: return self.full_name class Article(models.Model): - pub_date = models.DateTimeField() + pub_date = models.DateField() headline = models.CharField(max_length=200) content = models.TextField() reporter = models.ForeignKey(Reporter) @@ -96,8 +96,8 @@ access your data. The API is created on the fly, no code generation necessary:: DoesNotExist: Reporter matching query does not exist. Lookup parameters were {'id': 2} # Create an article. - >>> from datetime import datetime - >>> a = Article(pub_date=datetime.now(), headline='Django is cool', + >>> from datetime import date + >>> a = Article(pub_date=date.today(), headline='Django is cool', ... content='Yeah.', reporter=r) >>> a.save() @@ -140,7 +140,7 @@ as registering your model in the admin site:: from django.db import models class Article(models.Model): - pub_date = models.DateTimeField() + pub_date = models.DateField() headline = models.CharField(max_length=200) content = models.TextField() reporter = models.ForeignKey(Reporter) diff --git a/docs/intro/tutorial03.txt b/docs/intro/tutorial03.txt index 6d4fb7eef1..03d4bf68b3 100644 --- a/docs/intro/tutorial03.txt +++ b/docs/intro/tutorial03.txt @@ -513,11 +513,12 @@ Here's what happens if a user goes to "/polls/34/" in this system: further processing. Now that we've decoupled that, we need to decouple the ``polls.urls`` -URLconf by removing the leading "polls/" from each line, and removing the -lines registering the admin site. Your ``polls/urls.py`` file should now look like +URLconf by removing the leading "polls/" from each line, removing the +lines registering the admin site, and removing the ``include`` import which +is no longer used. Your ``polls/urls.py`` file should now look like this:: - from django.conf.urls import patterns, include, url + from django.conf.urls import patterns, url urlpatterns = patterns('polls.views', url(r'^$', 'index'), diff --git a/docs/intro/tutorial04.txt b/docs/intro/tutorial04.txt index 31680ea5e5..49e597ca29 100644 --- a/docs/intro/tutorial04.txt +++ b/docs/intro/tutorial04.txt @@ -218,7 +218,7 @@ Read on for details. First, open the ``polls/urls.py`` URLconf. It looks like this, according to the tutorial so far:: - from django.conf.urls import patterns, include, url + from django.conf.urls import patterns, url urlpatterns = patterns('polls.views', url(r'^$', 'index'), @@ -229,7 +229,7 @@ tutorial so far:: Change it like so:: - from django.conf.urls import patterns, include, url + from django.conf.urls import patterns, url from django.views.generic import DetailView, ListView from polls.models import Poll diff --git a/docs/ref/class-based-views/base.txt b/docs/ref/class-based-views/base.txt index 3f82b44f46..cc9aa852f1 100644 --- a/docs/ref/class-based-views/base.txt +++ b/docs/ref/class-based-views/base.txt @@ -8,6 +8,11 @@ themselves or inherited from. They may not provide all the capabilities required for projects, in which case there are Mixins and Generic class-based views. +Many of Django's built-in class-based views inherit from other class-based +views or various mixins. Because this inheritence chain is very important, the +ancestor classes are documented under the section title of **Ancestors (MRO)**. +MRO is an acronym for Method Resolution Order. + View ---- @@ -20,6 +25,7 @@ View 1. :meth:`dispatch()` 2. :meth:`http_method_not_allowed()` + 3. :meth:`options()` **Example views.py**:: @@ -41,8 +47,20 @@ View url(r'^mine/$', MyView.as_view(), name='my-view'), ) + **Attributes** + + .. attribute:: http_method_names = ['get', 'post', 'put', 'delete', 'head', 'options', 'trace'] + + The default list of HTTP method names that this view will accept. + **Methods** + .. classmethod:: as_view(**initkwargs) + + Returns a callable view that takes a request and returns a response:: + + response = MyView.as_view()(request) + .. method:: dispatch(request, *args, **kwargs) The ``view`` part of the view -- the method that accepts a ``request`` @@ -53,6 +71,11 @@ View delegated to :meth:`~View.get()`, a ``POST`` to :meth:`~View.post()`, and so on. + By default, a ``HEAD`` request will be delegated to :meth:`~View.get()`. + If you need to handle ``HEAD`` requests in a different way than ``GET``, + you can override the :meth:`~View.head()` method. See + :ref:`supporting-other-http-methods` for an example. + The default implementation also sets ``request``, ``args`` and ``kwargs`` as instance variables, so any method on the view can know the full details of the request that was made to invoke the view. @@ -62,14 +85,13 @@ View If the view was called with a HTTP method it doesn't support, this method is called instead. - The default implementation returns ``HttpResponseNotAllowed`` with list - of allowed methods in plain text. + The default implementation returns ``HttpResponseNotAllowed`` with a + list of allowed methods in plain text. - .. note:: + .. method:: options(request, *args, **kwargs) - Documentation on class-based views is a work in progress. As yet, only the - methods defined directly on the class are documented here, not methods - defined on superclasses. + Handles responding to requests for the OPTIONS HTTP verb. Returns a + list of the allowed HTTP method names for the view. TemplateView ------------ @@ -81,6 +103,8 @@ TemplateView **Ancestors (MRO)** + This view inherits methods and attributes from the following views: + * :class:`django.views.generic.base.TemplateView` * :class:`django.views.generic.base.TemplateResponseMixin` * :class:`django.views.generic.base.View` @@ -116,28 +140,11 @@ TemplateView url(r'^$', HomePageView.as_view(), name='home'), ) - **Methods and Attributes** - - .. attribute:: template_name - - The full name of a template to use. - - .. method:: get_context_data(**kwargs) - - Return a context data dictionary consisting of the contents of - ``kwargs`` stored in the context variable ``params``. - **Context** * ``params``: The dictionary of keyword arguments captured from the URL pattern that served the view. - .. note:: - - Documentation on class-based views is a work in progress. As yet, only the - methods defined directly on the class are documented here, not methods - defined on superclasses. - RedirectView ------------ @@ -156,6 +163,8 @@ RedirectView **Ancestors (MRO)** + This view inherits methods and attributes from the following view: + * :class:`django.views.generic.base.View` **Method Flowchart** @@ -194,7 +203,7 @@ RedirectView url(r'^go-to-django/$', RedirectView.as_view(url='http://djangoproject.com'), name='go-to-django'), ) - **Methods and Attributes** + **Attributes** .. attribute:: url @@ -215,6 +224,8 @@ RedirectView then the query string is discarded. By default, ``query_string`` is ``False``. + **Methods** + .. method:: get_redirect_url(**kwargs) Constructs the target URL for redirection. @@ -225,9 +236,3 @@ RedirectView :attr:`~RedirectView.query_string`. Subclasses may implement any behavior they wish, as long as the method returns a redirect-ready URL string. - - .. note:: - - Documentation on class-based views is a work in progress. As yet, only the - methods defined directly on the class are documented here, not methods - defined on superclasses. diff --git a/docs/ref/class-based-views/generic-date-based.txt b/docs/ref/class-based-views/generic-date-based.txt index 12776cbb94..64b269f514 100644 --- a/docs/ref/class-based-views/generic-date-based.txt +++ b/docs/ref/class-based-views/generic-date-based.txt @@ -2,13 +2,15 @@ Generic date views ================== -Date-based generic views (in the module :mod:`django.views.generic.dates`) -are views for displaying drilldown pages for date-based data. +.. module:: django.views.generic.dates + +Date-based generic views, provided in :mod:`django.views.generic.dates`, are +views for displaying drilldown pages for date-based data. ArchiveIndexView ---------------- -.. class:: django.views.generic.dates.ArchiveIndexView +.. class:: ArchiveIndexView A top-level index page showing the "latest" objects, by date. Objects with a date in the *future* are not included unless you set ``allow_future`` to @@ -36,7 +38,7 @@ ArchiveIndexView YearArchiveView --------------- -.. class:: django.views.generic.dates.YearArchiveView +.. class:: YearArchiveView A yearly archive page showing all available months in a given year. Objects with a date in the *future* are not displayed unless you set @@ -58,13 +60,15 @@ YearArchiveView A boolean specifying whether to retrieve the full list of objects for this year and pass those to the template. If ``True``, the list of - objects will be made available to the context. By default, this is + objects will be made available to the context. If ``False``, the + ``None`` queryset will be used as the object list. By default, this is ``False``. .. method:: get_make_object_list() - Determine if an object list will be returned as part of the context. If - ``False``, the ``None`` queryset will be used as the object list. + Determine if an object list will be returned as part of the context. + Returns :attr:`~YearArchiveView.make_object_list` by default. + **Context** @@ -80,16 +84,18 @@ YearArchiveView :class:`datetime.datetime` objects, in ascending order. - * ``year``: A :class:`datetime.date` object + * ``year``: A :class:`~datetime.date` object representing the given year. - * ``next_year``: A :class:`datetime.date` object - representing the first day of the next year. If the next year is in the - future, this will be ``None``. + * ``next_year``: A :class:`~datetime.date` object + representing the first day of the next year, according to + :attr:`~BaseDateListView.allow_empty` and + :attr:`~DateMixin.allow_future`. - * ``previous_year``: A :class:`datetime.date` object - representing the first day of the previous year. Unlike ``next_year``, - this will never be ``None``. + * ``previous_year``: A :class:`~datetime.date` object + representing the first day of the previous year, according to + :attr:`~BaseDateListView.allow_empty` and + :attr:`~DateMixin.allow_future`. **Notes** @@ -98,7 +104,7 @@ YearArchiveView MonthArchiveView ---------------- -.. class:: django.views.generic.dates.MonthArchiveView +.. class:: MonthArchiveView A monthly archive page showing all objects in a given month. Objects with a date in the *future* are not displayed unless you set ``allow_future`` to @@ -131,16 +137,18 @@ MonthArchiveView :class:`datetime.datetime` objects, in ascending order. - * ``month``: A :class:`datetime.date` object + * ``month``: A :class:`~datetime.date` object representing the given month. - * ``next_month``: A :class:`datetime.date` object - representing the first day of the next month. If the next month is in the - future, this will be ``None``. + * ``next_month``: A :class:`~datetime.date` object + representing the first day of the next month, according to + :attr:`~BaseDateListView.allow_empty` and + :attr:`~DateMixin.allow_future`. - * ``previous_month``: A :class:`datetime.date` object - representing the first day of the previous month. Unlike ``next_month``, - this will never be ``None``. + * ``previous_month``: A :class:`~datetime.date` object + representing the first day of the previous month, according to + :attr:`~BaseDateListView.allow_empty` and + :attr:`~DateMixin.allow_future`. **Notes** @@ -149,7 +157,7 @@ MonthArchiveView WeekArchiveView --------------- -.. class:: django.views.generic.dates.WeekArchiveView +.. class:: WeekArchiveView A weekly archive page showing all objects in a given week. Objects with a date in the *future* are not displayed unless you set ``allow_future`` to @@ -175,16 +183,18 @@ WeekArchiveView :class:`~django.views.generic.dates.BaseDateListView`), the template's context will be: - * ``week``: A :class:`datetime.date` object + * ``week``: A :class:`~datetime.date` object representing the first day of the given week. - * ``next_week``: A :class:`datetime.date` object - representing the first day of the next week. If the next week is in the - future, this will be ``None``. + * ``next_week``: A :class:`~datetime.date` object + representing the first day of the next week, according to + :attr:`~BaseDateListView.allow_empty` and + :attr:`~DateMixin.allow_future`. - * ``previous_week``: A :class:`datetime.date` object - representing the first day of the previous week. Unlike ``next_week``, - this will never be ``None``. + * ``previous_week``: A :class:`~datetime.date` object + representing the first day of the previous week, according to + :attr:`~BaseDateListView.allow_empty` and + :attr:`~DateMixin.allow_future`. **Notes** @@ -193,7 +203,7 @@ WeekArchiveView DayArchiveView -------------- -.. class:: django.views.generic.dates.DayArchiveView +.. class:: DayArchiveView A day archive page showing all objects in a given day. Days in the future throw a 404 error, regardless of whether any objects exist for future days, @@ -220,24 +230,28 @@ DayArchiveView :class:`~django.views.generic.dates.BaseDateListView`), the template's context will be: - * ``day``: A :class:`datetime.date` object + * ``day``: A :class:`~datetime.date` object representing the given day. - * ``next_day``: A :class:`datetime.date` object - representing the next day. If the next day is in the future, this will be - ``None``. + * ``next_day``: A :class:`~datetime.date` object + representing the next day, according to + :attr:`~BaseDateListView.allow_empty` and + :attr:`~DateMixin.allow_future`. - * ``previous_day``: A :class:`datetime.date` object - representing the previous day. Unlike ``next_day``, this will never be - ``None``. + * ``previous_day``: A :class:`~datetime.date` object + representing the previous day, according to + :attr:`~BaseDateListView.allow_empty` and + :attr:`~DateMixin.allow_future`. - * ``next_month``: A :class:`datetime.date` object - representing the first day of the next month. If the next month is in the - future, this will be ``None``. + * ``next_month``: A :class:`~datetime.date` object + representing the first day of the next month, according to + :attr:`~BaseDateListView.allow_empty` and + :attr:`~DateMixin.allow_future`. - * ``previous_month``: A :class:`datetime.date` object - representing the first day of the previous month. Unlike ``next_month``, - this will never be ``None``. + * ``previous_month``: A :class:`~datetime.date` object + representing the first day of the previous month, according to + :attr:`~BaseDateListView.allow_empty` and + :attr:`~DateMixin.allow_future`. **Notes** @@ -246,7 +260,7 @@ DayArchiveView TodayArchiveView ---------------- -.. class:: django.views.generic.dates.TodayArchiveView +.. class:: TodayArchiveView A day archive page showing all objects for *today*. This is exactly the same as :class:`django.views.generic.dates.DayArchiveView`, except today's @@ -271,7 +285,7 @@ TodayArchiveView DateDetailView -------------- -.. class:: django.views.generic.dates.DateDetailView +.. class:: DateDetailView A page representing an individual object. If the object has a date value in the future, the view will throw a 404 error by default, unless you set @@ -293,6 +307,22 @@ DateDetailView .. note:: - All of the generic views listed above have matching Base* views that only - differ in that the they do not include the - :class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`. + All of the generic views listed above have matching ``Base`` views that + only differ in that the they do not include the + :class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`: + + .. class:: BaseArchiveIndexView + + .. class:: BaseYearArchiveView + + .. class:: BaseMonthArchiveView + + .. class:: BaseWeekArchiveView + + .. class:: BaseDayArchiveView + + .. class:: BaseTodayArchiveView + + .. class:: BaseDateDetailView + + diff --git a/docs/ref/class-based-views/generic-display.txt b/docs/ref/class-based-views/generic-display.txt index ef3bc179ee..12603ff0df 100644 --- a/docs/ref/class-based-views/generic-display.txt +++ b/docs/ref/class-based-views/generic-display.txt @@ -15,6 +15,8 @@ DetailView **Ancestors (MRO)** + This view inherits methods and attributes from the following views: + * :class:`django.views.generic.detail.SingleObjectTemplateResponseMixin` * :class:`django.views.generic.base.TemplateResponseMixin` * :class:`django.views.generic.detail.BaseDetailView` @@ -71,7 +73,9 @@ ListView objects (usually, but not necessarily a queryset) that the view is operating upon. - **Mixins** + **Ancestors (MRO)** + + This view inherits methods and attributes from the following views: * :class:`django.views.generic.list.ListView` * :class:`django.views.generic.list.MultipleObjectTemplateResponseMixin` @@ -90,3 +94,54 @@ ListView 6. :meth:`get_context_data()` 7. :meth:`get()` 8. :meth:`render_to_response()` + + + **Example views.py**:: + + from django.views.generic.list import ListView + from django.utils import timezone + + from articles.models import Article + + class ArticleListView(ListView): + + model = Article + + def get_context_data(self, **kwargs): + context = super(ArticleListView, self).get_context_data(**kwargs) + context['now'] = timezone.now() + return context + + **Example urls.py**:: + + from django.conf.urls import patterns, url + + from article.views import ArticleListView + + urlpatterns = patterns('', + url(r'^$', ArticleListView.as_view(), name='article-list'), + ) + +.. class:: django.views.generic.list.BaseListView + + A base view for displaying a list of objects. It is not intended to be used + directly, but rather as a parent class of the + :class:`django.views.generic.list.ListView` or other views representing + lists of objects. + + **Ancestors (MRO)** + + This view inherits methods and attributes from the following views: + + * :class:`django.views.generic.list.MultipleObjectMixin` + * :class:`django.views.generic.base.View` + + **Methods** + + .. method:: get(request, *args, **kwargs) + + Adds :attr:`object_list` to the context. If + :attr:`~django.views.generic.list.MultipleObjectMixin.allow_empty` + is True then display an empty list. If + :attr:`~django.views.generic.list.MultipleObjectMixin.allow_empty` is + False then raise a 404 error. diff --git a/docs/ref/class-based-views/index.txt b/docs/ref/class-based-views/index.txt index f0e7bbc6c1..c4b632604a 100644 --- a/docs/ref/class-based-views/index.txt +++ b/docs/ref/class-based-views/index.txt @@ -23,7 +23,7 @@ it is safe to store state variables on the instance (i.e., ``self.foo = 3`` is a thread-safe operation). A class-based view is deployed into a URL pattern using the -:meth:`~View.as_view()` classmethod:: +:meth:`~django.views.generic.base.View.as_view()` classmethod:: urlpatterns = patterns('', (r'^view/$', MyView.as_view(size=42)), @@ -37,9 +37,10 @@ A class-based view is deployed into a URL pattern using the is modified, the actions of one user visiting your view could have an effect on subsequent users visiting the same view. -Any argument passed into :meth:`~View.as_view()` will be assigned onto the -instance that is used to service a request. Using the previous example, -this means that every request on ``MyView`` is able to use ``self.size``. +Any argument passed into :meth:`~django.views.generic.base.View.as_view()` will +be assigned onto the instance that is used to service a request. Using the +previous example, this means that every request on ``MyView`` is able to use +``self.size``. Base vs Generic views --------------------- diff --git a/docs/ref/class-based-views/mixins-date-based.txt b/docs/ref/class-based-views/mixins-date-based.txt index 6bf6f10b5d..01181ebb6c 100644 --- a/docs/ref/class-based-views/mixins-date-based.txt +++ b/docs/ref/class-based-views/mixins-date-based.txt @@ -2,11 +2,12 @@ Date-based mixins ================= +.. currentmodule:: django.views.generic.dates YearMixin --------- -.. class:: django.views.generic.dates.YearMixin +.. class:: YearMixin A mixin that can be used to retrieve and provide parsing information for a year component of a date. @@ -20,29 +21,45 @@ YearMixin .. attribute:: year - **Optional** The value for the year (as a string). By default, set to + **Optional** The value for the year, as a string. By default, set to ``None``, which means the year will be determined using other means. .. method:: get_year_format() - Returns the :func:`~time.strftime` format to use when parsing the year. Returns - :attr:`YearMixin.year_format` by default. + Returns the :func:`~time.strftime` format to use when parsing the + year. Returns :attr:`~YearMixin.year_format` by default. .. method:: get_year() - Returns the year for which this view will display data. Tries the - following sources, in order: + Returns the year for which this view will display data, as a string. + Tries the following sources, in order: * The value of the :attr:`YearMixin.year` attribute. - * The value of the `year` argument captured in the URL pattern + * The value of the `year` argument captured in the URL pattern. * The value of the `year` GET query argument. Raises a 404 if no valid year specification can be found. + .. method:: get_next_year(date) + + Returns a date object containing the first day of the year after the + date provided. This function can also return ``None`` or raise an + :class:`~django.http.Http404` exception, depending on the values of + :attr:`~BaseDateListView.allow_empty` and + :attr:`~DateMixin.allow_future`. + + .. method:: get_previous_year(date) + + Returns a date object containing the first day of the year before the + date provided. This function can also return ``None`` or raise an + :class:`~django.http.Http404` exception, depending on the values of + :attr:`~BaseDateListView.allow_empty` and + :attr:`~DateMixin.allow_future`. + MonthMixin ---------- -.. class:: django.views.generic.dates.MonthMixin +.. class:: MonthMixin A mixin that can be used to retrieve and provide parsing information for a month component of a date. @@ -51,26 +68,26 @@ MonthMixin .. attribute:: month_format - The :func:`~time.strftime` format to use when parsing the month. By default, this is - ``'%b'``. + The :func:`~time.strftime` format to use when parsing the month. By + default, this is ``'%b'``. .. attribute:: month - **Optional** The value for the month (as a string). By default, set to + **Optional** The value for the month, as a string. By default, set to ``None``, which means the month will be determined using other means. .. method:: get_month_format() - Returns the :func:`~time.strftime` format to use when parsing the month. Returns - :attr:`MonthMixin.month_format` by default. + Returns the :func:`~time.strftime` format to use when parsing the + month. Returns :attr:`~MonthMixin.month_format` by default. .. method:: get_month() - Returns the month for which this view will display data. Tries the - following sources, in order: + Returns the month for which this view will display data, as a string. + Tries the following sources, in order: * The value of the :attr:`MonthMixin.month` attribute. - * The value of the `month` argument captured in the URL pattern + * The value of the `month` argument captured in the URL pattern. * The value of the `month` GET query argument. Raises a 404 if no valid month specification can be found. @@ -78,20 +95,23 @@ MonthMixin .. method:: get_next_month(date) Returns a date object containing the first day of the month after the - date provided. Returns ``None`` if mixed with a view that sets - ``allow_future = False``, and the next month is in the future. If - ``allow_empty = False``, returns the next month that contains data. + date provided. This function can also return ``None`` or raise an + :class:`~django.http.Http404` exception, depending on the values of + :attr:`~BaseDateListView.allow_empty` and + :attr:`~DateMixin.allow_future`. .. method:: get_prev_month(date) Returns a date object containing the first day of the month before the - date provided. If ``allow_empty = False``, returns the previous month - that contained data. + date provided. This function can also return ``None`` or raise an + :class:`~django.http.Http404` exception, depending on the values of + :attr:`~BaseDateListView.allow_empty` and + :attr:`~DateMixin.allow_future`. DayMixin -------- -.. class:: django.views.generic.dates.DayMixin +.. class:: DayMixin A mixin that can be used to retrieve and provide parsing information for a day component of a date. @@ -100,46 +120,50 @@ DayMixin .. attribute:: day_format - The :func:`~time.strftime` format to use when parsing the day. By default, this is - ``'%d'``. + The :func:`~time.strftime` format to use when parsing the day. By + default, this is ``'%d'``. .. attribute:: day - **Optional** The value for the day (as a string). By default, set to + **Optional** The value for the day, as a string. By default, set to ``None``, which means the day will be determined using other means. .. method:: get_day_format() - Returns the :func:`~time.strftime` format to use when parsing the day. Returns - :attr:`DayMixin.day_format` by default. + Returns the :func:`~time.strftime` format to use when parsing the day. + Returns :attr:`~DayMixin.day_format` by default. .. method:: get_day() - Returns the day for which this view will display data. Tries the - following sources, in order: + Returns the day for which this view will display data, as a string. + Tries the following sources, in order: * The value of the :attr:`DayMixin.day` attribute. - * The value of the `day` argument captured in the URL pattern + * The value of the `day` argument captured in the URL pattern. * The value of the `day` GET query argument. Raises a 404 if no valid day specification can be found. .. method:: get_next_day(date) - Returns a date object containing the next day after the date provided. - Returns ``None`` if mixed with a view that sets ``allow_future = False``, - and the next day is in the future. If ``allow_empty = False``, returns - the next day that contains data. + Returns a date object containing the next valid day after the date + provided. This function can also return ``None`` or raise an + :class:`~django.http.Http404` exception, depending on the values of + :attr:`~BaseDateListView.allow_empty` and + :attr:`~DateMixin.allow_future`. .. method:: get_prev_day(date) - Returns a date object containing the previous day. If - ``allow_empty = False``, returns the previous day that contained data. + Returns a date object containing the previous valid day. This function + can also return ``None`` or raise an :class:`~django.http.Http404` + exception, depending on the values of + :attr:`~BaseDateListView.allow_empty` and + :attr:`~DateMixin.allow_future`. WeekMixin --------- -.. class:: django.views.generic.dates.WeekMixin +.. class:: WeekMixin A mixin that can be used to retrieve and provide parsing information for a week component of a date. @@ -148,23 +172,24 @@ WeekMixin .. attribute:: week_format - The :func:`~time.strftime` format to use when parsing the week. By default, this is - ``'%U'``. + The :func:`~time.strftime` format to use when parsing the week. By + default, this is ``'%U'``, which means the week starts on Sunday. Set + it to ``'%W'`` if your week starts on Monday. .. attribute:: week - **Optional** The value for the week (as a string). By default, set to + **Optional** The value for the week, as a string. By default, set to ``None``, which means the week will be determined using other means. .. method:: get_week_format() - Returns the :func:`~time.strftime` format to use when parsing the week. Returns - :attr:`WeekMixin.week_format` by default. + Returns the :func:`~time.strftime` format to use when parsing the + week. Returns :attr:`~WeekMixin.week_format` by default. .. method:: get_week() - Returns the week for which this view will display data. Tries the - following sources, in order: + Returns the week for which this view will display data, as a string. + Tries the following sources, in order: * The value of the :attr:`WeekMixin.week` attribute. * The value of the `week` argument captured in the URL pattern @@ -172,11 +197,26 @@ WeekMixin Raises a 404 if no valid week specification can be found. + .. method:: get_next_week(date) + + Returns a date object containing the first day of the week after the + date provided. This function can also return ``None`` or raise an + :class:`~django.http.Http404` exception, depending on the values of + :attr:`~BaseDateListView.allow_empty` and + :attr:`~DateMixin.allow_future`. + + .. method:: get_prev_week(date) + + Returns a date object containing the first day of the week before the + date provided. This function can also return ``None`` or raise an + :class:`~django.http.Http404` exception, depending on the values of + :attr:`~BaseDateListView.allow_empty` and + :attr:`~DateMixin.allow_future`. DateMixin --------- -.. class:: django.views.generic.dates.DateMixin +.. class:: DateMixin A mixin class providing common behavior for all date-based views. @@ -186,7 +226,7 @@ DateMixin The name of the ``DateField`` or ``DateTimeField`` in the ``QuerySet``'s model that the date-based archive should use to - determine the objects on the page. + determine the list of objects to display on the page. When :doc:`time zone support ` is enabled and ``date_field`` is a ``DateTimeField``, dates are assumed to be in the @@ -210,26 +250,26 @@ DateMixin .. method:: get_date_field() Returns the name of the field that contains the date data that this - view will operate on. Returns :attr:`DateMixin.date_field` by default. + view will operate on. Returns :attr:`~DateMixin.date_field` by default. .. method:: get_allow_future() Determine whether to include "future" objects on this page, where "future" means objects in which the field specified in ``date_field`` is greater than the current date/time. Returns - :attr:`DateMixin.allow_future` by default. + :attr:`~DateMixin.allow_future` by default. BaseDateListView ---------------- -.. class:: django.views.generic.dates.BaseDateListView +.. class:: BaseDateListView A base class that provides common behavior for all date-based views. There won't normally be a reason to instantiate :class:`~django.views.generic.dates.BaseDateListView`; instantiate one of the subclasses instead. - While this view (and it's subclasses) are executing, ``self.object_list`` + While this view (and its subclasses) are executing, ``self.object_list`` will contain the list of objects that the view is operating upon, and ``self.date_list`` will contain the list of dates for which data is available. @@ -245,10 +285,18 @@ BaseDateListView A boolean specifying whether to display the page if no objects are available. If this is ``True`` and no objects are available, the view - will display an empty page instead of raising a 404. By default, this - is ``False``. + will display an empty page instead of raising a 404. - .. method:: get_dated_items(): + This is identical to :attr:`MultipleObjectMixin.allow_empty`, except + for the default value, which is ``False``. + + .. attribute:: date_list_period + + **Optional** A string defining the aggregation period for + ``date_list``. It must be one of ``'year'`` (default), ``'month'``, or + ``'day'``. + + .. method:: get_dated_items() Returns a 3-tuple containing (``date_list``, ``object_list``, ``extra_context``). @@ -265,10 +313,17 @@ BaseDateListView ``lookup``. Enforces any restrictions on the queryset, such as ``allow_empty`` and ``allow_future``. - .. method:: get_date_list(queryset, date_type) + .. method:: get_date_list_period() - Returns the list of dates of type ``date_type`` for which - ``queryset`` contains entries. For example, ``get_date_list(qs, - 'year')`` will return the list of years for which ``qs`` has entries. - See :meth:`~django.db.models.query.QuerySet.dates()` for the - ways that the ``date_type`` argument can be used. + Returns the aggregation period for ``date_list``. Returns + :attr:`~BaseDateListView.date_list_period` by default. + + .. method:: get_date_list(queryset, date_type=None) + + Returns the list of dates of type ``date_type`` for which ``queryset`` + contains entries. For example, ``get_date_list(qs, 'year')`` will + return the list of years for which ``qs`` has entries. If + ``date_type`` isn't provided, the result of + :meth:`BaseDateListView.get_date_list_period` is used. See + :meth:`~django.db.models.query.QuerySet.dates()` for the ways that the + ``date_type`` argument can be used. diff --git a/docs/ref/class-based-views/mixins-multiple-object.txt b/docs/ref/class-based-views/mixins-multiple-object.txt index 8bc613b887..cdb743fcbd 100644 --- a/docs/ref/class-based-views/mixins-multiple-object.txt +++ b/docs/ref/class-based-views/mixins-multiple-object.txt @@ -86,7 +86,8 @@ MultipleObjectMixin .. method:: get_queryset() - Returns the queryset that represents the data this view will display. + Get the list of items for this view. This must be an iterable and may + be a queryset (in which queryset-specific behavior will be enabled). .. method:: paginate_queryset(queryset, page_size) diff --git a/docs/ref/class-based-views/mixins-simple.txt b/docs/ref/class-based-views/mixins-simple.txt index 61fc945cd3..d2f0df241e 100644 --- a/docs/ref/class-based-views/mixins-simple.txt +++ b/docs/ref/class-based-views/mixins-simple.txt @@ -9,16 +9,17 @@ ContextMixin .. versionadded:: 1.5 - **classpath** - - ``django.views.generic.base.ContextMixin`` - **Methods** .. method:: get_context_data(**kwargs) Returns a dictionary representing the template context. The keyword - arguments provided will make up the returned context. + arguments provided will make up the returned context. Example usage:: + + def get_context_data(self, **kwargs): + context = super(RandomNumberView, self).get_context_data(**kwargs) + context['number'] = random.randrange(1, 100) + return context The template context of all class-based generic views include a ``view`` variable that points to the ``View`` instance. @@ -42,7 +43,13 @@ TemplateResponseMixin suitable context. The template to use is configurable and can be further customized by subclasses. - **Methods and Attributes** + **Attributes** + + .. attribute:: template_name + + The full name of a template to use as defined by a string. Not defining + a template_name will raise a + :class:`django.core.exceptions.ImproperlyConfigured` exception. .. attribute:: response_class @@ -57,12 +64,14 @@ TemplateResponseMixin instantiation, create a ``TemplateResponse`` subclass and assign it to ``response_class``. + **Methods** + .. method:: render_to_response(context, **response_kwargs) Returns a ``self.response_class`` instance. - If any keyword arguments are provided, they will be - passed to the constructor of the response class. + If any keyword arguments are provided, they will be passed to the + constructor of the response class. Calls :meth:`~TemplateResponseMixin.get_template_names()` to obtain the list of template names that will be searched looking for an existent diff --git a/docs/ref/contrib/comments/moderation.txt b/docs/ref/contrib/comments/moderation.txt index 4f4b326cb2..f03c7fda0d 100644 --- a/docs/ref/contrib/comments/moderation.txt +++ b/docs/ref/contrib/comments/moderation.txt @@ -32,11 +32,11 @@ A simple example is the best illustration of this. Suppose we have the following model, which would represent entries in a Weblog:: from django.db import models - + class Entry(models.Model): title = models.CharField(maxlength=250) body = models.TextField() - pub_date = models.DateTimeField() + pub_date = models.DateField() enable_comments = models.BooleanField() Now, suppose that we want the following steps to be applied whenever a @@ -55,11 +55,11 @@ Accomplishing this is fairly straightforward and requires very little code:: from django.contrib.comments.moderation import CommentModerator, moderator - + class EntryModerator(CommentModerator): email_notification = True enable_field = 'enable_comments' - + moderator.register(Entry, EntryModerator) The :class:`CommentModerator` class pre-defines a number of useful moderation diff --git a/docs/ref/contrib/gis/install.txt b/docs/ref/contrib/gis/install.txt index 3e952c173b..b815973202 100644 --- a/docs/ref/contrib/gis/install.txt +++ b/docs/ref/contrib/gis/install.txt @@ -80,7 +80,7 @@ geospatial libraries: Program Description Required Supported Versions ======================== ==================================== ================================ ========================== :ref:`GEOS ` Geometry Engine Open Source Yes 3.3, 3.2, 3.1, 3.0 -`PROJ.4`_ Cartographic Projections library Yes (PostgreSQL and SQLite only) 4.7, 4.6, 4.5, 4.4 +`PROJ.4`_ Cartographic Projections library Yes (PostgreSQL and SQLite only) 4.8, 4.7, 4.6, 4.5, 4.4 :ref:`GDAL ` Geospatial Data Abstraction Library No (but, required for SQLite) 1.9, 1.8, 1.7, 1.6, 1.5 :ref:`GeoIP ` IP-based geolocation library No 1.4 `PostGIS`__ Spatial extensions for PostgreSQL Yes (PostgreSQL only) 1.5, 1.4, 1.3 @@ -140,16 +140,16 @@ internal geometry representation used by GeoDjango (it's behind the "lazy" geometries). Specifically, the C API library is called (e.g., ``libgeos_c.so``) directly from Python using ctypes. -First, download GEOS 3.3.0 from the refractions Web site and untar the source +First, download GEOS 3.3.5 from the refractions Web site and untar the source archive:: - $ wget http://download.osgeo.org/geos/geos-3.3.0.tar.bz2 - $ tar xjf geos-3.3.0.tar.bz2 + $ wget http://download.osgeo.org/geos/geos-3.3.5.tar.bz2 + $ tar xjf geos-3.3.5.tar.bz2 Next, change into the directory where GEOS was unpacked, run the configure script, compile, and install:: - $ cd geos-3.3.0 + $ cd geos-3.3.5 $ ./configure $ make $ sudo make install @@ -203,15 +203,15 @@ reference systems. First, download the PROJ.4 source code and datum shifting files [#]_:: - $ wget http://download.osgeo.org/proj/proj-4.7.0.tar.gz - $ wget http://download.osgeo.org/proj/proj-datumgrid-1.5.zip + $ wget http://download.osgeo.org/proj/proj-4.8.0.tar.gz + $ wget http://download.osgeo.org/proj/proj-datumgrid-1.5.tar.gz Next, untar the source code archive, and extract the datum shifting files in the ``nad`` subdirectory. This must be done *prior* to configuration:: - $ tar xzf proj-4.7.0.tar.gz - $ cd proj-4.7.0/nad - $ unzip ../../proj-datumgrid-1.5.zip + $ tar xzf proj-4.8.0.tar.gz + $ cd proj-4.8.0/nad + $ tar xzf ../../proj-datumgrid-1.5.tar.gz $ cd .. Finally, configure, make and install PROJ.4:: @@ -239,9 +239,9 @@ installed prior to building PostGIS. First download the source archive, and extract:: - $ wget http://postgis.refractions.net/download/postgis-1.5.2.tar.gz - $ tar xzf postgis-1.5.2.tar.gz - $ cd postgis-1.5.2 + $ wget http://postgis.refractions.net/download/postgis-1.5.5.tar.gz + $ tar xzf postgis-1.5.5.tar.gz + $ cd postgis-1.5.5 Next, configure, make and install PostGIS:: @@ -959,15 +959,15 @@ Ubuntu & Debian GNU/Linux Ubuntu ^^^^^^ -11.10 -~~~~~ +11.10 through 12.04 +~~~~~~~~~~~~~~~~~~~ -In Ubuntu 11.10, PostgreSQL was upgraded to 9.1. The installation commands are: +In Ubuntu 11.10, PostgreSQL was upgraded to 9.1. The installation command is: .. code-block:: bash - $ sudo apt-get install binutils gdal-bin libproj-dev postgresql-9.1-postgis \ - postgresql-server-dev-9.1 python-psycopg2 + $ sudo apt-get install binutils gdal-bin libproj-dev \ + postgresql-9.1-postgis postgresql-server-dev-9.1 python-psycopg2 .. _ubuntu10: @@ -976,7 +976,7 @@ In Ubuntu 11.10, PostgreSQL was upgraded to 9.1. The installation commands are: In Ubuntu 10.04, PostgreSQL was upgraded to 8.4 and GDAL was upgraded to 1.6. Ubuntu 10.04 uses PostGIS 1.4, while Ubuntu 10.10 uses PostGIS 1.5 (with -geography support). The installation commands are: +geography support). The installation command is: .. code-block:: bash diff --git a/docs/ref/contrib/markup.txt b/docs/ref/contrib/markup.txt index 8f3e0a95f9..9215c64f93 100644 --- a/docs/ref/contrib/markup.txt +++ b/docs/ref/contrib/markup.txt @@ -5,6 +5,9 @@ django.contrib.markup .. module:: django.contrib.markup :synopsis: A collection of template filters that implement common markup languages. +.. deprecated:: 1.5 + This module has been deprecated. + Django provides template filters that implement the following markup languages: diff --git a/docs/ref/contrib/messages.txt b/docs/ref/contrib/messages.txt index 4cf90ee381..bc921a9d33 100644 --- a/docs/ref/contrib/messages.txt +++ b/docs/ref/contrib/messages.txt @@ -5,14 +5,16 @@ The messages framework .. module:: django.contrib.messages :synopsis: Provides cookie- and session-based temporary message storage. -Quite commonly in web applications, you may need to display a one-time -notification message (also know as "flash message") to the user after -processing a form or some other types of user input. For this, Django provides -full support for cookie- and session-based messaging, for both anonymous and -authenticated users. The messages framework allows you to temporarily store -messages in one request and retrieve them for display in a subsequent request -(usually the next one). Every message is tagged with a specific ``level`` that -determines its priority (e.g., ``info``, ``warning``, or ``error``). +Quite commonly in web applications, you need to display a one-time +notification message (also known as "flash message") to the user after +processing a form or some other types of user input. + +For this, Django provides full support for cookie- and session-based +messaging, for both anonymous and authenticated users. The messages framework +allows you to temporarily store messages in one request and retrieve them for +display in a subsequent request (usually the next one). Every message is +tagged with a specific ``level`` that determines its priority (e.g., ``info``, +``warning``, or ``error``). Enabling messages ================= @@ -20,32 +22,27 @@ Enabling messages Messages are implemented through a :doc:`middleware ` class and corresponding :doc:`context processor `. -To enable message functionality, do the following: +The default ``settings.py`` created by ``django-admin.py startproject`` +already contains all the settings required to enable message functionality: -* Edit the :setting:`MIDDLEWARE_CLASSES` setting and make sure - it contains ``'django.contrib.messages.middleware.MessageMiddleware'``. +* ``'django.contrib.messages'`` is in :setting:`INSTALLED_APPS`. - If you are using a :ref:`storage backend ` that - relies on :doc:`sessions ` (the default), - ``'django.contrib.sessions.middleware.SessionMiddleware'`` must be - enabled and appear before ``MessageMiddleware`` in your +* :setting:`MIDDLEWARE_CLASSES` contains + ``'django.contrib.sessions.middleware.SessionMiddleware'`` and + ``'django.contrib.messages.middleware.MessageMiddleware'``. + + The default :ref:`storage backend ` relies on + :doc:`sessions `. That's why ``SessionMiddleware`` + must be enabled and appear before ``MessageMiddleware`` in :setting:`MIDDLEWARE_CLASSES`. -* Edit the :setting:`TEMPLATE_CONTEXT_PROCESSORS` setting and make sure - it contains ``'django.contrib.messages.context_processors.messages'``. +* :setting:`TEMPLATE_CONTEXT_PROCESSORS` contains + ``'django.contrib.messages.context_processors.messages'``. -* Add ``'django.contrib.messages'`` to your :setting:`INSTALLED_APPS` - setting - -The default ``settings.py`` created by ``django-admin.py startproject`` has -``MessageMiddleware`` activated and the ``django.contrib.messages`` app -installed. Also, the default value for :setting:`TEMPLATE_CONTEXT_PROCESSORS` -contains ``'django.contrib.messages.context_processors.messages'``. - -If you don't want to use messages, you can remove the -``MessageMiddleware`` line from :setting:`MIDDLEWARE_CLASSES`, the ``messages`` -context processor from :setting:`TEMPLATE_CONTEXT_PROCESSORS` and -``'django.contrib.messages'`` from your :setting:`INSTALLED_APPS`. +If you don't want to use messages, you can remove +``'django.contrib.messages'`` from your :setting:`INSTALLED_APPS`, the +``MessageMiddleware`` line from :setting:`MIDDLEWARE_CLASSES`, and the +``messages`` context processor from :setting:`TEMPLATE_CONTEXT_PROCESSORS`. Configuring the message engine ============================== @@ -56,34 +53,35 @@ Storage backends ---------------- The messages framework can use different backends to store temporary messages. -If the default FallbackStorage isn't suitable to your needs, you can change -which backend is being used by adding a `MESSAGE_STORAGE`_ to your -settings, referencing the module and class of the storage class. For -example:: - MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage' +Django provides three built-in storage classes: -The value should be the full path of the desired storage class. +.. class:: django.contrib.messages.storage.session.SessionStorage -Three storage classes are available: + This class stores all messages inside of the request's session. Therefore + it requires Django's ``contrib.sessions`` application. -``'django.contrib.messages.storage.session.SessionStorage'`` - This class stores all messages inside of the request's session. It - requires Django's ``contrib.sessions`` application. +.. class:: django.contrib.messages.storage.cookie.CookieStorage -``'django.contrib.messages.storage.cookie.CookieStorage'`` This class stores the message data in a cookie (signed with a secret hash to prevent manipulation) to persist notifications across requests. Old - messages are dropped if the cookie data size would exceed 4096 bytes. + messages are dropped if the cookie data size would exceed 2048 bytes. -``'django.contrib.messages.storage.fallback.FallbackStorage'`` - This is the default storage class. +.. class:: django.contrib.messages.storage.fallback.FallbackStorage - This class first uses CookieStorage for all messages, falling back to using - SessionStorage for the messages that could not fit in a single cookie. + This class first uses ``CookieStorage``, and falls back to using + ``SessionStorage`` for the messages that could not fit in a single cookie. + It also requires Django's ``contrib.sessions`` application. - Since it is uses SessionStorage, it also requires Django's - ``contrib.sessions`` application. + This behavior avoids writing to the session whenever possible. It should + provide the best performance in the general case. + +:class:`~django.contrib.messages.storage.fallback.FallbackStorage` is the +default storage class. If it isn't suitable to your needs, you can select +another storage class by setting `MESSAGE_STORAGE`_ to its full import path, +for example:: + + MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage' To write your own storage class, subclass the ``BaseStorage`` class in ``django.contrib.messages.storage.base`` and implement the ``_get`` and @@ -97,8 +95,8 @@ to that of the Python logging module. Message levels allow you to group messages by type so they can be filtered or displayed differently in views and templates. -The built-in levels (which can be imported from ``django.contrib.messages`` -directly) are: +The built-in levels, which can be imported from ``django.contrib.messages`` +directly, are: =========== ======== Constant Purpose diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index 082ec17a35..2a8f449799 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -398,11 +398,21 @@ For each field, we describe the default widget used if you don't specify If no ``input_formats`` argument is provided, the default input formats are:: - '%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06' - '%b %d %Y', '%b %d, %Y', # 'Oct 25 2006', 'Oct 25, 2006' - '%d %b %Y', '%d %b, %Y', # '25 Oct 2006', '25 Oct, 2006' - '%B %d %Y', '%B %d, %Y', # 'October 25 2006', 'October 25, 2006' - '%d %B %Y', '%d %B, %Y', # '25 October 2006', '25 October, 2006' + '%Y-%m-%d', # '2006-10-25' + '%m/%d/%Y', # '10/25/2006' + '%m/%d/%y', # '10/25/06' + + Additionally, if you specify :setting:`USE_L10N=False` in your settings, the + following will also be included in the default input formats:: + + '%b %m %d', # 'Oct 25 2006' + '%b %d, %Y', # 'Oct 25, 2006' + '%d %b %Y', # '25 Oct 2006' + '%d %b, %Y', # '25 Oct, 2006' + '%B %d %Y', # 'October 25 2006' + '%B %d, %Y', # 'October 25, 2006' + '%d %B %Y', # '25 October 2006' + '%d %B, %Y', # '25 October, 2006' ``DateTimeField`` ~~~~~~~~~~~~~~~~~ diff --git a/docs/ref/forms/validation.txt b/docs/ref/forms/validation.txt index 95424d0cd0..1af32da875 100644 --- a/docs/ref/forms/validation.txt +++ b/docs/ref/forms/validation.txt @@ -70,9 +70,8 @@ overridden: formfield-specific piece of validation and, possibly, cleaning/normalizing the data. - Just like the general field ``clean()`` method, above, this method - should return the cleaned data, regardless of whether it changed - anything or not. + This method should return the cleaned value obtained from cleaned_data, + regardless of whether it changed anything or not. * The Form subclass's ``clean()`` method. This method can perform any validation that requires access to multiple fields from the form at diff --git a/docs/ref/forms/widgets.txt b/docs/ref/forms/widgets.txt index fb7657349a..eab314a4cd 100644 --- a/docs/ref/forms/widgets.txt +++ b/docs/ref/forms/widgets.txt @@ -310,6 +310,10 @@ commonly used groups of widgets: A callable that takes the value of the CheckBoxInput and returns ``True`` if the checkbox should be checked for that value. + .. versionchanged:: 1.5 + Exceptions from ``check_test`` used to be silenced by its caller, + this is no longer the case, they will propagate upwards. + ``Select`` ~~~~~~~~~~ diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index 275c696230..8b3c31f029 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -195,6 +195,14 @@ support tablespaces for indexes, this option is ignored. The default value for the field. This can be a value or a callable object. If callable it will be called every time a new object is created. +The default cannot be a mutable object (model instance, list, set, etc.), as a +reference to the same instance of that object would be used as the default +value in all new model instances. Instead, wrap the desired default in a +callable. For example, if you had a custom ``JSONField`` and wanted to specify +a dictionary as the default, use a ``lambda`` as follows:: + + contact_info = JSONField("ContactInfo", default=lambda:{"email": "to1@example.com"}) + ``editable`` ------------ @@ -983,10 +991,10 @@ define the details of how the relation works. this with functions from the Python ``datetime`` module to limit choices of objects by date. For example:: - limit_choices_to = {'pub_date__lte': datetime.now} + limit_choices_to = {'pub_date__lte': datetime.date.today} only allows the choice of related objects with a ``pub_date`` before the - current date/time to be chosen. + current date to be chosen. Instead of a dictionary this can also be a :class:`~django.db.models.Q` object for more :ref:`complex queries `. However, diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt index 472ac96457..2fdc87df8c 100644 --- a/docs/ref/models/instances.txt +++ b/docs/ref/models/instances.txt @@ -135,7 +135,7 @@ access to more than a single field:: raise ValidationError('Draft entries may not have a publication date.') # Set the pub_date for published items if it hasn't been set already. if self.status == 'published' and self.pub_date is None: - self.pub_date = datetime.datetime.now() + self.pub_date = datetime.date.today() Any :exc:`~django.core.exceptions.ValidationError` exceptions raised by ``Model.clean()`` will be stored in a special key error dictionary key, diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt index 9d076f6274..c5ae8398ea 100644 --- a/docs/ref/models/options.txt +++ b/docs/ref/models/options.txt @@ -83,9 +83,10 @@ Django quotes column and table names behind the scenes. .. attribute:: Options.get_latest_by - The name of a :class:`DateField` or :class:`DateTimeField` in the model. - This specifies the default field to use in your model :class:`Manager`'s - :class:`~QuerySet.latest` method. + The name of an orderable field in the model, typically a :class:`DateField`, + :class:`DateTimeField`, or :class:`IntegerField`. This specifies the default + field to use in your model :class:`Manager`'s :class:`~QuerySet.latest` + method. Example:: diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 4f5f8858b5..80b3158f01 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -31,6 +31,9 @@ You can evaluate a ``QuerySet`` in the following ways: for e in Entry.objects.all(): print(e.headline) + Note: Don't use this if all you want to do is determine if at least one + result exists. It's more efficient to use :meth:`~QuerySet.exists`. + * **Slicing.** As explained in :ref:`limiting-querysets`, a ``QuerySet`` can be sliced, using Python's array-slicing syntax. Slicing an unevaluated ``QuerySet`` usually returns another unevaluated ``QuerySet``, but Django @@ -75,7 +78,7 @@ You can evaluate a ``QuerySet`` in the following ways: Note: *Don't* use this if all you want to do is determine if at least one result exists, and don't need the actual objects. It's more efficient to - use :meth:`exists() ` (see below). + use :meth:`~QuerySet.exists` (see below). .. _pickling QuerySets: @@ -1523,9 +1526,40 @@ exists Returns ``True`` if the :class:`.QuerySet` contains any results, and ``False`` if not. This tries to perform the query in the simplest and fastest way -possible, but it *does* execute nearly the same query. This means that calling -:meth:`.QuerySet.exists` is faster than ``bool(some_query_set)``, but not by -a large degree. If ``some_query_set`` has not yet been evaluated, but you know +possible, but it *does* execute nearly the same query as a normal +:class:`.QuerySet` query. + +:meth:`~.QuerySet.exists` is useful for searches relating to both +object membership in a :class:`.QuerySet` and to the existence of any objects in +a :class:`.QuerySet`, particularly in the context of a large :class:`.QuerySet`. + +The most efficient method of finding whether a model with a unique field +(e.g. ``primary_key``) is a member of a :class:`.QuerySet` is:: + + entry = Entry.objects.get(pk=123) + if some_query_set.filter(pk=entry.pk).exists(): + print("Entry contained in queryset") + +Which will be faster than the following which requires evaluating and iterating +through the entire queryset:: + + if entry in some_query_set: + print("Entry contained in QuerySet") + +And to find whether a queryset contains any items:: + + if some_query_set.exists(): + print("There is at least one object in some_query_set") + +Which will be faster than:: + + if some_query_set: + print("There is at least one object in some_query_set") + +... but not by a large degree (hence needing a large queryset for efficiency +gains). + +Additionally, if a ``some_query_set`` has not yet been evaluated, but you know that it will be at some point, then using ``some_query_set.exists()`` will do more overall work (one query for the existence check plus an extra one to later retrieve the results) than simply using ``bool(some_query_set)``, which @@ -1945,6 +1979,17 @@ SQL equivalent:: You can use ``range`` anywhere you can use ``BETWEEN`` in SQL — for dates, numbers and even characters. +.. warning:: + + Filtering a ``DateTimeField`` with dates won't include items on the last + day, because the bounds are interpreted as "0am on the given date". If + ``pub_date`` was a ``DateTimeField``, the above expression would be turned + into this SQL:: + + SELECT ... WHERE pub_date BETWEEN '2005-01-01 00:00:00' and '2005-03-31 00:00:00'; + + Generally speaking, you can't mix dates and datetimes. + .. fieldlookup:: year year @@ -1958,7 +2003,7 @@ Example:: SQL equivalent:: - SELECT ... WHERE pub_date BETWEEN '2005-01-01' AND '2005-12-31 23:59:59.999999'; + SELECT ... WHERE pub_date BETWEEN '2005-01-01' AND '2005-12-31'; (The exact SQL syntax varies for each database engine.) diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index d15c5f0669..8ab3f90a63 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -1313,25 +1313,13 @@ The URL where requests are redirected after login when the This is used by the :func:`~django.contrib.auth.decorators.login_required` decorator, for example. -.. _`note on LOGIN_REDIRECT_URL setting`: +.. versionchanged:: 1.5 -.. note:: - You can use :func:`~django.core.urlresolvers.reverse_lazy` to reference - URLs by their name instead of providing a hardcoded value. Assuming a - ``urls.py`` with an URLpattern named ``home``:: - - urlpatterns = patterns('', - url('^welcome/$', 'test_app.views.home', name='home'), - ) - - You can use :func:`~django.core.urlresolvers.reverse_lazy` like this:: - - from django.core.urlresolvers import reverse_lazy - - LOGIN_REDIRECT_URL = reverse_lazy('home') - - This also works fine with localized URLs using - :func:`~django.conf.urls.i18n.i18n_patterns`. +This setting now also accepts view function names and +:ref:`named URL patterns ` which can be used to reduce +configuration duplication since you no longer have to define the URL in two +places (``settings`` and URLconf). +For backward compatibility reasons the default remains unchanged. .. setting:: LOGIN_URL @@ -1343,8 +1331,13 @@ Default: ``'/accounts/login/'`` The URL where requests are redirected for login, especially when using the :func:`~django.contrib.auth.decorators.login_required` decorator. -.. note:: - See the `note on LOGIN_REDIRECT_URL setting`_ +.. versionchanged:: 1.5 + +This setting now also accepts view function names and +:ref:`named URL patterns ` which can be used to reduce +configuration duplication since you no longer have to define the URL in two +places (``settings`` and URLconf). +For backward compatibility reasons the default remains unchanged. .. setting:: LOGOUT_URL diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index 5728d8559a..6420239f47 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -121,6 +121,12 @@ Django 1.5 also includes several smaller improvements worth noting: argument. By default the batch_size is unlimited except for SQLite where single batch is limited so that 999 parameters per query isn't exceeded. +* The :setting:`LOGIN_URL` and :setting:`LOGIN_REDIRECT_URL` settings now also + accept view function names and + :ref:`named URL patterns `. This allows you to reduce + configuration duplication. More information can be found in the + :func:`~django.contrib.auth.decorators.login_required` documentation. + Backwards incompatible changes in 1.5 ===================================== @@ -358,3 +364,11 @@ the built-in :func:`itertools.product` instead. The :class:`~django.utils.encoding.StrAndUnicode` mix-in has been deprecated. Define a ``__str__`` method and apply the :func:`~django.utils.encoding.python_2_unicode_compatible` decorator instead. + +``django.utils.markup`` +~~~~~~~~~~~~~~~~~~~~~~~ + +The markup contrib module has been deprecated and will follow an accelerated +deprecation schedule. Direct use of python markup libraries or 3rd party tag +libraries is preferred to Django maintaining this functionality in the +framework. diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt index d85cf25f64..c141d9eb15 100644 --- a/docs/topics/auth.txt +++ b/docs/topics/auth.txt @@ -951,6 +951,13 @@ The login_required decorator (r'^accounts/login/$', 'django.contrib.auth.views.login'), + .. versionchanged:: 1.5 + + As of version 1.5 :setting:`settings.LOGIN_URL ` now also accepts + view function names and :ref:`named URL patterns `. + This allows you to freely remap your login view within your URLconf + without having to update the setting. + .. function:: views.login(request, [template_name, redirect_field_name, authentication_form]) **URL name:** ``login`` diff --git a/docs/topics/class-based-views/index.txt b/docs/topics/class-based-views/index.txt index 6637bd5fcb..2d3e00ab4c 100644 --- a/docs/topics/class-based-views/index.txt +++ b/docs/topics/class-based-views/index.txt @@ -84,6 +84,50 @@ function-like entry to class-based views:: For more information on how to use the built in generic views, consult the next topic on :doc:`generic class based views`. +.. _supporting-other-http-methods: + +Supporting other HTTP methods +----------------------------- + +Suppose somebody wants to access our book library over HTTP using the views +as an API. The API client would connect every now and then and download book +data for the books published since last visit. But if no new books appeared +since then, it is a waste of CPU time and bandwidth to fetch the books from the +database, render a full response and send it to the client. It might be +preferable to ask the API when the most recent book was published. + +We map the URL to book list view in the URLconf:: + + from django.conf.urls import patterns + from books.views import BookListView + + urlpatterns = patterns('', + (r'^books/$', BookListView.as_view()), + ) + +And the view:: + + from django.http import HttpResponse + from django.views.generic import ListView + from books.models import Book + + class BookListView(ListView): + model = Book + + def head(self, *args, **kwargs): + last_book = self.get_queryset().latest('publication_date') + response = HttpResponse('') + # RFC 1123 date format + response['Last-Modified'] = last_book.publication_date.strftime('%a, %d %b %Y %H:%M:%S GMT') + return response + +If the view is accessed from a ``GET`` request, a plain-and-simple object +list is returned in the response (using ``book_list.html`` template). But if +the client issues a ``HEAD`` request, the response has an empty body and +the ``Last-Modified`` header indicates when the most recent book was published. +Based on this information, the client may or may not download the full object +list. + Decorating class-based views ============================ diff --git a/docs/topics/db/examples/many_to_one.txt b/docs/topics/db/examples/many_to_one.txt index 0a9978b8d1..c869362d16 100644 --- a/docs/topics/db/examples/many_to_one.txt +++ b/docs/topics/db/examples/many_to_one.txt @@ -42,8 +42,8 @@ Create a few Reporters:: Create an Article:: - >>> from datetime import datetime - >>> a = Article(id=None, headline="This is a test", pub_date=datetime(2005, 7, 27), reporter=r) + >>> from datetime import date + >>> a = Article(id=None, headline="This is a test", pub_date=date(2005, 7, 27), reporter=r) >>> a.save() >>> a.reporter.id @@ -65,7 +65,7 @@ database, which always returns unicode strings):: Create an Article via the Reporter object:: - >>> new_article = r.article_set.create(headline="John's second story", pub_date=datetime(2005, 7, 29)) + >>> new_article = r.article_set.create(headline="John's second story", pub_date=date(2005, 7, 29)) >>> new_article >>> new_article.reporter @@ -75,7 +75,7 @@ Create an Article via the Reporter object:: Create a new article, and add it to the article set:: - >>> new_article2 = Article(headline="Paul's story", pub_date=datetime(2006, 1, 17)) + >>> new_article2 = Article(headline="Paul's story", pub_date=date(2006, 1, 17)) >>> r.article_set.add(new_article2) >>> new_article2.reporter diff --git a/docs/topics/db/multi-db.txt b/docs/topics/db/multi-db.txt index 03a7d3b7cd..d2ff8645a9 100644 --- a/docs/topics/db/multi-db.txt +++ b/docs/topics/db/multi-db.txt @@ -201,73 +201,129 @@ An example write to propagate to the slaves). It also doesn't consider the interaction of transactions with the database utilization strategy. -So - what does this mean in practice? Say you want ``myapp`` to -exist on the ``other`` database, and you want all other models in a -master/slave relationship between the databases ``master``, ``slave1`` and -``slave2``. To implement this, you would need 2 routers:: +So - what does this mean in practice? Let's consider another sample +configuration. This one will have several databases: one for the +``auth`` application, and all other apps using a master/slave setup +with two read slaves. Here are the settings specifying these +databases:: - class MyAppRouter(object): - """A router to control all database operations on models in - the myapp application""" + DATABASES = { + 'auth_db': { + 'NAME': 'auth_db', + 'ENGINE': 'django.db.backends.mysql', + 'USER': 'mysql_user', + 'PASSWORD': 'swordfish', + }, + 'master': { + 'NAME': 'master', + 'ENGINE': 'django.db.backends.mysql', + 'USER': 'mysql_user', + 'PASSWORD': 'spam', + }, + 'slave1': { + 'NAME': 'slave1', + 'ENGINE': 'django.db.backends.mysql', + 'USER': 'mysql_user', + 'PASSWORD': 'eggs', + }, + 'slave2': { + 'NAME': 'slave2', + 'ENGINE': 'django.db.backends.mysql', + 'USER': 'mysql_user', + 'PASSWORD': 'bacon', + }, + } +Now we'll need to handle routing. First we want a router that knows to +send queries for the ``auth`` app to ``auth_db``:: + + class AuthRouter(object): + """ + A router to control all database operations on models in the + auth application. + """ def db_for_read(self, model, **hints): - "Point all operations on myapp models to 'other'" - if model._meta.app_label == 'myapp': - return 'other' + """ + Attempts to read auth models go to auth_db. + """ + if model._meta.app_label == 'auth': + return 'auth_db' return None def db_for_write(self, model, **hints): - "Point all operations on myapp models to 'other'" - if model._meta.app_label == 'myapp': - return 'other' + """ + Attempts to write auth models go to auth_db. + """ + if model._meta.app_label == 'auth': + return 'auth_db' return None def allow_relation(self, obj1, obj2, **hints): - "Allow any relation if a model in myapp is involved" - if obj1._meta.app_label == 'myapp' or obj2._meta.app_label == 'myapp': - return True + """ + Allow relations if a model in the auth app is involved. + """ + if obj1._meta.app_label == 'auth' or \ + obj2._meta.app_label == 'auth': + return True return None def allow_syncdb(self, db, model): - "Make sure the myapp app only appears on the 'other' db" - if db == 'other': - return model._meta.app_label == 'myapp' - elif model._meta.app_label == 'myapp': + """ + Make sure the auth app only appears in the 'auth_db' + database. + """ + if db == 'auth_db': + return model._meta.app_label == 'auth' + elif model._meta.app_label == 'auth': return False return None - class MasterSlaveRouter(object): - """A router that sets up a simple master/slave configuration""" +And we also want a router that sends all other apps to the +master/slave configuration, and randomly chooses a slave to read +from:: + import random + + class MasterSlaveRouter(object): def db_for_read(self, model, **hints): - "Point all read operations to a random slave" - return random.choice(['slave1','slave2']) + """ + Reads go to a randomly-chosen slave. + """ + return random.choice(['slave1', 'slave2']) def db_for_write(self, model, **hints): - "Point all write operations to the master" + """ + Writes always go to master. + """ return 'master' def allow_relation(self, obj1, obj2, **hints): - "Allow any relation between two objects in the db pool" - db_list = ('master','slave1','slave2') - if obj1._state.db in db_list and obj2._state.db in db_list: + """ + Relations between objects are allowed if both objects are + in the master/slave pool. + """ + db_list = ('master', 'slave1', 'slave2') + if obj1.state.db in db_list and obj2.state.db in db_list: return True return None def allow_syncdb(self, db, model): - "Explicitly put all models on all databases." + """ + All non-auth models end up in this pool. + """ return True -Then, in your settings file, add the following (substituting ``path.to.`` with -the actual python path to the module where you define the routers):: +Finally, in the settings file, we add the following (substituting +``path.to.`` with the actual python path to the module(s) where the +routers are defined):: - DATABASE_ROUTERS = ['path.to.MyAppRouter', 'path.to.MasterSlaveRouter'] + DATABASE_ROUTERS = ['path.to.AuthRouter', 'path.to.MasterSlaveRouter'] The order in which routers are processed is significant. Routers will be queried in the order the are listed in the :setting:`DATABASE_ROUTERS` setting . In this example, the -``MyAppRouter`` is processed before the ``MasterSlaveRouter``, and as a -result, decisions concerning the models in ``myapp`` are processed +``AuthRouter`` is processed before the ``MasterSlaveRouter``, and as a +result, decisions concerning the models in ``auth`` are processed before any other decision is made. If the :setting:`DATABASE_ROUTERS` setting listed the two routers in the other order, ``MasterSlaveRouter.allow_syncdb()`` would be processed first. The @@ -276,11 +332,11 @@ that all models would be available on all databases. With this setup installed, lets run some Django code:: - >>> # This retrieval will be performed on the 'credentials' database + >>> # This retrieval will be performed on the 'auth_db' database >>> fred = User.objects.get(username='fred') >>> fred.first_name = 'Frederick' - >>> # This save will also be directed to 'credentials' + >>> # This save will also be directed to 'auth_db' >>> fred.save() >>> # These retrieval will be randomly allocated to a slave database diff --git a/docs/topics/db/queries.txt b/docs/topics/db/queries.txt index f87fa2920f..5385b2a72d 100644 --- a/docs/topics/db/queries.txt +++ b/docs/topics/db/queries.txt @@ -35,8 +35,8 @@ models, which comprise a Weblog application: blog = models.ForeignKey(Blog) headline = models.CharField(max_length=255) body_text = models.TextField() - pub_date = models.DateTimeField() - mod_date = models.DateTimeField() + pub_date = models.DateField() + mod_date = models.DateField() authors = models.ManyToManyField(Author) n_comments = models.IntegerField() n_pingbacks = models.IntegerField() @@ -233,7 +233,7 @@ refinements together. For example:: >>> Entry.objects.filter( ... headline__startswith='What' ... ).exclude( - ... pub_date__gte=datetime.now() + ... pub_date__gte=datetime.date.today() ... ).filter( ... pub_date__gte=datetime(2005, 1, 30) ... ) @@ -258,8 +258,8 @@ stored, used and reused. Example:: >> q1 = Entry.objects.filter(headline__startswith="What") - >> q2 = q1.exclude(pub_date__gte=datetime.now()) - >> q3 = q1.filter(pub_date__gte=datetime.now()) + >> q2 = q1.exclude(pub_date__gte=datetime.date.today()) + >> q3 = q1.filter(pub_date__gte=datetime.date.today()) These three ``QuerySets`` are separate. The first is a base :class:`~django.db.models.query.QuerySet` containing all entries that contain a @@ -282,7 +282,7 @@ actually run the query until the :class:`~django.db.models.query.QuerySet` is *evaluated*. Take a look at this example:: >>> q = Entry.objects.filter(headline__startswith="What") - >>> q = q.filter(pub_date__lte=datetime.now()) + >>> q = q.filter(pub_date__lte=datetime.date.today()) >>> q = q.exclude(body_text__icontains="food") >>> print(q) @@ -968,11 +968,12 @@ Be aware that the ``update()`` method is converted directly to an SQL statement. It is a bulk operation for direct updates. It doesn't run any :meth:`~django.db.models.Model.save` methods on your models, or emit the ``pre_save`` or ``post_save`` signals (which are a consequence of calling -:meth:`~django.db.models.Model.save`). If you want to save every item in a -:class:`~django.db.models.query.QuerySet` and make sure that the -:meth:`~django.db.models.Model.save` method is called on each instance, you -don't need any special function to handle that. Just loop over them and call -:meth:`~django.db.models.Model.save`:: +:meth:`~django.db.models.Model.save`), or honor the +:attr:`~django.db.models.DateField.auto_now` field option. +If you want to save every item in a :class:`~django.db.models.query.QuerySet` +and make sure that the :meth:`~django.db.models.Model.save` method is called on +each instance, you don't need any special function to handle that. Just loop +over them and call :meth:`~django.db.models.Model.save`:: for item in my_queryset: item.save() diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt index 8159c8850c..caff03c581 100644 --- a/docs/topics/forms/modelforms.txt +++ b/docs/topics/forms/modelforms.txt @@ -311,18 +311,18 @@ model fields: to exclude from the form. For example, if you want a form for the ``Author`` model (defined -above) that includes only the ``name`` and ``title`` fields, you would +above) that includes only the ``name`` and ``birth_date`` fields, you would specify ``fields`` or ``exclude`` like this:: class PartialAuthorForm(ModelForm): class Meta: model = Author - fields = ('name', 'title') + fields = ('name', 'birth_date') class PartialAuthorForm(ModelForm): class Meta: model = Author - exclude = ('birth_date',) + exclude = ('title',) Since the Author model has only 3 fields, 'name', 'title', and 'birth_date', the forms above will contain exactly the same fields. diff --git a/docs/topics/http/urls.txt b/docs/topics/http/urls.txt index 7297184ed3..4503bbd6ef 100644 --- a/docs/topics/http/urls.txt +++ b/docs/topics/http/urls.txt @@ -20,18 +20,18 @@ Overview ======== To design URLs for an app, you create a Python module informally called a -**URLconf** (URL configuration). This module is pure Python code and -is a simple mapping between URL patterns (as simple regular expressions) to -Python callback functions (your views). +**URLconf** (URL configuration). This module is pure Python code and is a +simple mapping between URL patterns (simple regular expressions) to Python +functions (your views). This mapping can be as short or as long as needed. It can reference other mappings. And, because it's pure Python code, it can be constructed dynamically. .. versionadded:: 1.4 - Django also allows to translate URLs according to the active language. - This process is described in the - :ref:`internationalization docs `. + Django also provides a way to translate URLs according to the active + language. See the :ref:`internationalization documentation + ` for more information. .. _how-django-processes-a-request: @@ -154,11 +154,12 @@ The matching/grouping algorithm Here's the algorithm the URLconf parser follows, with respect to named groups vs. non-named groups in a regular expression: -If there are any named arguments, it will use those, ignoring non-named arguments. -Otherwise, it will pass all non-named arguments as positional arguments. +1. If there are any named arguments, it will use those, ignoring non-named + arguments. -In both cases, it will pass any extra keyword arguments as keyword arguments. -See "Passing extra options to view functions" below. +2. Otherwise, it will pass all non-named arguments as positional arguments. + +In both cases, any extra keyword arguments that have been given as per `Passing extra options to view functions`_ (below) will also be passed to the view. What the URLconf searches against ================================= @@ -176,6 +177,44 @@ The URLconf doesn't look at the request method. In other words, all request methods -- ``POST``, ``GET``, ``HEAD``, etc. -- will be routed to the same function for the same URL. +Notes on capturing text in URLs +=============================== + +Each captured argument is sent to the view as a plain Python string, regardless +of what sort of match the regular expression makes. For example, in this +URLconf line:: + + (r'^articles/(?P\d{4})/$', 'news.views.year_archive'), + +...the ``year`` argument to ``news.views.year_archive()`` will be a string, not +an integer, even though the ``\d{4}`` will only match integer strings. + +A convenient trick is to specify default parameters for your views' arguments. +Here's an example URLconf and view:: + + # URLconf + urlpatterns = patterns('', + (r'^blog/$', 'blog.views.page'), + (r'^blog/page(?P\d+)/$', 'blog.views.page'), + ) + + # View (in blog/views.py) + def page(request, num="1"): + # Output the appropriate page of blog entries, according to num. + +In the above example, both URL patterns point to the same view -- +``blog.views.page`` -- but the first pattern doesn't capture anything from the +URL. If the first pattern matches, the ``page()`` function will use its +default argument for ``num``, ``"1"``. If the second pattern matches, +``page()`` will use whatever ``num`` value was captured by the regex. + +Performance +=========== + +Each regular expression in a ``urlpatterns`` is compiled the first time it's +accessed. This makes the system blazingly fast. + + Syntax of the urlpatterns variable ================================== @@ -209,10 +248,10 @@ The first argument to ``patterns()`` is a string ``prefix``. See The remaining arguments should be tuples in this format:: - (regular expression, Python callback function [, optional dictionary [, optional name]]) + (regular expression, Python callback function [, optional_dictionary [, optional_name]]) -...where ``optional dictionary`` and ``optional name`` are optional. (See -`Passing extra options to view functions`_ below.) +The ``optional_dictionary`` and ``optional_name`` parameters are described in +`Passing extra options to view functions`_ below. .. note:: Because `patterns()` is a function call, it accepts a maximum of 255 @@ -332,43 +371,6 @@ value should suffice. See the documentation about :ref:`the 500 (HTTP Internal Server Error) view ` for more information. -Notes on capturing text in URLs -=============================== - -Each captured argument is sent to the view as a plain Python string, regardless -of what sort of match the regular expression makes. For example, in this -URLconf line:: - - (r'^articles/(?P\d{4})/$', 'news.views.year_archive'), - -...the ``year`` argument to ``news.views.year_archive()`` will be a string, not -an integer, even though the ``\d{4}`` will only match integer strings. - -A convenient trick is to specify default parameters for your views' arguments. -Here's an example URLconf and view:: - - # URLconf - urlpatterns = patterns('', - (r'^blog/$', 'blog.views.page'), - (r'^blog/page(?P\d+)/$', 'blog.views.page'), - ) - - # View (in blog/views.py) - def page(request, num="1"): - # Output the appropriate page of blog entries, according to num. - -In the above example, both URL patterns point to the same view -- -``blog.views.page`` -- but the first pattern doesn't capture anything from the -URL. If the first pattern matches, the ``page()`` function will use its -default argument for ``num``, ``"1"``. If the second pattern matches, -``page()`` will use whatever ``num`` value was captured by the regex. - -Performance -=========== - -Each regular expression in a ``urlpatterns`` is compiled the first time it's -accessed. This makes the system blazingly fast. - The view prefix =============== diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt index 9bd53da2b9..a7f48fe1fd 100644 --- a/docs/topics/i18n/translation.txt +++ b/docs/topics/i18n/translation.txt @@ -1251,6 +1251,11 @@ As a convenience, Django comes with a view, :func:`django.views.i18n.set_languag that sets a user's language preference and redirects to a given URL or, by default, back to the previous page. +Make sure that the following item is in your +:setting:`TEMPLATE_CONTEXT_PROCESSORS` list in your settings file:: + + 'django.core.context_processors.i18n' + Activate this view by adding the following line to your URLconf:: (r'^i18n/', include('django.conf.urls.i18n')), diff --git a/docs/topics/install.txt b/docs/topics/install.txt index a11a44baa1..890c5e3195 100644 --- a/docs/topics/install.txt +++ b/docs/topics/install.txt @@ -9,10 +9,8 @@ Install Python Being a Python Web framework, Django requires Python. -It works with any Python version from 2.6.5 to 2.7 (due to backwards -incompatibilities in Python 3.0, Django does not currently work with -Python 3.0; see :doc:`the Django FAQ ` for more -information on supported Python versions and the 3.0 transition). +It works with any Python version from 2.6.5 to 2.7. It also features +experimental support for versions 3.2 and 3.3. Get Python at http://www.python.org. If you're running Linux or Mac OS X, you probably already have it installed. diff --git a/docs/topics/python3.txt b/docs/topics/python3.txt index 457486caa4..f5749faaf2 100644 --- a/docs/topics/python3.txt +++ b/docs/topics/python3.txt @@ -25,10 +25,11 @@ free to chose another strategy for your own code, especially if you don't need to stay compatible with Python 2. But authors of pluggable applications are encouraged to use the same porting strategy as Django itself. -Writing compatible code is much easier if you target Python ≥ 2.6. You will -most likely take advantage of the compatibility functions introduced in Django -1.5, like :mod:`django.utils.six`, so your application will also require -Django ≥ 1.5. +Writing compatible code is much easier if you target Python ≥ 2.6. Django 1.5 +introduces compatibility tools such as :mod:`django.utils.six`. For +convenience, forwards-compatible aliases were introduced in Django 1.4.2. If +your application takes advantage of these tools, it will require Django ≥ +1.4.2. Obviously, writing compatible source code adds some overhead, and that can cause frustration. Django's developers have found that attempting to write @@ -102,6 +103,8 @@ Old name New name For backwards compatibility, the old names still work on Python 2. Under Python 3, ``smart_str`` is an alias for ``smart_text``. +For forwards compatibility, the new names work as of Django 1.4.2. + .. note:: :mod:`django.utils.encoding` was deeply refactored in Django 1.5 to @@ -126,6 +129,8 @@ For backwards compatibility, the old names still work on Python 2. Under Python 3, ``EscapeString`` and ``SafeString`` are aliases for ``EscapeText`` and ``SafeText`` respectively. +For forwards compatibility, the new names work as of Django 1.4.2. + :meth:`__str__` and :meth:`__unicode__` methods ----------------------------------------------- @@ -166,6 +171,8 @@ On Python 3, the decorator is a no-op. On Python 2, it defines appropriate This technique is the best match for Django's porting philosophy. +For forwards compatibility, this decorator is available as of Django 1.4.2. + Finally, note that :meth:`__repr__` must return a :class:`str` on all versions of Python. @@ -317,7 +324,8 @@ Writing compatible code with six six_ is the canonical compatibility library for supporting Python 2 and 3 in a single codebase. Read its documentation! -:mod:`six` is bundled with Django: you can import it as :mod:`django.utils.six`. +:mod:`six` is bundled with Django as of version 1.4.2. You can import it as +:mod:`django.utils.six`. Here are the most common changes required to write compatible code. @@ -392,5 +400,12 @@ The version of six bundled with Django includes one extra function: 2 and :meth:`~django.utils.datastructures.MultiValueDict.lists()` on Python 3. +.. function:: assertRaisesRegex(testcase, *args, **kwargs) + + This replaces ``testcase.assertRaisesRegexp`` on Python 2, and + ``testcase.assertRaisesRegex`` on Python 3. ``assertRaisesRegexp`` still + exists in current Python3 versions, but issues a warning. + + In addition to six' defaults moves, Django's version provides ``thread`` as ``_thread`` and ``dummy_thread`` as ``_dummy_thread``. diff --git a/docs/topics/security.txt b/docs/topics/security.txt index 151853d4ac..0a3c6bff02 100644 --- a/docs/topics/security.txt +++ b/docs/topics/security.txt @@ -76,9 +76,17 @@ POST to your Web site and have another logged in user unwittingly submit that form. The malicious user would have to know the nonce, which is user specific (using a cookie). +When deployed with :ref:`HTTPS `, +``CsrfViewMiddleware`` will check that the HTTP referer header is set to a +URL on the same origin (including subdomain and port). Because HTTPS +provides additional security, it is imperative to ensure connections use HTTPS +where it is available by forwarding insecure connection requests and using +HSTS for supported browsers. + Be very careful with marking views with the ``csrf_exempt`` decorator unless it is absolutely necessary. + SQL injection protection ======================== @@ -112,6 +120,8 @@ The middleware is strongly recommended for any site that does not need to have its pages wrapped in a frame by third party sites, or only needs to allow that for a small section of the site. +.. _security-recommendation-ssl: + SSL/HTTPS ========= @@ -147,7 +157,15 @@ server, there are some additional steps you may need: any POST data being accepted over HTTP (which will be fine if you are redirecting all HTTP traffic to HTTPS). -.. _additional-security-topics: +* Use HTTP Strict Transport Security (HSTS) + + HSTS is an HTTP header that informs a browser that all future connections + to a particular site should always use HTTPS. Combined with redirecting + requests over HTTP to HTTPS, this will ensure that connections always enjoy + the added security of SSL provided one successful connection has occurred. + HSTS is usually configured on the web server. + +.. _host-headers-virtual-hosting: Host headers and virtual hosting ================================ @@ -158,15 +176,17 @@ Site Scripting attacks, they can be used for Cross-Site Request Forgery and cache poisoning attacks in some circumstances. We recommend you ensure your Web server is configured such that: - * It always validates incoming HTTP ``Host`` headers against the expected - host name. - * Disallows requests with no ``Host`` header. - * Is *not* configured with a catch-all virtual host that forwards requests - to a Django application. +* It always validates incoming HTTP ``Host`` headers against the expected + host name. +* Disallows requests with no ``Host`` header. +* Is *not* configured with a catch-all virtual host that forwards requests + to a Django application. Additionally, as of 1.3.1, Django requires you to explicitly enable support for the ``X-Forwarded-Host`` header if your configuration requires it. +.. _additional-security-topics: + Additional security topics ========================== diff --git a/tests/modeltests/basic/tests.py b/tests/modeltests/basic/tests.py index d96c60bbe8..6ec9ca03af 100644 --- a/tests/modeltests/basic/tests.py +++ b/tests/modeltests/basic/tests.py @@ -5,7 +5,7 @@ from datetime import datetime from django.core.exceptions import ObjectDoesNotExist from django.db.models.fields import Field, FieldDoesNotExist from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature -from django.utils.six import PY3 +from django.utils import six from django.utils.translation import ugettext_lazy from .models import Article @@ -82,7 +82,7 @@ class ModelTest(TestCase): # Django raises an Article.DoesNotExist exception for get() if the # parameters don't match any object. - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ObjectDoesNotExist, "Article matching query does not exist. Lookup parameters were " "{'id__exact': 2000}", @@ -91,14 +91,14 @@ class ModelTest(TestCase): ) # To avoid dict-ordering related errors check only one lookup # in single assert. - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ObjectDoesNotExist, ".*'pub_date__year': 2005.*", Article.objects.get, pub_date__year=2005, pub_date__month=8, ) - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ObjectDoesNotExist, ".*'pub_date__month': 8.*", Article.objects.get, @@ -106,7 +106,7 @@ class ModelTest(TestCase): pub_date__month=8, ) - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ObjectDoesNotExist, "Article matching query does not exist. Lookup parameters were " "{'pub_date__week_day': 6}", @@ -168,7 +168,7 @@ class ModelTest(TestCase): self.assertEqual(a4.headline, 'Fourth article') # Don't use invalid keyword arguments. - self.assertRaisesRegexp( + six.assertRaisesRegex(self, TypeError, "'foo' is an invalid keyword argument for this function", Article, @@ -259,13 +259,13 @@ class ModelTest(TestCase): "datetime.datetime(2005, 7, 28, 0, 0)"]) # dates() requires valid arguments. - self.assertRaisesRegexp( + six.assertRaisesRegex(self, TypeError, "dates\(\) takes at least 3 arguments \(1 given\)", Article.objects.dates, ) - self.assertRaisesRegexp( + six.assertRaisesRegex(self, FieldDoesNotExist, "Article has no field named 'invalid_field'", Article.objects.dates, @@ -273,7 +273,7 @@ class ModelTest(TestCase): "year", ) - self.assertRaisesRegexp( + six.assertRaisesRegex(self, AssertionError, "'kind' must be one of 'year', 'month' or 'day'.", Article.objects.dates, @@ -281,7 +281,7 @@ class ModelTest(TestCase): "bad_kind", ) - self.assertRaisesRegexp( + six.assertRaisesRegex(self, AssertionError, "'order' must be either 'ASC' or 'DESC'.", Article.objects.dates, @@ -323,7 +323,7 @@ class ModelTest(TestCase): ""]) # Slicing works with longs (Python 2 only -- Python 3 doesn't have longs). - if not PY3: + if not six.PY3: self.assertEqual(Article.objects.all()[long(0)], a) self.assertQuerysetEqual(Article.objects.all()[long(1):long(3)], ["", ""]) @@ -369,14 +369,14 @@ class ModelTest(TestCase): ""]) # Also, once you have sliced you can't filter, re-order or combine - self.assertRaisesRegexp( + six.assertRaisesRegex(self, AssertionError, "Cannot filter a query once a slice has been taken.", Article.objects.all()[0:5].filter, id=a.id, ) - self.assertRaisesRegexp( + six.assertRaisesRegex(self, AssertionError, "Cannot reorder a query once a slice has been taken.", Article.objects.all()[0:5].order_by, @@ -411,7 +411,7 @@ class ModelTest(TestCase): # An Article instance doesn't have access to the "objects" attribute. # That's only available on the class. - self.assertRaisesRegexp( + six.assertRaisesRegex(self, AttributeError, "Manager isn't accessible via Article instances", getattr, diff --git a/tests/modeltests/empty/tests.py b/tests/modeltests/empty/tests.py index db464c632d..4c0c4409d8 100644 --- a/tests/modeltests/empty/tests.py +++ b/tests/modeltests/empty/tests.py @@ -1,10 +1,10 @@ from __future__ import absolute_import -from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.db.models.loading import get_app from django.test import TestCase from django.test.utils import override_settings +from django.utils import six from .models import Empty @@ -14,12 +14,13 @@ class EmptyModelTests(TestCase): m = Empty() self.assertEqual(m.id, None) m.save() - m2 = Empty.objects.create() + Empty.objects.create() self.assertEqual(len(Empty.objects.all()), 2) self.assertTrue(m.id is not None) existing = Empty(m.id) existing.save() + class NoModelTests(TestCase): """ Test for #7198 to ensure that the proper error message is raised @@ -32,6 +33,6 @@ class NoModelTests(TestCase): """ @override_settings(INSTALLED_APPS=("modeltests.empty.no_models",)) def test_no_models(self): - with self.assertRaisesRegexp(ImproperlyConfigured, + with six.assertRaisesRegex(self, ImproperlyConfigured, 'App with label no_models is missing a models.py module.'): get_app('no_models') diff --git a/tests/modeltests/fixtures/tests.py b/tests/modeltests/fixtures/tests.py index b332348425..f9b0ac8a46 100644 --- a/tests/modeltests/fixtures/tests.py +++ b/tests/modeltests/fixtures/tests.py @@ -4,7 +4,7 @@ from django.contrib.sites.models import Site from django.core import management from django.db import connection, IntegrityError from django.test import TestCase, TransactionTestCase, skipUnlessDBFeature -from django.utils.six import StringIO +from django.utils import six from .models import Article, Book, Spy, Tag, Visa @@ -21,16 +21,17 @@ class TestCaseFixtureLoadingTests(TestCase): '', ]) + class FixtureLoadingTests(TestCase): def _dumpdata_assert(self, args, output, format='json', natural_keys=False, use_base_manager=False, exclude_list=[]): - new_io = StringIO() - management.call_command('dumpdata', *args, **{'format':format, - 'stdout':new_io, - 'stderr':new_io, - 'use_natural_keys':natural_keys, - 'use_base_manager':use_base_manager, + new_io = six.StringIO() + management.call_command('dumpdata', *args, **{'format': format, + 'stdout': new_io, + 'stderr': new_io, + 'use_natural_keys': natural_keys, + 'use_base_manager': use_base_manager, 'exclude': exclude_list}) command_output = new_io.getvalue().strip() self.assertEqual(command_output, output) @@ -42,8 +43,6 @@ class FixtureLoadingTests(TestCase): ]) def test_loading_and_dumping(self): - new_io = StringIO() - Site.objects.all().delete() # Load fixture 1. Single JSON file, with two objects. management.call_command('loaddata', 'fixture1.json', verbosity=0, commit=False) @@ -184,12 +183,12 @@ class FixtureLoadingTests(TestCase): exclude_list=['fixtures.Article', 'fixtures.Book', 'sites']) # Excluding a bogus app should throw an error - with self.assertRaisesRegexp(management.CommandError, + with six.assertRaisesRegex(self, management.CommandError, "Unknown app in excludes: foo_app"): self._dumpdata_assert(['fixtures', 'sites'], '', exclude_list=['foo_app']) # Excluding a bogus model should throw an error - with self.assertRaisesRegexp(management.CommandError, + with six.assertRaisesRegex(self, management.CommandError, "Unknown model in excludes: fixtures.FooModel"): self._dumpdata_assert(['fixtures', 'sites'], '', exclude_list=['fixtures.FooModel']) @@ -199,7 +198,7 @@ class FixtureLoadingTests(TestCase): self.assertQuerysetEqual(Spy.objects.all(), ['']) # Use the default manager - self._dumpdata_assert(['fixtures.Spy'],'[{"pk": %d, "model": "fixtures.spy", "fields": {"cover_blown": false}}]' % spy1.pk) + self._dumpdata_assert(['fixtures.Spy'], '[{"pk": %d, "model": "fixtures.spy", "fields": {"cover_blown": false}}]' % spy1.pk) # Dump using Django's base manager. Should return all objects, # even those normally filtered by the manager self._dumpdata_assert(['fixtures.Spy'], '[{"pk": %d, "model": "fixtures.spy", "fields": {"cover_blown": true}}, {"pk": %d, "model": "fixtures.spy", "fields": {"cover_blown": false}}]' % (spy2.pk, spy1.pk), use_base_manager=True) @@ -227,7 +226,7 @@ class FixtureLoadingTests(TestCase): def test_ambiguous_compressed_fixture(self): # The name "fixture5" is ambigous, so loading it will raise an error - with self.assertRaisesRegexp(management.CommandError, + with six.assertRaisesRegex(self, management.CommandError, "Multiple fixtures named 'fixture5'"): management.call_command('loaddata', 'fixture5', verbosity=0, commit=False) @@ -251,7 +250,7 @@ class FixtureLoadingTests(TestCase): # is closed at the end of each test. if connection.vendor == 'mysql': connection.cursor().execute("SET sql_mode = 'TRADITIONAL'") - with self.assertRaisesRegexp(IntegrityError, + with six.assertRaisesRegex(self, IntegrityError, "Could not load fixtures.Article\(pk=1\): .*$"): management.call_command('loaddata', 'invalid.json', verbosity=0, commit=False) @@ -290,10 +289,11 @@ class FixtureLoadingTests(TestCase): self._dumpdata_assert(['fixtures'], """ News StoriesLatest news storiesPoker has no place on ESPN2006-06-16T12:00:00Time to reform copyright2006-06-16T13:00:00copyrightfixturesarticle3lawfixturesarticle3Django ReinhardtStephane GrappelliPrinceAchieving self-awareness of Python programs""", format='xml', natural_keys=True) + class FixtureTransactionTests(TransactionTestCase): def _dumpdata_assert(self, args, output, format='json'): - new_io = StringIO() - management.call_command('dumpdata', *args, **{'format':format, 'stdout':new_io}) + new_io = six.StringIO() + management.call_command('dumpdata', *args, **{'format': format, 'stdout': new_io}) command_output = new_io.getvalue().strip() self.assertEqual(command_output, output) @@ -308,7 +308,7 @@ class FixtureTransactionTests(TransactionTestCase): # Try to load fixture 2 using format discovery; this will fail # because there are two fixture2's in the fixtures directory - with self.assertRaisesRegexp(management.CommandError, + with six.assertRaisesRegex(self, management.CommandError, "Multiple fixtures named 'fixture2'"): management.call_command('loaddata', 'fixture2', verbosity=0) diff --git a/tests/modeltests/many_to_many/tests.py b/tests/modeltests/many_to_many/tests.py index 44bdde3aeb..7d30379b94 100644 --- a/tests/modeltests/many_to_many/tests.py +++ b/tests/modeltests/many_to_many/tests.py @@ -1,6 +1,7 @@ from __future__ import absolute_import from django.test import TestCase +from django.utils import six from .models import Article, Publication @@ -52,7 +53,7 @@ class ManyToManyTests(TestCase): ]) # Adding an object of the wrong type raises TypeError - with self.assertRaisesRegexp(TypeError, "'Publication' instance expected, got "]) # Adding an object of the wrong type raises TypeError. - with self.assertRaisesRegexp(TypeError, "'Article' instance expected, got )" % ('class' if PY3 else 'type'), "Weight = 1.2 (<%s 'float'>)" % ('class' if PY3 else 'type')) ] ) finally: - signals.pre_save.disconnect(animal_pre_save_check) + signals.pre_save.disconnect(self.animal_pre_save_check) def test_dumpdata_uses_default_manager(self): """ @@ -353,7 +349,7 @@ class TestFixtures(TestCase): """ Regression for #3615 - Ensure data with nonexistent child key references raises error """ - with self.assertRaisesRegexp(IntegrityError, + with six.assertRaisesRegex(self, IntegrityError, "Problem installing fixture"): management.call_command( 'loaddata', @@ -385,7 +381,7 @@ class TestFixtures(TestCase): """ Regression for #7043 - Error is quickly reported when no fixtures is provided in the command line. """ - with self.assertRaisesRegexp(management.CommandError, + with six.assertRaisesRegex(self, management.CommandError, "No database fixture specified. Please provide the path of " "at least one fixture in the command line."): management.call_command( diff --git a/tests/regressiontests/forms/tests/fields.py b/tests/regressiontests/forms/tests/fields.py index 6efdb9682f..197ce1abd9 100644 --- a/tests/regressiontests/forms/tests/fields.py +++ b/tests/regressiontests/forms/tests/fields.py @@ -475,7 +475,7 @@ class FieldsTests(SimpleTestCase): def test_regexfield_5(self): f = RegexField('^\d+$', min_length=5, max_length=10) self.assertRaisesMessage(ValidationError, "'Ensure this value has at least 5 characters (it has 3).'", f.clean, '123') - self.assertRaisesRegexp(ValidationError, "'Ensure this value has at least 5 characters \(it has 3\)\.', u?'Enter a valid value\.'", f.clean, 'abc') + six.assertRaisesRegex(self, ValidationError, "'Ensure this value has at least 5 characters \(it has 3\)\.', u?'Enter a valid value\.'", f.clean, 'abc') self.assertEqual('12345', f.clean('12345')) self.assertEqual('1234567890', f.clean('1234567890')) self.assertRaisesMessage(ValidationError, "'Ensure this value has at most 10 characters (it has 11).'", f.clean, '12345678901') @@ -1036,7 +1036,7 @@ class FieldsTests(SimpleTestCase): self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None) self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, '') self.assertRaisesMessage(ValidationError, "'Enter a list of values.'", f.clean, 'hello') - self.assertRaisesRegexp(ValidationError, "'Enter a valid date\.', u?'Enter a valid time\.'", f.clean, ['hello', 'there']) + six.assertRaisesRegex(self, ValidationError, "'Enter a valid date\.', u?'Enter a valid time\.'", f.clean, ['hello', 'there']) self.assertRaisesMessage(ValidationError, "'Enter a valid time.'", f.clean, ['2006-01-10', 'there']) self.assertRaisesMessage(ValidationError, "'Enter a valid date.'", f.clean, ['hello', '07:30']) @@ -1049,7 +1049,7 @@ class FieldsTests(SimpleTestCase): self.assertEqual(None, f.clean([''])) self.assertEqual(None, f.clean(['', ''])) self.assertRaisesMessage(ValidationError, "'Enter a list of values.'", f.clean, 'hello') - self.assertRaisesRegexp(ValidationError, "'Enter a valid date\.', u?'Enter a valid time\.'", f.clean, ['hello', 'there']) + six.assertRaisesRegex(self, ValidationError, "'Enter a valid date\.', u?'Enter a valid time\.'", f.clean, ['hello', 'there']) self.assertRaisesMessage(ValidationError, "'Enter a valid time.'", f.clean, ['2006-01-10', 'there']) self.assertRaisesMessage(ValidationError, "'Enter a valid date.'", f.clean, ['hello', '07:30']) self.assertRaisesMessage(ValidationError, "'Enter a valid time.'", f.clean, ['2006-01-10', '']) diff --git a/tests/regressiontests/forms/tests/widgets.py b/tests/regressiontests/forms/tests/widgets.py index c950aef719..544ca41642 100644 --- a/tests/regressiontests/forms/tests/widgets.py +++ b/tests/regressiontests/forms/tests/widgets.py @@ -216,13 +216,9 @@ class FormsWidgetTestCase(TestCase): self.assertHTMLEqual(w.render('greeting', 'hello there'), '') self.assertHTMLEqual(w.render('greeting', 'hello & goodbye'), '') - # A subtlety: If the 'check_test' argument cannot handle a value and raises any - # exception during its __call__, then the exception will be swallowed and the box - # will not be checked. In this example, the 'check_test' assumes the value has a - # startswith() method, which fails for the values True, False and None. - self.assertHTMLEqual(w.render('greeting', True), '') - self.assertHTMLEqual(w.render('greeting', False), '') - self.assertHTMLEqual(w.render('greeting', None), '') + # Ticket #17888: calling check_test shouldn't swallow exceptions + with self.assertRaises(AttributeError): + w.render('greeting', True) # The CheckboxInput widget will return False if the key is not found in the data # dictionary (because HTML form submission doesn't send any result for unchecked diff --git a/tests/regressiontests/handlers/tests.py b/tests/regressiontests/handlers/tests.py index ae2062c756..34863b6493 100644 --- a/tests/regressiontests/handlers/tests.py +++ b/tests/regressiontests/handlers/tests.py @@ -17,10 +17,8 @@ class HandlerTests(unittest.TestCase): # Try running the handler, it will fail in load_middleware handler = WSGIHandler() self.assertEqual(handler.initLock.locked(), False) - try: + with self.assertRaises(Exception): handler(None, None) - except: - pass self.assertEqual(handler.initLock.locked(), False) # Reset settings settings.MIDDLEWARE_CLASSES = old_middleware_classes diff --git a/tests/regressiontests/httpwrappers/tests.py b/tests/regressiontests/httpwrappers/tests.py index 21ba198bc3..4c6aed1b97 100644 --- a/tests/regressiontests/httpwrappers/tests.py +++ b/tests/regressiontests/httpwrappers/tests.py @@ -11,6 +11,7 @@ from django.http import (QueryDict, HttpResponse, HttpResponseRedirect, SimpleCookie, BadHeaderError, parse_cookie) from django.test import TestCase +from django.utils.encoding import smart_str from django.utils import six from django.utils import unittest @@ -228,33 +229,52 @@ class QueryDictTests(unittest.TestCase): self.assertEqual(copy.deepcopy(q).encoding, 'iso-8859-15') class HttpResponseTests(unittest.TestCase): - def test_unicode_headers(self): + + def test_headers_type(self): r = HttpResponse() - # If we insert a unicode value it will be converted to an ascii - r['value'] = 'test value' - self.assertTrue(isinstance(r['value'], str)) + # The following tests explicitly test types in addition to values + # because in Python 2 u'foo' == b'foo'. - # An error is raised when a unicode object with non-ascii is assigned. - self.assertRaises(UnicodeEncodeError, r.__setitem__, 'value', 't\xebst value') + # ASCII unicode or bytes values are converted to native strings. + r['key'] = 'test' + self.assertEqual(r['key'], str('test')) + self.assertIsInstance(r['key'], str) + r['key'] = 'test'.encode('ascii') + self.assertEqual(r['key'], str('test')) + self.assertIsInstance(r['key'], str) - # An error is raised when a unicode object with non-ASCII format is - # passed as initial mimetype or content_type. - self.assertRaises(UnicodeEncodeError, HttpResponse, - content_type='t\xebst value') + # Latin-1 unicode or bytes values are also converted to native strings. + r['key'] = 'café' + self.assertEqual(r['key'], smart_str('café', 'latin-1')) + self.assertIsInstance(r['key'], str) + r['key'] = 'café'.encode('latin-1') + self.assertEqual(r['key'], smart_str('café', 'latin-1')) + self.assertIsInstance(r['key'], str) - # HttpResponse headers must be convertible to ASCII. - self.assertRaises(UnicodeEncodeError, HttpResponse, - content_type='t\xebst value') + # Other unicode values are MIME-encoded (there's no way to pass them as bytes). + r['key'] = '†' + self.assertEqual(r['key'], str('=?utf-8?b?4oCg?=')) + self.assertIsInstance(r['key'], str) - # The response also converts unicode keys to strings.) - r['test'] = 'testing key' + # The response also converts unicode or bytes keys to strings, but requires + # them to contain ASCII + r = HttpResponse() + r['foo'] = 'bar' l = list(r.items()) - l.sort() - self.assertEqual(l[1], ('test', 'testing key')) + self.assertEqual(l[0], ('foo', 'bar')) + self.assertIsInstance(l[0][0], str) + + r = HttpResponse() + r[b'foo'] = 'bar' + l = list(r.items()) + self.assertEqual(l[0], ('foo', 'bar')) + self.assertIsInstance(l[0][0], str) + + r = HttpResponse() + self.assertRaises(UnicodeError, r.__setitem__, 'føø', 'bar') + self.assertRaises(UnicodeError, r.__setitem__, 'føø'.encode('utf-8'), 'bar') - # It will also raise errors for keys with non-ascii data. - self.assertRaises(UnicodeEncodeError, r.__setitem__, 't\xebst key', 'value') def test_newlines_in_headers(self): # Bug #10188: Do not allow newlines in headers (CR or LF) diff --git a/tests/regressiontests/i18n/commands/compilation.py b/tests/regressiontests/i18n/commands/compilation.py index b6119cf43d..c6ab77941b 100644 --- a/tests/regressiontests/i18n/commands/compilation.py +++ b/tests/regressiontests/i18n/commands/compilation.py @@ -3,11 +3,12 @@ import os from django.core.management import call_command, CommandError from django.test import TestCase from django.test.utils import override_settings -from django.utils import translation +from django.utils import translation, six from django.utils.six import StringIO test_dir = os.path.abspath(os.path.dirname(__file__)) + class MessageCompilationTests(TestCase): def setUp(self): @@ -19,12 +20,12 @@ class MessageCompilationTests(TestCase): class PoFileTests(MessageCompilationTests): - LOCALE='es_AR' - MO_FILE='locale/%s/LC_MESSAGES/django.mo' % LOCALE + LOCALE = 'es_AR' + MO_FILE = 'locale/%s/LC_MESSAGES/django.mo' % LOCALE def test_bom_rejection(self): os.chdir(test_dir) - with self.assertRaisesRegexp(CommandError, + with six.assertRaisesRegex(self, CommandError, "file has a BOM \(Byte Order Mark\)"): call_command('compilemessages', locale=self.LOCALE, stderr=StringIO()) self.assertFalse(os.path.exists(self.MO_FILE)) diff --git a/tests/regressiontests/inline_formsets/tests.py b/tests/regressiontests/inline_formsets/tests.py index 6e63f34ed0..e979fb4337 100644 --- a/tests/regressiontests/inline_formsets/tests.py +++ b/tests/regressiontests/inline_formsets/tests.py @@ -123,7 +123,7 @@ class InlineFormsetFactoryTest(TestCase): Child has two ForeignKeys to Parent, so if we don't specify which one to use for the inline formset, we should get an exception. """ - self.assertRaisesRegexp(Exception, + six.assertRaisesRegex(self, Exception, " has more than 1 ForeignKey to ", inlineformset_factory, Parent, Child ) @@ -143,7 +143,7 @@ class InlineFormsetFactoryTest(TestCase): If the field specified in fk_name is not a ForeignKey, we should get an exception. """ - self.assertRaisesRegexp(Exception, + six.assertRaisesRegex(self, Exception, " has no field named 'test'", inlineformset_factory, Parent, Child, fk_name='test' ) diff --git a/tests/regressiontests/localflavor/tr/tests.py b/tests/regressiontests/localflavor/tr/tests.py index 3ec1f5b7c4..476efc12ba 100644 --- a/tests/regressiontests/localflavor/tr/tests.py +++ b/tests/regressiontests/localflavor/tr/tests.py @@ -2,29 +2,31 @@ from django.contrib.localflavor.tr import forms as trforms from django.core.exceptions import ValidationError +from django.utils import six from django.utils.unittest import TestCase + class TRLocalFlavorTests(TestCase): def test_TRPostalCodeField(self): f = trforms.TRPostalCodeField() self.assertEqual(f.clean("06531"), "06531") self.assertEqual(f.clean("12345"), "12345") - self.assertRaisesRegexp(ValidationError, + six.assertRaisesRegex(self, ValidationError, "Enter a postal code in the format XXXXX.", f.clean, "a1234") - self.assertRaisesRegexp(ValidationError, + six.assertRaisesRegex(self, ValidationError, "Enter a postal code in the format XXXXX.", f.clean, "1234") - self.assertRaisesRegexp(ValidationError, + six.assertRaisesRegex(self, ValidationError, "Enter a postal code in the format XXXXX.", f.clean, "82123") - self.assertRaisesRegexp(ValidationError, + six.assertRaisesRegex(self, ValidationError, "Enter a postal code in the format XXXXX.", f.clean, "00123") - self.assertRaisesRegexp(ValidationError, + six.assertRaisesRegex(self, ValidationError, "Enter a postal code in the format XXXXX.", f.clean, "123456") - self.assertRaisesRegexp(ValidationError, + six.assertRaisesRegex(self, ValidationError, "Enter a postal code in the format XXXXX.", f.clean, "12 34") self.assertRaises(ValidationError, f.clean, None) @@ -40,34 +42,34 @@ class TRLocalFlavorTests(TestCase): self.assertEqual(f.clean("+90 312 455 4567"), "3124554567") self.assertEqual(f.clean("+90 312 455 45 67"), "3124554567") self.assertEqual(f.clean("+90 (312) 4554567"), "3124554567") - self.assertRaisesRegexp(ValidationError, + six.assertRaisesRegex(self, ValidationError, 'Phone numbers must be in 0XXX XXX XXXX format.', f.clean, "1234 233 1234") - self.assertRaisesRegexp(ValidationError, + six.assertRaisesRegex(self, ValidationError, 'Phone numbers must be in 0XXX XXX XXXX format.', f.clean, "0312 233 12345") - self.assertRaisesRegexp(ValidationError, + six.assertRaisesRegex(self, ValidationError, 'Phone numbers must be in 0XXX XXX XXXX format.', f.clean, "0312 233 123") - self.assertRaisesRegexp(ValidationError, + six.assertRaisesRegex(self, ValidationError, 'Phone numbers must be in 0XXX XXX XXXX format.', f.clean, "0312 233 xxxx") def test_TRIdentificationNumberField(self): f = trforms.TRIdentificationNumberField() self.assertEqual(f.clean("10000000146"), "10000000146") - self.assertRaisesRegexp(ValidationError, + six.assertRaisesRegex(self, ValidationError, 'Enter a valid Turkish Identification number.', f.clean, "10000000136") - self.assertRaisesRegexp(ValidationError, + six.assertRaisesRegex(self, ValidationError, 'Enter a valid Turkish Identification number.', f.clean, "10000000147") - self.assertRaisesRegexp(ValidationError, + six.assertRaisesRegex(self, ValidationError, 'Turkish Identification number must be 11 digits.', f.clean, "123456789") - self.assertRaisesRegexp(ValidationError, + six.assertRaisesRegex(self, ValidationError, 'Enter a valid Turkish Identification number.', f.clean, "1000000014x") - self.assertRaisesRegexp(ValidationError, + six.assertRaisesRegex(self, ValidationError, 'Enter a valid Turkish Identification number.', f.clean, "x0000000146") diff --git a/tests/regressiontests/m2m_regress/tests.py b/tests/regressiontests/m2m_regress/tests.py index e3dab59b9f..92628c1825 100644 --- a/tests/regressiontests/m2m_regress/tests.py +++ b/tests/regressiontests/m2m_regress/tests.py @@ -2,6 +2,7 @@ from __future__ import absolute_import from django.core.exceptions import FieldError from django.test import TestCase +from django.utils import six from .models import (SelfRefer, Tag, TagCollection, Entry, SelfReferChild, SelfReferChildSibling, Worksheet) @@ -35,7 +36,7 @@ class M2MRegressionTests(TestCase): # The secret internal related names for self-referential many-to-many # fields shouldn't appear in the list when an error is made. - self.assertRaisesRegexp(FieldError, + six.assertRaisesRegex(self, FieldError, "Choices are: id, name, references, related, selfreferchild, selfreferchildsibling$", lambda: SelfRefer.objects.filter(porcupine='fred') ) @@ -70,7 +71,7 @@ class M2MRegressionTests(TestCase): t2 = Tag.objects.create(name='t2') c1 = TagCollection.objects.create(name='c1') - c1.tags = [t1,t2] + c1.tags = [t1, t2] c1 = TagCollection.objects.get(name='c1') self.assertQuerysetEqual(c1.tags.all(), ["", ""]) diff --git a/tests/regressiontests/many_to_one_regress/tests.py b/tests/regressiontests/many_to_one_regress/tests.py index 481a037139..d980d7437c 100644 --- a/tests/regressiontests/many_to_one_regress/tests.py +++ b/tests/regressiontests/many_to_one_regress/tests.py @@ -2,8 +2,9 @@ from __future__ import absolute_import from django.db import models from django.test import TestCase +from django.utils import six -from .models import First, Second, Third, Parent, Child, Category, Record, Relation +from .models import First, Third, Parent, Child, Category, Record, Relation class ManyToOneRegressionTests(TestCase): @@ -59,7 +60,7 @@ class ManyToOneRegressionTests(TestCase): self.assertRaises(ValueError, Child.objects.create, name='xyzzy', parent=None) # Trying to assign to unbound attribute raises AttributeError - self.assertRaisesRegexp(AttributeError, "must be accessed via instance", + six.assertRaisesRegex(self, AttributeError, "must be accessed via instance", Child.parent.__set__, None, p) # Creation using keyword argument should cache the related object. diff --git a/tests/regressiontests/modeladmin/tests.py b/tests/regressiontests/modeladmin/tests.py index d55d50d0a5..b0a181218b 100644 --- a/tests/regressiontests/modeladmin/tests.py +++ b/tests/regressiontests/modeladmin/tests.py @@ -16,7 +16,7 @@ from django.forms.models import BaseModelFormSet from django.forms.widgets import Select from django.test import TestCase from django.test.utils import str_prefix -from django.utils import unittest +from django.utils import unittest, six from .models import Band, Concert, ValidationTestModel, ValidationTestInlineModel @@ -506,7 +506,7 @@ class ValidationTests(unittest.TestCase): site = AdminSite() - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.raw_id_fields' must be a list or tuple.", site.register, @@ -524,7 +524,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): raw_id_fields = 10 - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.raw_id_fields' must be a list or tuple.", validate, @@ -535,7 +535,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): raw_id_fields = ('non_existent_field',) - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.raw_id_fields' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.", validate, @@ -546,7 +546,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): raw_id_fields = ('name',) - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.raw_id_fields\[0\]', 'name' must be either a ForeignKey or ManyToManyField.", validate, @@ -564,7 +564,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): fieldsets = 10 - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.fieldsets' must be a list or tuple.", validate, @@ -575,7 +575,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): fieldsets = ({},) - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.fieldsets\[0\]' must be a list or tuple.", validate, @@ -586,7 +586,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): fieldsets = ((),) - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.fieldsets\[0\]' does not have exactly two elements.", validate, @@ -597,7 +597,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): fieldsets = (("General", ()),) - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.fieldsets\[0\]\[1\]' must be a dictionary.", validate, @@ -608,7 +608,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): fieldsets = (("General", {}),) - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'fields' key is required in ValidationTestModelAdmin.fieldsets\[0\]\[1\] field options dict.", validate, @@ -619,7 +619,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): fieldsets = (("General", {"fields": ("non_existent_field",)}),) - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.fieldsets\[0\]\[1\]\['fields'\]' refers to field 'non_existent_field' that is missing from the form.", validate, @@ -636,7 +636,7 @@ class ValidationTests(unittest.TestCase): fieldsets = (("General", {"fields": ("name",)}),) fields = ["name",] - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "Both fieldsets and fields are specified in ValidationTestModelAdmin.", validate, @@ -647,7 +647,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): fieldsets = [(None, {'fields': ['name', 'name']})] - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "There are duplicate field\(s\) in ValidationTestModelAdmin.fieldsets", validate, @@ -658,7 +658,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): fields = ["name", "name"] - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "There are duplicate field\(s\) in ValidationTestModelAdmin.fields", validate, @@ -674,7 +674,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): form = FakeForm - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "ValidationTestModelAdmin.form does not inherit from BaseModelForm.", validate, @@ -692,7 +692,7 @@ class ValidationTests(unittest.TestCase): }), ) - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'BandAdmin.fieldsets\[0\]\[1\]\['fields'\]' refers to field 'non_existent_field' that is missing from the form.", validate, @@ -722,7 +722,7 @@ class ValidationTests(unittest.TestCase): }), ) - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'BandAdmin.fieldsets\[0]\[1\]\['fields'\]' refers to field 'non_existent_field' that is missing from the form.", validate, @@ -752,7 +752,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): filter_vertical = 10 - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.filter_vertical' must be a list or tuple.", validate, @@ -763,7 +763,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): filter_vertical = ("non_existent_field",) - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.filter_vertical' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.", validate, @@ -774,7 +774,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): filter_vertical = ("name",) - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.filter_vertical\[0\]' must be a ManyToManyField.", validate, @@ -792,7 +792,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): filter_horizontal = 10 - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.filter_horizontal' must be a list or tuple.", validate, @@ -803,7 +803,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): filter_horizontal = ("non_existent_field",) - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.filter_horizontal' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.", validate, @@ -814,7 +814,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): filter_horizontal = ("name",) - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.filter_horizontal\[0\]' must be a ManyToManyField.", validate, @@ -832,7 +832,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): radio_fields = () - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.radio_fields' must be a dictionary.", validate, @@ -843,7 +843,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): radio_fields = {"non_existent_field": None} - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.radio_fields' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.", validate, @@ -854,7 +854,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): radio_fields = {"name": None} - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.radio_fields\['name'\]' is neither an instance of ForeignKey nor does have choices set.", validate, @@ -865,7 +865,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): radio_fields = {"state": None} - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.radio_fields\['state'\]' is neither admin.HORIZONTAL nor admin.VERTICAL.", validate, @@ -883,7 +883,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): prepopulated_fields = () - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.prepopulated_fields' must be a dictionary.", validate, @@ -894,7 +894,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): prepopulated_fields = {"non_existent_field": None} - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.prepopulated_fields' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.", validate, @@ -905,7 +905,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): prepopulated_fields = {"slug": ("non_existent_field",)} - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.prepopulated_fields\['slug'\]\[0\]' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.", validate, @@ -916,7 +916,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): prepopulated_fields = {"users": ("name",)} - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.prepopulated_fields\['users'\]' is either a DateTimeField, ForeignKey or ManyToManyField. This isn't allowed.", validate, @@ -934,7 +934,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): list_display = 10 - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.list_display' must be a list or tuple.", validate, @@ -945,7 +945,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): list_display = ('non_existent_field',) - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, str_prefix("ValidationTestModelAdmin.list_display\[0\], %(_)s'non_existent_field' is not a callable or an attribute of 'ValidationTestModelAdmin' or found in the model 'ValidationTestModel'."), validate, @@ -956,7 +956,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): list_display = ('users',) - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.list_display\[0\]', 'users' is a ManyToManyField which is not supported.", validate, @@ -979,7 +979,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): list_display_links = 10 - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.list_display_links' must be a list or tuple.", validate, @@ -990,7 +990,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): list_display_links = ('non_existent_field',) - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.list_display_links\[0\]' refers to 'non_existent_field' which is not defined in 'list_display'.", validate, @@ -1001,7 +1001,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): list_display_links = ('name',) - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.list_display_links\[0\]' refers to 'name' which is not defined in 'list_display'.", validate, @@ -1025,7 +1025,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): list_filter = 10 - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.list_filter' must be a list or tuple.", validate, @@ -1036,7 +1036,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): list_filter = ('non_existent_field',) - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.list_filter\[0\]' refers to 'non_existent_field' which does not refer to a Field.", validate, @@ -1050,7 +1050,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): list_filter = (RandomClass,) - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.list_filter\[0\]' is 'RandomClass' which is not a descendant of ListFilter.", validate, @@ -1061,7 +1061,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): list_filter = (('is_active', RandomClass),) - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.list_filter\[0\]\[1\]' is 'RandomClass' which is not of type FieldListFilter.", validate, @@ -1080,7 +1080,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): list_filter = (('is_active', AwesomeFilter),) - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.list_filter\[0\]\[1\]' is 'AwesomeFilter' which is not of type FieldListFilter.", validate, @@ -1091,7 +1091,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): list_filter = (BooleanFieldListFilter,) - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.list_filter\[0\]' is 'BooleanFieldListFilter' which is of type FieldListFilter but is not associated with a field name.", validate, @@ -1111,7 +1111,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): list_per_page = 'hello' - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.list_per_page' should be a integer.", validate, @@ -1129,7 +1129,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): list_max_show_all = 'hello' - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.list_max_show_all' should be an integer.", validate, @@ -1147,7 +1147,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): search_fields = 10 - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.search_fields' must be a list or tuple.", validate, @@ -1160,7 +1160,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): date_hierarchy = 'non_existent_field' - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.date_hierarchy' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.", validate, @@ -1171,7 +1171,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): date_hierarchy = 'name' - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.date_hierarchy is neither an instance of DateField nor DateTimeField.", validate, @@ -1189,7 +1189,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): ordering = 10 - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.ordering' must be a list or tuple.", validate, @@ -1200,7 +1200,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): ordering = ('non_existent_field',) - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.ordering\[0\]' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.", validate, @@ -1211,7 +1211,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): ordering = ('?', 'name') - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.ordering' has the random ordering marker '\?', but contains other fields as well. Please either remove '\?' or the other fields.", validate, @@ -1239,7 +1239,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): list_select_related = 1 - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.list_select_related' should be a boolean.", validate, @@ -1257,7 +1257,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): save_as = 1 - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.save_as' should be a boolean.", validate, @@ -1275,7 +1275,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): save_on_top = 1 - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.save_on_top' should be a boolean.", validate, @@ -1293,7 +1293,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): inlines = 10 - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.inlines' must be a list or tuple.", validate, @@ -1307,7 +1307,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): inlines = [ValidationTestInline] - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.inlines\[0\]' does not inherit from BaseModelAdmin.", validate, @@ -1321,7 +1321,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): inlines = [ValidationTestInline] - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'model' is a required attribute of 'ValidationTestModelAdmin.inlines\[0\]'.", validate, @@ -1338,7 +1338,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): inlines = [ValidationTestInline] - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestModelAdmin.inlines\[0\].model' does not inherit from models.Model.", validate, @@ -1363,7 +1363,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): inlines = [ValidationTestInline] - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestInline.fields' must be a list or tuple.", validate, @@ -1378,7 +1378,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): inlines = [ValidationTestInline] - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestInline.fields' refers to field 'non_existent_field' that is missing from the form.", validate, @@ -1395,7 +1395,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): inlines = [ValidationTestInline] - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestInline.fk_name' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestInlineModel'.", validate, @@ -1421,7 +1421,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): inlines = [ValidationTestInline] - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestInline.extra' should be a integer.", validate, @@ -1447,7 +1447,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): inlines = [ValidationTestInline] - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestInline.max_num' should be an integer or None \(default\).", validate, @@ -1476,7 +1476,7 @@ class ValidationTests(unittest.TestCase): class ValidationTestModelAdmin(ModelAdmin): inlines = [ValidationTestInline] - self.assertRaisesRegexp( + six.assertRaisesRegex(self, ImproperlyConfigured, "'ValidationTestInline.formset' does not inherit from BaseModelFormSet.", validate, diff --git a/tests/regressiontests/queries/tests.py b/tests/regressiontests/queries/tests.py index 005aa9650b..71ac107486 100644 --- a/tests/regressiontests/queries/tests.py +++ b/tests/regressiontests/queries/tests.py @@ -1677,10 +1677,7 @@ class CloneTests(TestCase): list(n_list) # Use the note queryset in a query, and evalute # that query in a way that involves cloning. - try: - self.assertEqual(ExtraInfo.objects.filter(note__in=n_list)[0].info, 'good') - except: - self.fail('Query should be clonable') + self.assertEqual(ExtraInfo.objects.filter(note__in=n_list)[0].info, 'good') class EmptyQuerySetTests(TestCase): diff --git a/tests/regressiontests/resolve_url/__init__.py b/tests/regressiontests/resolve_url/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regressiontests/resolve_url/models.py b/tests/regressiontests/resolve_url/models.py new file mode 100644 index 0000000000..238902edd2 --- /dev/null +++ b/tests/regressiontests/resolve_url/models.py @@ -0,0 +1,12 @@ +""" +Regression tests for the resolve_url function. +""" + +from django.db import models + + +class UnimportantThing(models.Model): + importance = models.IntegerField() + + def get_absolute_url(self): + return '/importance/%d/' % (self.importance,) diff --git a/tests/regressiontests/resolve_url/tests.py b/tests/regressiontests/resolve_url/tests.py new file mode 100644 index 0000000000..d0bf44abde --- /dev/null +++ b/tests/regressiontests/resolve_url/tests.py @@ -0,0 +1,68 @@ +from __future__ import unicode_literals + +from django.core.urlresolvers import NoReverseMatch +from django.contrib.auth.views import logout +from django.utils.unittest import TestCase +from django.shortcuts import resolve_url + +from .models import UnimportantThing + + +class ResolveUrlTests(TestCase): + """ + Tests for the ``resolve_url`` function. + """ + + def test_url_path(self): + """ + Tests that passing a URL path to ``resolve_url`` will result in the + same url. + """ + self.assertEqual('/something/', resolve_url('/something/')) + + def test_full_url(self): + """ + Tests that passing a full URL to ``resolve_url`` will result in the + same url. + """ + url = 'http://example.com/' + self.assertEqual(url, resolve_url(url)) + + def test_model(self): + """ + Tests that passing a model to ``resolve_url`` will result in + ``get_absolute_url`` being called on that model instance. + """ + m = UnimportantThing(importance=1) + self.assertEqual(m.get_absolute_url(), resolve_url(m)) + + def test_view_function(self): + """ + Tests that passing a view name to ``resolve_url`` will result in the + URL path mapping to that view name. + """ + resolved_url = resolve_url(logout) + self.assertEqual('/accounts/logout/', resolved_url) + + def test_valid_view_name(self): + """ + Tests that passing a view function to ``resolve_url`` will result in + the URL path mapping to that view. + """ + resolved_url = resolve_url('django.contrib.auth.views.logout') + self.assertEqual('/accounts/logout/', resolved_url) + + def test_domain(self): + """ + Tests that passing a domain to ``resolve_url`` returns the same domain. + """ + self.assertEqual(resolve_url('example.com'), 'example.com') + + def test_non_view_callable_raises_no_reverse_match(self): + """ + Tests that passing a non-view callable into ``resolve_url`` raises a + ``NoReverseMatch`` exception. + """ + with self.assertRaises(NoReverseMatch): + resolve_url(lambda: 'asdf') + diff --git a/tests/regressiontests/settings_tests/tests.py b/tests/regressiontests/settings_tests/tests.py index 7225fb03ef..aaf8bcffcf 100644 --- a/tests/regressiontests/settings_tests/tests.py +++ b/tests/regressiontests/settings_tests/tests.py @@ -71,16 +71,18 @@ class SettingGetter(object): def __init__(self): self.test = getattr(settings, 'TEST', 'undefined') -testvalue = None - -def signal_callback(sender, setting, value, **kwargs): - if setting == 'TEST': - global testvalue - testvalue = value - -signals.setting_changed.connect(signal_callback) class SettingsTests(TestCase): + def setUp(self): + self.testvalue = None + signals.setting_changed.connect(self.signal_callback) + + def tearDown(self): + signals.setting_changed.disconnect(self.signal_callback) + + def signal_callback(self, sender, setting, value, **kwargs): + if setting == 'TEST': + self.testvalue = value def test_override(self): settings.TEST = 'test' @@ -128,12 +130,12 @@ class SettingsTests(TestCase): def test_signal_callback_context_manager(self): self.assertRaises(AttributeError, getattr, settings, 'TEST') with self.settings(TEST='override'): - self.assertEqual(testvalue, 'override') - self.assertEqual(testvalue, None) + self.assertEqual(self.testvalue, 'override') + self.assertEqual(self.testvalue, None) @override_settings(TEST='override') def test_signal_callback_decorator(self): - self.assertEqual(testvalue, 'override') + self.assertEqual(self.testvalue, 'override') # # Regression tests for #10130: deleting settings. diff --git a/tests/regressiontests/signals_regress/tests.py b/tests/regressiontests/signals_regress/tests.py index 4809a1e2a5..8fb3ad5a48 100644 --- a/tests/regressiontests/signals_regress/tests.py +++ b/tests/regressiontests/signals_regress/tests.py @@ -6,31 +6,6 @@ from django.test import TestCase from .models import Author, Book -signal_output = [] - -def pre_save_test(signal, sender, instance, **kwargs): - signal_output.append('pre_save signal, %s' % instance) - if kwargs.get('raw'): - signal_output.append('Is raw') - -def post_save_test(signal, sender, instance, **kwargs): - signal_output.append('post_save signal, %s' % instance) - if 'created' in kwargs: - if kwargs['created']: - signal_output.append('Is created') - else: - signal_output.append('Is updated') - if kwargs.get('raw'): - signal_output.append('Is raw') - -def pre_delete_test(signal, sender, instance, **kwargs): - signal_output.append('pre_save signal, %s' % instance) - signal_output.append('instance.id is not None: %s' % (instance.id != None)) - -def post_delete_test(signal, sender, instance, **kwargs): - signal_output.append('post_delete signal, %s' % instance) - signal_output.append('instance.id is not None: %s' % (instance.id != None)) - class SignalsRegressTests(TestCase): """ Testing signals before/after saving and deleting. @@ -38,12 +13,35 @@ class SignalsRegressTests(TestCase): def get_signal_output(self, fn, *args, **kwargs): # Flush any existing signal output - global signal_output - signal_output = [] + self.signal_output = [] fn(*args, **kwargs) - return signal_output + return self.signal_output + + def pre_save_test(self, signal, sender, instance, **kwargs): + self.signal_output.append('pre_save signal, %s' % instance) + if kwargs.get('raw'): + self.signal_output.append('Is raw') + + def post_save_test(self, signal, sender, instance, **kwargs): + self.signal_output.append('post_save signal, %s' % instance) + if 'created' in kwargs: + if kwargs['created']: + self.signal_output.append('Is created') + else: + self.signal_output.append('Is updated') + if kwargs.get('raw'): + self.signal_output.append('Is raw') + + def pre_delete_test(self, signal, sender, instance, **kwargs): + self.signal_output.append('pre_save signal, %s' % instance) + self.signal_output.append('instance.id is not None: %s' % (instance.id != None)) + + def post_delete_test(self, signal, sender, instance, **kwargs): + self.signal_output.append('post_delete signal, %s' % instance) + self.signal_output.append('instance.id is not None: %s' % (instance.id != None)) def setUp(self): + self.signal_output = [] # Save up the number of connected signals so that we can check at the end # that all the signals we register get properly unregistered (#9989) self.pre_signals = (len(models.signals.pre_save.receivers), @@ -51,16 +49,16 @@ class SignalsRegressTests(TestCase): len(models.signals.pre_delete.receivers), len(models.signals.post_delete.receivers)) - models.signals.pre_save.connect(pre_save_test) - models.signals.post_save.connect(post_save_test) - models.signals.pre_delete.connect(pre_delete_test) - models.signals.post_delete.connect(post_delete_test) + models.signals.pre_save.connect(self.pre_save_test) + models.signals.post_save.connect(self.post_save_test) + models.signals.pre_delete.connect(self.pre_delete_test) + models.signals.post_delete.connect(self.post_delete_test) def tearDown(self): - models.signals.post_delete.disconnect(post_delete_test) - models.signals.pre_delete.disconnect(pre_delete_test) - models.signals.post_save.disconnect(post_save_test) - models.signals.pre_save.disconnect(pre_save_test) + models.signals.post_delete.disconnect(self.post_delete_test) + models.signals.pre_delete.disconnect(self.pre_delete_test) + models.signals.post_save.disconnect(self.post_save_test) + models.signals.pre_save.disconnect(self.pre_save_test) # Check that all our signals got disconnected properly. post_signals = (len(models.signals.pre_save.receivers), diff --git a/tests/regressiontests/staticfiles_tests/tests.py b/tests/regressiontests/staticfiles_tests/tests.py index 078788a6a9..7ecbccc448 100644 --- a/tests/regressiontests/staticfiles_tests/tests.py +++ b/tests/regressiontests/staticfiles_tests/tests.py @@ -617,7 +617,7 @@ class TestServeDisabled(TestServeStatic): settings.DEBUG = False def test_disabled_serving(self): - self.assertRaisesRegexp(ImproperlyConfigured, 'The staticfiles view ' + six.assertRaisesRegex(self, ImproperlyConfigured, 'The staticfiles view ' 'can only be used in debug mode ', self._response, 'test.txt') diff --git a/tests/regressiontests/templates/custom.py b/tests/regressiontests/templates/custom.py index 4e295d990e..4aea08237d 100644 --- a/tests/regressiontests/templates/custom.py +++ b/tests/regressiontests/templates/custom.py @@ -1,6 +1,7 @@ from __future__ import absolute_import, unicode_literals from django import template +from django.utils import six from django.utils.unittest import TestCase from .templatetags import custom @@ -51,7 +52,7 @@ class CustomTagTests(TestCase): t = template.Template('{% load custom %}{% simple_one_default one=99 two="hello" %}') self.assertEqual(t.render(c), 'simple_one_default - Expected result: 99, hello') - self.assertRaisesRegexp(template.TemplateSyntaxError, + six.assertRaisesRegex(self, template.TemplateSyntaxError, "'simple_one_default' received unexpected keyword argument 'three'", template.Template, '{% load custom %}{% simple_one_default 99 two="hello" three="foo" %}') @@ -70,22 +71,22 @@ class CustomTagTests(TestCase): t = template.Template('{% load custom %}{% simple_only_unlimited_args 37 42 56 89 %}') self.assertEqual(t.render(c), 'simple_only_unlimited_args - Expected result: 37, 42, 56, 89') - self.assertRaisesRegexp(template.TemplateSyntaxError, + six.assertRaisesRegex(self, template.TemplateSyntaxError, "'simple_two_params' received too many positional arguments", template.Template, '{% load custom %}{% simple_two_params 37 42 56 %}') - self.assertRaisesRegexp(template.TemplateSyntaxError, + six.assertRaisesRegex(self, template.TemplateSyntaxError, "'simple_one_default' received too many positional arguments", template.Template, '{% load custom %}{% simple_one_default 37 42 56 %}') t = template.Template('{% load custom %}{% simple_unlimited_args_kwargs 37 40|add:2 56 eggs="scrambled" four=1|add:3 %}') self.assertEqual(t.render(c), 'simple_unlimited_args_kwargs - Expected result: 37, 42, 56 / eggs=scrambled, four=4') - self.assertRaisesRegexp(template.TemplateSyntaxError, + six.assertRaisesRegex(self, template.TemplateSyntaxError, "'simple_unlimited_args_kwargs' received some positional argument\(s\) after some keyword argument\(s\)", template.Template, '{% load custom %}{% simple_unlimited_args_kwargs 37 40|add:2 eggs="scrambled" 56 four=1|add:3 %}') - self.assertRaisesRegexp(template.TemplateSyntaxError, + six.assertRaisesRegex(self, template.TemplateSyntaxError, "'simple_unlimited_args_kwargs' received multiple values for keyword argument 'eggs'", template.Template, '{% load custom %}{% simple_unlimited_args_kwargs 37 eggs="scrambled" eggs="scrambled" %}') @@ -101,7 +102,7 @@ class CustomTagTests(TestCase): def test_simple_tag_missing_context(self): # The 'context' parameter must be present when takes_context is True - self.assertRaisesRegexp(template.TemplateSyntaxError, + six.assertRaisesRegex(self, template.TemplateSyntaxError, "'simple_tag_without_context_parameter' is decorated with takes_context=True so it must have a first argument of 'context'", template.Template, '{% load custom %}{% simple_tag_without_context_parameter 123 %}') @@ -135,7 +136,7 @@ class CustomTagTests(TestCase): t = template.Template('{% load custom %}{% inclusion_one_default one=99 two="hello" %}') self.assertEqual(t.render(c), 'inclusion_one_default - Expected result: 99, hello\n') - self.assertRaisesRegexp(template.TemplateSyntaxError, + six.assertRaisesRegex(self, template.TemplateSyntaxError, "'inclusion_one_default' received unexpected keyword argument 'three'", template.Template, '{% load custom %}{% inclusion_one_default 99 two="hello" three="foo" %}') @@ -154,36 +155,36 @@ class CustomTagTests(TestCase): t = template.Template('{% load custom %}{% inclusion_only_unlimited_args 37 42 56 89 %}') self.assertEqual(t.render(c), 'inclusion_only_unlimited_args - Expected result: 37, 42, 56, 89\n') - self.assertRaisesRegexp(template.TemplateSyntaxError, + six.assertRaisesRegex(self, template.TemplateSyntaxError, "'inclusion_two_params' received too many positional arguments", template.Template, '{% load custom %}{% inclusion_two_params 37 42 56 %}') - self.assertRaisesRegexp(template.TemplateSyntaxError, + six.assertRaisesRegex(self, template.TemplateSyntaxError, "'inclusion_one_default' received too many positional arguments", template.Template, '{% load custom %}{% inclusion_one_default 37 42 56 %}') - self.assertRaisesRegexp(template.TemplateSyntaxError, + six.assertRaisesRegex(self, template.TemplateSyntaxError, "'inclusion_one_default' did not receive value\(s\) for the argument\(s\): 'one'", template.Template, '{% load custom %}{% inclusion_one_default %}') - self.assertRaisesRegexp(template.TemplateSyntaxError, + six.assertRaisesRegex(self, template.TemplateSyntaxError, "'inclusion_unlimited_args' did not receive value\(s\) for the argument\(s\): 'one'", template.Template, '{% load custom %}{% inclusion_unlimited_args %}') t = template.Template('{% load custom %}{% inclusion_unlimited_args_kwargs 37 40|add:2 56 eggs="scrambled" four=1|add:3 %}') self.assertEqual(t.render(c), 'inclusion_unlimited_args_kwargs - Expected result: 37, 42, 56 / eggs=scrambled, four=4\n') - self.assertRaisesRegexp(template.TemplateSyntaxError, + six.assertRaisesRegex(self, template.TemplateSyntaxError, "'inclusion_unlimited_args_kwargs' received some positional argument\(s\) after some keyword argument\(s\)", template.Template, '{% load custom %}{% inclusion_unlimited_args_kwargs 37 40|add:2 eggs="scrambled" 56 four=1|add:3 %}') - self.assertRaisesRegexp(template.TemplateSyntaxError, + six.assertRaisesRegex(self, template.TemplateSyntaxError, "'inclusion_unlimited_args_kwargs' received multiple values for keyword argument 'eggs'", template.Template, '{% load custom %}{% inclusion_unlimited_args_kwargs 37 eggs="scrambled" eggs="scrambled" %}') def test_include_tag_missing_context(self): # The 'context' parameter must be present when takes_context is True - self.assertRaisesRegexp(template.TemplateSyntaxError, + six.assertRaisesRegex(self, template.TemplateSyntaxError, "'inclusion_tag_without_context_parameter' is decorated with takes_context=True so it must have a first argument of 'context'", template.Template, '{% load custom %}{% inclusion_tag_without_context_parameter 123 %}') @@ -296,7 +297,7 @@ class CustomTagTests(TestCase): t = template.Template('{% load custom %}{% assignment_one_default one=99 two="hello" as var %}The result is: {{ var }}') self.assertEqual(t.render(c), 'The result is: assignment_one_default - Expected result: 99, hello') - self.assertRaisesRegexp(template.TemplateSyntaxError, + six.assertRaisesRegex(self, template.TemplateSyntaxError, "'assignment_one_default' received unexpected keyword argument 'three'", template.Template, '{% load custom %}{% assignment_one_default 99 two="hello" three="foo" as var %}') @@ -315,42 +316,42 @@ class CustomTagTests(TestCase): t = template.Template('{% load custom %}{% assignment_only_unlimited_args 37 42 56 89 as var %}The result is: {{ var }}') self.assertEqual(t.render(c), 'The result is: assignment_only_unlimited_args - Expected result: 37, 42, 56, 89') - self.assertRaisesRegexp(template.TemplateSyntaxError, + six.assertRaisesRegex(self, template.TemplateSyntaxError, "'assignment_one_param' tag takes at least 2 arguments and the second last argument must be 'as'", template.Template, '{% load custom %}{% assignment_one_param 37 %}The result is: {{ var }}') - self.assertRaisesRegexp(template.TemplateSyntaxError, + six.assertRaisesRegex(self, template.TemplateSyntaxError, "'assignment_one_param' tag takes at least 2 arguments and the second last argument must be 'as'", template.Template, '{% load custom %}{% assignment_one_param 37 as %}The result is: {{ var }}') - self.assertRaisesRegexp(template.TemplateSyntaxError, + six.assertRaisesRegex(self, template.TemplateSyntaxError, "'assignment_one_param' tag takes at least 2 arguments and the second last argument must be 'as'", template.Template, '{% load custom %}{% assignment_one_param 37 ass var %}The result is: {{ var }}') - self.assertRaisesRegexp(template.TemplateSyntaxError, + six.assertRaisesRegex(self, template.TemplateSyntaxError, "'assignment_two_params' received too many positional arguments", template.Template, '{% load custom %}{% assignment_two_params 37 42 56 as var %}The result is: {{ var }}') - self.assertRaisesRegexp(template.TemplateSyntaxError, + six.assertRaisesRegex(self, template.TemplateSyntaxError, "'assignment_one_default' received too many positional arguments", template.Template, '{% load custom %}{% assignment_one_default 37 42 56 as var %}The result is: {{ var }}') - self.assertRaisesRegexp(template.TemplateSyntaxError, + six.assertRaisesRegex(self, template.TemplateSyntaxError, "'assignment_one_default' did not receive value\(s\) for the argument\(s\): 'one'", template.Template, '{% load custom %}{% assignment_one_default as var %}The result is: {{ var }}') - self.assertRaisesRegexp(template.TemplateSyntaxError, + six.assertRaisesRegex(self, template.TemplateSyntaxError, "'assignment_unlimited_args' did not receive value\(s\) for the argument\(s\): 'one'", template.Template, '{% load custom %}{% assignment_unlimited_args as var %}The result is: {{ var }}') t = template.Template('{% load custom %}{% assignment_unlimited_args_kwargs 37 40|add:2 56 eggs="scrambled" four=1|add:3 as var %}The result is: {{ var }}') self.assertEqual(t.render(c), 'The result is: assignment_unlimited_args_kwargs - Expected result: 37, 42, 56 / eggs=scrambled, four=4') - self.assertRaisesRegexp(template.TemplateSyntaxError, + six.assertRaisesRegex(self, template.TemplateSyntaxError, "'assignment_unlimited_args_kwargs' received some positional argument\(s\) after some keyword argument\(s\)", template.Template, '{% load custom %}{% assignment_unlimited_args_kwargs 37 40|add:2 eggs="scrambled" 56 four=1|add:3 as var %}The result is: {{ var }}') - self.assertRaisesRegexp(template.TemplateSyntaxError, + six.assertRaisesRegex(self, template.TemplateSyntaxError, "'assignment_unlimited_args_kwargs' received multiple values for keyword argument 'eggs'", template.Template, '{% load custom %}{% assignment_unlimited_args_kwargs 37 eggs="scrambled" eggs="scrambled" as var %}The result is: {{ var }}') @@ -371,6 +372,6 @@ class CustomTagTests(TestCase): def test_assignment_tag_missing_context(self): # The 'context' parameter must be present when takes_context is True - self.assertRaisesRegexp(template.TemplateSyntaxError, + six.assertRaisesRegex(self, template.TemplateSyntaxError, "'assignment_tag_without_context_parameter' is decorated with takes_context=True so it must have a first argument of 'context'", template.Template, '{% load custom %}{% assignment_tag_without_context_parameter 123 as var %}') diff --git a/tests/regressiontests/templates/loaders.py b/tests/regressiontests/templates/loaders.py index 6b635c8f23..7fbb0841f9 100644 --- a/tests/regressiontests/templates/loaders.py +++ b/tests/regressiontests/templates/loaders.py @@ -17,7 +17,7 @@ import os.path from django.template import TemplateDoesNotExist, Context from django.template.loaders.eggs import Loader as EggLoader from django.template import loader -from django.utils import unittest +from django.utils import unittest, six from django.utils.six import StringIO @@ -30,7 +30,7 @@ class MockProvider(pkg_resources.NullProvider): def _has(self, path): return path in self.module._resources - def _isdir(self,path): + def _isdir(self, path): return False def get_resource_stream(self, manager, resource_name): @@ -61,8 +61,8 @@ class EggLoaderTest(unittest.TestCase): self.empty_egg = create_egg("egg_empty", {}) self.egg_1 = create_egg("egg_1", { - os.path.normcase('templates/y.html') : StringIO("y"), - os.path.normcase('templates/x.txt') : StringIO("x"), + os.path.normcase('templates/y.html'): StringIO("y"), + os.path.normcase('templates/x.txt'): StringIO("x"), }) self._old_installed_apps = settings.INSTALLED_APPS settings.INSTALLED_APPS = [] @@ -144,12 +144,12 @@ class RenderToStringTest(unittest.TestCase): self.assertEqual(context['obj'], 'before') def test_empty_list(self): - self.assertRaisesRegexp(TemplateDoesNotExist, + six.assertRaisesRegex(self, TemplateDoesNotExist, 'No template names provided$', loader.render_to_string, []) def test_select_templates_from_empty_list(self): - self.assertRaisesRegexp(TemplateDoesNotExist, + six.assertRaisesRegex(self, TemplateDoesNotExist, 'No template names provided$', loader.select_template, []) diff --git a/tests/regressiontests/test_utils/tests.py b/tests/regressiontests/test_utils/tests.py index 3749014372..468af77f44 100644 --- a/tests/regressiontests/test_utils/tests.py +++ b/tests/regressiontests/test_utils/tests.py @@ -137,15 +137,15 @@ class AssertTemplateUsedContextManagerTests(TestCase): pass def test_error_message(self): - with self.assertRaisesRegexp(AssertionError, r'^template_used/base\.html'): + with six.assertRaisesRegex(self, AssertionError, r'^template_used/base\.html'): with self.assertTemplateUsed('template_used/base.html'): pass - with self.assertRaisesRegexp(AssertionError, r'^template_used/base\.html'): + with six.assertRaisesRegex(self, AssertionError, r'^template_used/base\.html'): with self.assertTemplateUsed(template_name='template_used/base.html'): pass - with self.assertRaisesRegexp(AssertionError, r'^template_used/base\.html.*template_used/alternative\.html$'): + with six.assertRaisesRegex(self, AssertionError, r'^template_used/base\.html.*template_used/alternative\.html$'): with self.assertTemplateUsed('template_used/base.html'): render_to_string('template_used/alternative.html') diff --git a/tests/regressiontests/transactions_regress/tests.py b/tests/regressiontests/transactions_regress/tests.py index 90b3df03d4..472e2aafd9 100644 --- a/tests/regressiontests/transactions_regress/tests.py +++ b/tests/regressiontests/transactions_regress/tests.py @@ -1,7 +1,6 @@ from __future__ import absolute_import -from django.core.exceptions import ImproperlyConfigured -from django.db import connection, connections, transaction, DEFAULT_DB_ALIAS +from django.db import connection, connections, transaction, DEFAULT_DB_ALIAS, DatabaseError from django.db.transaction import commit_on_success, commit_manually, TransactionManagementError from django.test import TransactionTestCase, skipUnlessDBFeature from django.test.utils import override_settings @@ -151,21 +150,14 @@ class TestTransactionClosing(TransactionTestCase): # Create a user create_system_user() - try: - # The second call to create_system_user should fail for violating a unique constraint - # (it's trying to re-create the same user) + with self.assertRaises(DatabaseError): + # The second call to create_system_user should fail for violating + # a unique constraint (it's trying to re-create the same user) create_system_user() - except: - pass - else: - raise ImproperlyConfigured('Unique constraint not enforced on django.contrib.auth.models.User') - try: - # Try to read the database. If the last transaction was indeed closed, - # this should cause no problems - _ = User.objects.all()[0] - except: - self.fail("A transaction consisting of a failed operation was not closed.") + # Try to read the database. If the last transaction was indeed closed, + # this should cause no problems + User.objects.all()[0] @override_settings(DEBUG=True) def test_failing_query_transaction_closed_debug(self): diff --git a/tests/regressiontests/urlpatterns_reverse/tests.py b/tests/regressiontests/urlpatterns_reverse/tests.py index 168892719a..0ea5ffe380 100644 --- a/tests/regressiontests/urlpatterns_reverse/tests.py +++ b/tests/regressiontests/urlpatterns_reverse/tests.py @@ -4,6 +4,7 @@ Unit tests for reverse URL lookups. from __future__ import absolute_import, unicode_literals from django.conf import settings +from django.contrib.auth.models import User from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist from django.core.urlresolvers import (reverse, resolve, get_callable, get_resolver, NoReverseMatch, Resolver404, ResolverMatch, RegexURLResolver, @@ -11,10 +12,9 @@ from django.core.urlresolvers import (reverse, resolve, get_callable, from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect from django.shortcuts import redirect from django.test import TestCase -from django.utils import unittest -from django.contrib.auth.models import User +from django.utils import unittest, six -from . import urlconf_outer, urlconf_inner, middleware, views +from . import urlconf_outer, middleware, views resolve_test_data = ( @@ -535,7 +535,7 @@ class ViewLoadingTests(TestCase): def test_view_loading(self): # A missing view (identified by an AttributeError) should raise # ViewDoesNotExist, ... - self.assertRaisesRegexp(ViewDoesNotExist, ".*View does not exist in.*", + six.assertRaisesRegex(self, ViewDoesNotExist, ".*View does not exist in.*", get_callable, 'regressiontests.urlpatterns_reverse.views.i_should_not_exist') # ... but if the AttributeError is caused by something else don't diff --git a/tests/regressiontests/utils/os_utils.py b/tests/regressiontests/utils/os_utils.py new file mode 100644 index 0000000000..a78f348cf5 --- /dev/null +++ b/tests/regressiontests/utils/os_utils.py @@ -0,0 +1,21 @@ +from django.utils import unittest +from django.utils._os import safe_join + + +class SafeJoinTests(unittest.TestCase): + def test_base_path_ends_with_sep(self): + self.assertEqual( + safe_join("/abc/", "abc"), + "/abc/abc", + ) + + def test_root_path(self): + self.assertEqual( + safe_join("/", "path"), + "/path", + ) + + self.assertEqual( + safe_join("/", ""), + "/", + ) diff --git a/tests/regressiontests/utils/tests.py b/tests/regressiontests/utils/tests.py index f4fa75b177..061c669eb7 100644 --- a/tests/regressiontests/utils/tests.py +++ b/tests/regressiontests/utils/tests.py @@ -21,6 +21,7 @@ from .http import TestUtilsHttp from .ipv6 import TestUtilsIPv6 from .jslex import JsToCForGettextTest, JsTokensTest from .module_loading import CustomLoader, DefaultLoader, EggLoader +from .os_utils import SafeJoinTests from .regex_helper import NormalizeTests from .simplelazyobject import TestUtilsSimpleLazyObject from .termcolors import TermColorTests diff --git a/tests/regressiontests/wsgi/tests.py b/tests/regressiontests/wsgi/tests.py index 5a40bd88e2..6c1483d2cd 100644 --- a/tests/regressiontests/wsgi/tests.py +++ b/tests/regressiontests/wsgi/tests.py @@ -6,8 +6,7 @@ from django.core.wsgi import get_wsgi_application from django.test import TestCase from django.test.client import RequestFactory from django.test.utils import override_settings -from django.utils import six -from django.utils import unittest +from django.utils import six, unittest class WSGITest(TestCase): @@ -84,7 +83,7 @@ class GetInternalWSGIApplicationTest(unittest.TestCase): @override_settings(WSGI_APPLICATION="regressiontests.wsgi.noexist.app") def test_bad_module(self): - with self.assertRaisesRegexp( + with six.assertRaisesRegex(self, ImproperlyConfigured, r"^WSGI application 'regressiontests.wsgi.noexist.app' could not be loaded; could not import module 'regressiontests.wsgi.noexist':"): @@ -93,7 +92,7 @@ class GetInternalWSGIApplicationTest(unittest.TestCase): @override_settings(WSGI_APPLICATION="regressiontests.wsgi.wsgi.noexist") def test_bad_name(self): - with self.assertRaisesRegexp( + with six.assertRaisesRegex(self, ImproperlyConfigured, r"^WSGI application 'regressiontests.wsgi.wsgi.noexist' could not be loaded; can't find 'noexist' in module 'regressiontests.wsgi.wsgi':"): diff --git a/tests/runtests.py b/tests/runtests.py index c548d2745b..a81fee6858 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -92,7 +92,7 @@ def setup(verbosity, test_labels): settings.TEMPLATE_DIRS = (os.path.join(RUNTESTS_DIR, TEST_TEMPLATE_DIR),) settings.USE_I18N = True settings.LANGUAGE_CODE = 'en' - settings.LOGIN_URL = '/accounts/login/' + settings.LOGIN_URL = 'django.contrib.auth.views.login' settings.MIDDLEWARE_CLASSES = ( 'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',