Merge remote-tracking branch 'core/master' into schema-alteration

Conflicts:
	django/db/backends/__init__.py
	django/db/models/fields/related.py
	tests/field_deconstruction/tests.py
This commit is contained in:
Andrew Godwin 2013-06-28 17:32:57 +01:00
commit 7a47ba6f6a
130 changed files with 1166 additions and 419 deletions

View File

@ -97,6 +97,7 @@ answer newbie questions, and generally made Django that much better:
Ned Batchelder <http://www.nedbatchelder.com/> Ned Batchelder <http://www.nedbatchelder.com/>
batiste@dosimple.ch batiste@dosimple.ch
Batman Batman
Oliver Beattie <oliver@obeattie.com>
Brian Beck <http://blog.brianbeck.com/> Brian Beck <http://blog.brianbeck.com/>
Shannon -jj Behrens <http://jjinux.blogspot.com/> Shannon -jj Behrens <http://jjinux.blogspot.com/>
Esdras Beleza <linux@esdrasbeleza.com> Esdras Beleza <linux@esdrasbeleza.com>
@ -151,6 +152,7 @@ answer newbie questions, and generally made Django that much better:
Antonis Christofides <anthony@itia.ntua.gr> Antonis Christofides <anthony@itia.ntua.gr>
Michal Chruszcz <troll@pld-linux.org> Michal Chruszcz <troll@pld-linux.org>
Can Burak Çilingir <canburak@cs.bilgi.edu.tr> Can Burak Çilingir <canburak@cs.bilgi.edu.tr>
Andrew Clark <amclark7@gmail.com>
Ian Clelland <clelland@gmail.com> Ian Clelland <clelland@gmail.com>
Travis Cline <travis.cline@gmail.com> Travis Cline <travis.cline@gmail.com>
Russell Cloran <russell@rucus.net> Russell Cloran <russell@rucus.net>
@ -248,6 +250,7 @@ answer newbie questions, and generally made Django that much better:
martin.glueck@gmail.com martin.glueck@gmail.com
Ben Godfrey <http://aftnn.org> Ben Godfrey <http://aftnn.org>
GomoX <gomo@datafull.com> GomoX <gomo@datafull.com>
Gil Gonçalves <lursty@gmail.com>
Guilherme Mesquita Gondim <semente@taurinus.org> Guilherme Mesquita Gondim <semente@taurinus.org>
Mario Gonzalez <gonzalemario@gmail.com> Mario Gonzalez <gonzalemario@gmail.com>
David Gouldin <dgouldin@gmail.com> David Gouldin <dgouldin@gmail.com>
@ -271,6 +274,7 @@ answer newbie questions, and generally made Django that much better:
Brian Harring <ferringb@gmail.com> Brian Harring <ferringb@gmail.com>
Brant Harris Brant Harris
Ronny Haryanto <http://ronny.haryan.to/> Ronny Haryanto <http://ronny.haryan.to/>
Axel Haustant <noirbizarre@gmail.com>
Hawkeye Hawkeye
Kent Hauser <kent@khauser.net> Kent Hauser <kent@khauser.net>
Joe Heck <http://www.rhonabwy.com/wp/> Joe Heck <http://www.rhonabwy.com/wp/>
@ -486,7 +490,7 @@ answer newbie questions, and generally made Django that much better:
Brian Ray <http://brianray.chipy.org/> Brian Ray <http://brianray.chipy.org/>
Lee Reilly <lee@leereilly.net> Lee Reilly <lee@leereilly.net>
Łukasz Rekucki <lrekucki@gmail.com> Łukasz Rekucki <lrekucki@gmail.com>
remco@diji.biz Remco Wendt <remco.wendt@gmail.com>
Marc Remolt <m.remolt@webmasters.de> Marc Remolt <m.remolt@webmasters.de>
Bruno Renié <buburno@gmail.com> Bruno Renié <buburno@gmail.com>
David Reynolds <david@reynoldsfamily.org.uk> David Reynolds <david@reynoldsfamily.org.uk>
@ -608,6 +612,7 @@ answer newbie questions, and generally made Django that much better:
Filip Wasilewski <filip.wasilewski@gmail.com> Filip Wasilewski <filip.wasilewski@gmail.com>
Dan Watson <http://danwatson.net/> Dan Watson <http://danwatson.net/>
Joel Watts <joel@joelwatts.com> Joel Watts <joel@joelwatts.com>
Russ Webber
Lakin Wecker <lakin@structuredabstraction.com> Lakin Wecker <lakin@structuredabstraction.com>
Chris Wesseling <Chris.Wesseling@cwi.nl> Chris Wesseling <Chris.Wesseling@cwi.nl>
Benjamin Wohlwend <piquadrat@gmail.com> Benjamin Wohlwend <piquadrat@gmail.com>

View File

@ -1,4 +1,4 @@
VERSION = (1, 6, 0, 'alpha', 1) VERSION = (1, 7, 0, 'alpha', 0)
def get_version(*args, **kwargs): def get_version(*args, **kwargs):
# Don't litter django/__init__.py with all the get_version stuff. # Don't litter django/__init__.py with all the get_version stuff.

View File

@ -19,7 +19,7 @@ def delete_selected(modeladmin, request, queryset):
deleteable objects, or, if the user has no permission one of the related deleteable objects, or, if the user has no permission one of the related
childs (foreignkeys), a "permission denied" message. childs (foreignkeys), a "permission denied" message.
Next, it delets all selected objects and redirects back to the change list. Next, it deletes all selected objects and redirects back to the change list.
""" """
opts = modeladmin.model._meta opts = modeladmin.model._meta
app_label = opts.app_label app_label = opts.app_label

View File

@ -4,18 +4,15 @@ from functools import partial, reduce, update_wrapper
from django import forms from django import forms
from django.conf import settings from django.conf import settings
from django.forms.formsets import all_valid, DELETION_FIELD_NAME from django.contrib import messages
from django.forms.models import (modelform_factory, modelformset_factory,
inlineformset_factory, BaseInlineFormSet, modelform_defines_fields)
from django.contrib.contenttypes.models import ContentType
from django.contrib.admin import widgets, helpers from django.contrib.admin import widgets, helpers
from django.contrib.admin.util import (unquote, flatten_fieldsets, get_deleted_objects, from django.contrib.admin.util import (unquote, flatten_fieldsets, get_deleted_objects,
model_format_dict, NestedObjects, lookup_needs_distinct) model_format_dict, NestedObjects, lookup_needs_distinct)
from django.contrib.admin import validation from django.contrib.admin import validation
from django.contrib.admin.templatetags.admin_static import static from django.contrib.admin.templatetags.admin_static import static
from django.contrib.admin.templatetags.admin_urls import add_preserved_filters from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
from django.contrib import messages from django.contrib.auth import get_permission_codename
from django.views.decorators.csrf import csrf_protect from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import PermissionDenied, ValidationError, FieldError from django.core.exceptions import PermissionDenied, ValidationError, FieldError
from django.core.paginator import Paginator from django.core.paginator import Paginator
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
@ -24,7 +21,10 @@ from django.db.models.constants import LOOKUP_SEP
from django.db.models.related import RelatedObject from django.db.models.related import RelatedObject
from django.db.models.fields import BLANK_CHOICE_DASH, FieldDoesNotExist from django.db.models.fields import BLANK_CHOICE_DASH, FieldDoesNotExist
from django.db.models.sql.constants import QUERY_TERMS from django.db.models.sql.constants import QUERY_TERMS
from django.http import Http404, HttpResponse, HttpResponseRedirect from django.forms.formsets import all_valid, DELETION_FIELD_NAME
from django.forms.models import (modelform_factory, modelformset_factory,
inlineformset_factory, BaseInlineFormSet, modelform_defines_fields)
from django.http import Http404, HttpResponseRedirect
from django.http.response import HttpResponseBase from django.http.response import HttpResponseBase
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.template.response import SimpleTemplateResponse, TemplateResponse from django.template.response import SimpleTemplateResponse, TemplateResponse
@ -39,6 +39,10 @@ from django.utils.text import capfirst, get_text_list
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.utils.translation import ungettext from django.utils.translation import ungettext
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.views.decorators.csrf import csrf_protect
IS_POPUP_VAR = '_popup'
HORIZONTAL, VERTICAL = 1, 2 HORIZONTAL, VERTICAL = 1, 2
# returns the <ul> class for a given radio_admin field # returns the <ul> class for a given radio_admin field
@ -56,15 +60,15 @@ FORMFIELD_FOR_DBFIELD_DEFAULTS = {
'form_class': forms.SplitDateTimeField, 'form_class': forms.SplitDateTimeField,
'widget': widgets.AdminSplitDateTime 'widget': widgets.AdminSplitDateTime
}, },
models.DateField: {'widget': widgets.AdminDateWidget}, models.DateField: {'widget': widgets.AdminDateWidget},
models.TimeField: {'widget': widgets.AdminTimeWidget}, models.TimeField: {'widget': widgets.AdminTimeWidget},
models.TextField: {'widget': widgets.AdminTextareaWidget}, models.TextField: {'widget': widgets.AdminTextareaWidget},
models.URLField: {'widget': widgets.AdminURLFieldWidget}, models.URLField: {'widget': widgets.AdminURLFieldWidget},
models.IntegerField: {'widget': widgets.AdminIntegerFieldWidget}, models.IntegerField: {'widget': widgets.AdminIntegerFieldWidget},
models.BigIntegerField: {'widget': widgets.AdminBigIntegerFieldWidget}, models.BigIntegerField: {'widget': widgets.AdminBigIntegerFieldWidget},
models.CharField: {'widget': widgets.AdminTextInputWidget}, models.CharField: {'widget': widgets.AdminTextInputWidget},
models.ImageField: {'widget': widgets.AdminFileWidget}, models.ImageField: {'widget': widgets.AdminFileWidget},
models.FileField: {'widget': widgets.AdminFileWidget}, models.FileField: {'widget': widgets.AdminFileWidget},
} }
csrf_protect_m = method_decorator(csrf_protect) csrf_protect_m = method_decorator(csrf_protect)
@ -350,7 +354,8 @@ class BaseModelAdmin(six.with_metaclass(RenameBaseModelAdminMethods)):
Can be overridden by the user in subclasses. Can be overridden by the user in subclasses.
""" """
opts = self.opts opts = self.opts
return request.user.has_perm(opts.app_label + '.' + opts.get_add_permission()) codename = get_permission_codename('add', opts)
return request.user.has_perm("%s.%s" % (opts.app_label, codename))
def has_change_permission(self, request, obj=None): def has_change_permission(self, request, obj=None):
""" """
@ -364,7 +369,8 @@ class BaseModelAdmin(six.with_metaclass(RenameBaseModelAdminMethods)):
request has permission to change *any* object of the given type. request has permission to change *any* object of the given type.
""" """
opts = self.opts opts = self.opts
return request.user.has_perm(opts.app_label + '.' + opts.get_change_permission()) codename = get_permission_codename('change', opts)
return request.user.has_perm("%s.%s" % (opts.app_label, codename))
def has_delete_permission(self, request, obj=None): def has_delete_permission(self, request, obj=None):
""" """
@ -378,7 +384,9 @@ class BaseModelAdmin(six.with_metaclass(RenameBaseModelAdminMethods)):
request has permission to delete *any* object of the given type. request has permission to delete *any* object of the given type.
""" """
opts = self.opts opts = self.opts
return request.user.has_perm(opts.app_label + '.' + opts.get_delete_permission()) codename = get_permission_codename('delete', opts)
return request.user.has_perm("%s.%s" % (opts.app_label, codename))
class ModelAdmin(BaseModelAdmin): class ModelAdmin(BaseModelAdmin):
"Encapsulates all admin options and functionality for a given model." "Encapsulates all admin options and functionality for a given model."
@ -606,11 +614,11 @@ class ModelAdmin(BaseModelAdmin):
""" """
from django.contrib.admin.models import LogEntry, ADDITION from django.contrib.admin.models import LogEntry, ADDITION
LogEntry.objects.log_action( LogEntry.objects.log_action(
user_id = request.user.pk, user_id=request.user.pk,
content_type_id = ContentType.objects.get_for_model(object).pk, content_type_id=ContentType.objects.get_for_model(object).pk,
object_id = object.pk, object_id=object.pk,
object_repr = force_text(object), object_repr=force_text(object),
action_flag = ADDITION action_flag=ADDITION
) )
def log_change(self, request, object, message): def log_change(self, request, object, message):
@ -621,12 +629,12 @@ class ModelAdmin(BaseModelAdmin):
""" """
from django.contrib.admin.models import LogEntry, CHANGE from django.contrib.admin.models import LogEntry, CHANGE
LogEntry.objects.log_action( LogEntry.objects.log_action(
user_id = request.user.pk, user_id=request.user.pk,
content_type_id = ContentType.objects.get_for_model(object).pk, content_type_id=ContentType.objects.get_for_model(object).pk,
object_id = object.pk, object_id=object.pk,
object_repr = force_text(object), object_repr=force_text(object),
action_flag = CHANGE, action_flag=CHANGE,
change_message = message change_message=message
) )
def log_deletion(self, request, object, object_repr): def log_deletion(self, request, object, object_repr):
@ -638,11 +646,11 @@ class ModelAdmin(BaseModelAdmin):
""" """
from django.contrib.admin.models import LogEntry, DELETION from django.contrib.admin.models import LogEntry, DELETION
LogEntry.objects.log_action( LogEntry.objects.log_action(
user_id = request.user.pk, user_id=request.user.pk,
content_type_id = ContentType.objects.get_for_model(self.model).pk, content_type_id=ContentType.objects.get_for_model(self.model).pk,
object_id = object.pk, object_id=object.pk,
object_repr = object_repr, object_repr=object_repr,
action_flag = DELETION action_flag=DELETION
) )
def action_checkbox(self, obj): def action_checkbox(self, obj):
@ -660,8 +668,8 @@ class ModelAdmin(BaseModelAdmin):
""" """
# If self.actions is explicitly set to None that means that we don't # If self.actions is explicitly set to None that means that we don't
# want *any* actions enabled on this page. # want *any* actions enabled on this page.
from django.contrib.admin.views.main import IS_POPUP_VAR from django.contrib.admin.views.main import _is_changelist_popup
if self.actions is None or IS_POPUP_VAR in request.GET: if self.actions is None or _is_changelist_popup(request):
return SortedDict() return SortedDict()
actions = [] actions = []
@ -878,7 +886,7 @@ class ModelAdmin(BaseModelAdmin):
'has_add_permission': self.has_add_permission(request), 'has_add_permission': self.has_add_permission(request),
'has_change_permission': self.has_change_permission(request, obj), 'has_change_permission': self.has_change_permission(request, obj),
'has_delete_permission': self.has_delete_permission(request, obj), 'has_delete_permission': self.has_delete_permission(request, obj),
'has_file_field': True, # FIXME - this should check if form or formsets have a FileField, 'has_file_field': True, # FIXME - this should check if form or formsets have a FileField,
'has_absolute_url': hasattr(self.model, 'get_absolute_url'), 'has_absolute_url': hasattr(self.model, 'get_absolute_url'),
'form_url': form_url, 'form_url': form_url,
'opts': opts, 'opts': opts,
@ -908,12 +916,11 @@ class ModelAdmin(BaseModelAdmin):
msg_dict = {'name': force_text(opts.verbose_name), 'obj': force_text(obj)} msg_dict = {'name': force_text(opts.verbose_name), 'obj': force_text(obj)}
# Here, we distinguish between different save types by checking for # Here, we distinguish between different save types by checking for
# the presence of keys in request.POST. # the presence of keys in request.POST.
if "_popup" in request.POST: if IS_POPUP_VAR in request.POST:
return HttpResponse( return SimpleTemplateResponse('admin/popup_response.html', {
'<!DOCTYPE html><html><head><title></title></head><body>' 'pk_value': escape(pk_value),
'<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script></body></html>' % \ 'obj': escapejs(obj)
# escape() calls force_text. })
(escape(pk_value), escapejs(obj)))
elif "_continue" in request.POST: elif "_continue" in request.POST:
msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % msg_dict msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % msg_dict
@ -1049,7 +1056,7 @@ class ModelAdmin(BaseModelAdmin):
if action_form.is_valid(): if action_form.is_valid():
action = action_form.cleaned_data['action'] action = action_form.cleaned_data['action']
select_across = action_form.cleaned_data['select_across'] select_across = action_form.cleaned_data['select_across']
func, name, description = self.get_actions(request)[action] func = self.get_actions(request)[action][0]
# Get the list of selected PKs. If nothing's selected, we can't # Get the list of selected PKs. If nothing's selected, we can't
# perform an action on it, so bail. Except we want to perform # perform an action on it, so bail. Except we want to perform
@ -1158,7 +1165,7 @@ class ModelAdmin(BaseModelAdmin):
context = { context = {
'title': _('Add %s') % force_text(opts.verbose_name), 'title': _('Add %s') % force_text(opts.verbose_name),
'adminform': adminForm, 'adminform': adminForm,
'is_popup': "_popup" in request.REQUEST, 'is_popup': IS_POPUP_VAR in request.REQUEST,
'media': media, 'media': media,
'inline_admin_formsets': inline_admin_formsets, 'inline_admin_formsets': inline_admin_formsets,
'errors': helpers.AdminErrorList(form, formsets), 'errors': helpers.AdminErrorList(form, formsets),
@ -1251,7 +1258,7 @@ class ModelAdmin(BaseModelAdmin):
'adminform': adminForm, 'adminform': adminForm,
'object_id': object_id, 'object_id': object_id,
'original': obj, 'original': obj,
'is_popup': "_popup" in request.REQUEST, 'is_popup': IS_POPUP_VAR in request.REQUEST,
'media': media, 'media': media,
'inline_admin_formsets': inline_admin_formsets, 'inline_admin_formsets': inline_admin_formsets,
'errors': helpers.AdminErrorList(form, formsets), 'errors': helpers.AdminErrorList(form, formsets),
@ -1280,7 +1287,7 @@ class ModelAdmin(BaseModelAdmin):
actions = self.get_actions(request) actions = self.get_actions(request)
if actions: if actions:
# Add the action checkboxes if there are any actions available. # Add the action checkboxes if there are any actions available.
list_display = ['action_checkbox'] + list(list_display) list_display = ['action_checkbox'] + list(list_display)
ChangeList = self.get_changelist(request) ChangeList = self.get_changelist(request)
try: try:
@ -1429,7 +1436,10 @@ class ModelAdmin(BaseModelAdmin):
raise PermissionDenied raise PermissionDenied
if obj is None: if obj is None:
raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_text(opts.verbose_name), 'key': escape(object_id)}) raise Http404(
_('%(name)s object with primary key %(key)r does not exist.') %
{'name': force_text(opts.verbose_name), 'key': escape(object_id)}
)
using = router.db_for_write(self.model) using = router.db_for_write(self.model)
@ -1438,7 +1448,7 @@ class ModelAdmin(BaseModelAdmin):
(deleted_objects, perms_needed, protected) = get_deleted_objects( (deleted_objects, perms_needed, protected) = get_deleted_objects(
[obj], opts, request.user, self.admin_site, using) [obj], opts, request.user, self.admin_site, using)
if request.POST: # The user has already confirmed the deletion. if request.POST: # The user has already confirmed the deletion.
if perms_needed: if perms_needed:
raise PermissionDenied raise PermissionDenied
obj_display = force_text(obj) obj_display = force_text(obj)
@ -1456,7 +1466,9 @@ class ModelAdmin(BaseModelAdmin):
(opts.app_label, opts.model_name), (opts.app_label, opts.model_name),
current_app=self.admin_site.name) current_app=self.admin_site.name)
preserved_filters = self.get_preserved_filters(request) preserved_filters = self.get_preserved_filters(request)
post_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, post_url) post_url = add_preserved_filters(
{'preserved_filters': preserved_filters, 'opts': opts}, post_url
)
else: else:
post_url = reverse('admin:index', post_url = reverse('admin:index',
current_app=self.admin_site.name) current_app=self.admin_site.name)
@ -1522,6 +1534,7 @@ class ModelAdmin(BaseModelAdmin):
"admin/object_history.html" "admin/object_history.html"
], context, current_app=self.admin_site.name) ], context, current_app=self.admin_site.name)
class InlineModelAdmin(BaseModelAdmin): class InlineModelAdmin(BaseModelAdmin):
""" """
Options for inline editing of ``model`` instances. Options for inline editing of ``model`` instances.
@ -1665,8 +1678,7 @@ class InlineModelAdmin(BaseModelAdmin):
# to have the change permission for the related model in order to # to have the change permission for the related model in order to
# be able to do anything with the intermediate model. # be able to do anything with the intermediate model.
return self.has_change_permission(request) return self.has_change_permission(request)
return request.user.has_perm( return super(InlineModelAdmin, self).has_add_permission(request)
self.opts.app_label + '.' + self.opts.get_add_permission())
def has_change_permission(self, request, obj=None): def has_change_permission(self, request, obj=None):
opts = self.opts opts = self.opts
@ -1677,8 +1689,8 @@ class InlineModelAdmin(BaseModelAdmin):
if field.rel and field.rel.to != self.parent_model: if field.rel and field.rel.to != self.parent_model:
opts = field.rel.to._meta opts = field.rel.to._meta
break break
return request.user.has_perm( codename = get_permission_codename('change', opts)
opts.app_label + '.' + opts.get_change_permission()) return request.user.has_perm("%s.%s" % (opts.app_label, codename))
def has_delete_permission(self, request, obj=None): def has_delete_permission(self, request, obj=None):
if self.opts.auto_created: if self.opts.auto_created:
@ -1687,8 +1699,7 @@ class InlineModelAdmin(BaseModelAdmin):
# to have the change permission for the related model in order to # to have the change permission for the related model in order to
# be able to do anything with the intermediate model. # be able to do anything with the intermediate model.
return self.has_change_permission(request, obj) return self.has_change_permission(request, obj)
return request.user.has_perm( return super(InlineModelAdmin, self).has_delete_permission(request, obj)
self.opts.app_label + '.' + self.opts.get_delete_permission())
class StackedInline(InlineModelAdmin): class StackedInline(InlineModelAdmin):

View File

@ -32,9 +32,9 @@ function showRelatedObjectLookupPopup(triggeringLink) {
name = id_to_windowname(name); name = id_to_windowname(name);
var href; var href;
if (triggeringLink.href.search(/\?/) >= 0) { if (triggeringLink.href.search(/\?/) >= 0) {
href = triggeringLink.href + '&pop=1'; href = triggeringLink.href + '&_popup=1';
} else { } else {
href = triggeringLink.href + '?pop=1'; href = triggeringLink.href + '?_popup=1';
} }
var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes'); var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes');
win.focus(); win.focus();

View File

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<head><title></title></head>
<body>
<script type="text/javascript">
opener.dismissAddAnotherPopup(window, "{{ pk_value }}", "{{ obj }}");
</script>
</body>
</html>

View File

@ -6,7 +6,7 @@
<input type="text" size="40" name="{{ search_var }}" value="{{ cl.query }}" id="searchbar" /> <input type="text" size="40" name="{{ search_var }}" value="{{ cl.query }}" id="searchbar" />
<input type="submit" value="{% trans 'Search' %}" /> <input type="submit" value="{% trans 'Search' %}" />
{% if show_result_count %} {% if show_result_count %}
<span class="small quiet">{% blocktrans count counter=cl.result_count %}{{ counter }} result{% plural %}{{ counter }} results{% endblocktrans %} (<a href="?{% if cl.is_popup %}pop=1{% endif %}">{% blocktrans with full_result_count=cl.full_result_count %}{{ full_result_count }} total{% endblocktrans %}</a>)</span> <span class="small quiet">{% blocktrans count counter=cl.result_count %}{{ counter }} result{% plural %}{{ counter }} results{% endblocktrans %} (<a href="?{% if cl.is_popup %}_popup=1{% endif %}">{% blocktrans with full_result_count=cl.full_result_count %}{{ full_result_count }} total{% endblocktrans %}</a>)</span>
{% endif %} {% endif %}
{% for pair in cl.params.items %} {% for pair in cl.params.items %}
{% ifnotequal pair.0 search_var %}<input type="hidden" name="{{ pair.0 }}" value="{{ pair.1 }}"/>{% endifnotequal %} {% ifnotequal pair.0 search_var %}<input type="hidden" name="{{ pair.0 }}" value="{{ pair.1 }}"/>{% endifnotequal %}

View File

@ -3,7 +3,7 @@
{% trans "Please go to the following page and choose a new password:" %} {% trans "Please go to the following page and choose a new password:" %}
{% block reset_link %} {% block reset_link %}
{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb36=uid token=token %} {{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
{% endblock %} {% endblock %}
{% trans "Your username, in case you've forgotten:" %} {{ user.get_username }} {% trans "Your username, in case you've forgotten:" %} {{ user.get_username }}

View File

@ -11,7 +11,7 @@ from django.contrib.admin.templatetags.admin_static import static
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.db import models from django.db import models
from django.utils import formats from django.utils import formats
from django.utils.html import format_html from django.utils.html import escapejs, format_html
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.text import capfirst from django.utils.text import capfirst
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
@ -226,12 +226,12 @@ def items_for_result(cl, result, form):
else: else:
attr = pk attr = pk
value = result.serializable_value(attr) value = result.serializable_value(attr)
result_id = repr(force_text(value))[1:] result_id = escapejs(value)
yield format_html('<{0}{1}><a href="{2}"{3}>{4}</a></{5}>', yield format_html('<{0}{1}><a href="{2}"{3}>{4}</a></{5}>',
table_tag, table_tag,
row_class, row_class,
url, url,
format_html(' onclick="opener.dismissRelatedLookupPopup(window, {0}); return false;"', result_id) format_html(' onclick="opener.dismissRelatedLookupPopup(window, &#39;{0}&#39;); return false;"', result_id)
if cl.is_popup else '', if cl.is_popup else '',
result_repr, result_repr,
table_tag) table_tag)

View File

@ -1,5 +1,3 @@
from django.utils.http import urlencode
try: try:
from urllib.parse import parse_qsl, urlparse, urlunparse from urllib.parse import parse_qsl, urlparse, urlunparse
except ImportError: except ImportError:
@ -8,6 +6,7 @@ except ImportError:
from django import template from django import template
from django.contrib.admin.util import quote from django.contrib.admin.util import quote
from django.core.urlresolvers import resolve, Resolver404 from django.core.urlresolvers import resolve, Resolver404
from django.utils.http import urlencode
register = template.Library() register = template.Library()
@ -47,7 +46,8 @@ def add_preserved_filters(context, url, popup=False):
merged_qs.update(preserved_filters) merged_qs.update(preserved_filters)
if popup: if popup:
merged_qs['_popup'] = 1 from django.contrib.admin.options import IS_POPUP_VAR
merged_qs[IS_POPUP_VAR] = 1
merged_qs.update(parsed_qs) merged_qs.update(parsed_qs)

View File

@ -1,8 +1,7 @@
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.db import models from django.db import models
from django.db.models.fields import FieldDoesNotExist from django.db.models.fields import FieldDoesNotExist
from django.forms.models import (BaseModelForm, BaseModelFormSet, fields_for_model, from django.forms.models import BaseModelForm, BaseModelFormSet, _get_foreign_key
_get_foreign_key)
from django.contrib.admin.util import get_fields_from_path, NotRelationField from django.contrib.admin.util import get_fields_from_path, NotRelationField
""" """

View File

@ -15,7 +15,7 @@ from django.utils.http import urlencode
from django.contrib.admin import FieldListFilter from django.contrib.admin import FieldListFilter
from django.contrib.admin.exceptions import DisallowedModelAdminLookup from django.contrib.admin.exceptions import DisallowedModelAdminLookup
from django.contrib.admin.options import IncorrectLookupParameters from django.contrib.admin.options import IncorrectLookupParameters, IS_POPUP_VAR
from django.contrib.admin.util import (quote, get_fields_from_path, from django.contrib.admin.util import (quote, get_fields_from_path,
lookup_needs_distinct, prepare_lookup_value) lookup_needs_distinct, prepare_lookup_value)
@ -26,7 +26,6 @@ ORDER_TYPE_VAR = 'ot'
PAGE_VAR = 'p' PAGE_VAR = 'p'
SEARCH_VAR = 'q' SEARCH_VAR = 'q'
TO_FIELD_VAR = 't' TO_FIELD_VAR = 't'
IS_POPUP_VAR = 'pop'
ERROR_FLAG = 'e' ERROR_FLAG = 'e'
IGNORED_PARAMS = ( IGNORED_PARAMS = (
@ -36,6 +35,29 @@ IGNORED_PARAMS = (
EMPTY_CHANGELIST_VALUE = ugettext_lazy('(None)') EMPTY_CHANGELIST_VALUE = ugettext_lazy('(None)')
def _is_changelist_popup(request):
"""
Returns True if the popup GET parameter is set.
This function is introduced to facilitate deprecating the legacy
value for IS_POPUP_VAR and should be removed at the end of the
deprecation cycle.
"""
if IS_POPUP_VAR in request.GET:
return True
IS_LEGACY_POPUP_VAR = 'pop'
if IS_LEGACY_POPUP_VAR in request.GET:
warnings.warn(
"The `%s` GET parameter has been renamed to `%s`." %
(IS_LEGACY_POPUP_VAR, IS_POPUP_VAR),
PendingDeprecationWarning, 2)
return True
return False
class RenameChangeListMethods(RenameMethodsBase): class RenameChangeListMethods(RenameMethodsBase):
renamed_methods = ( renamed_methods = (
('get_query_set', 'get_queryset', PendingDeprecationWarning), ('get_query_set', 'get_queryset', PendingDeprecationWarning),
@ -67,7 +89,7 @@ class ChangeList(six.with_metaclass(RenameChangeListMethods)):
except ValueError: except ValueError:
self.page_num = 0 self.page_num = 0
self.show_all = ALL_VAR in request.GET self.show_all = ALL_VAR in request.GET
self.is_popup = IS_POPUP_VAR in request.GET self.is_popup = _is_changelist_popup(request)
self.to_field = request.GET.get(TO_FIELD_VAR) self.to_field = request.GET.get(TO_FIELD_VAR)
self.params = dict(request.GET.items()) self.params = dict(request.GET.items())
if PAGE_VAR in self.params: if PAGE_VAR in self.params:

View File

@ -17,7 +17,6 @@ from django.utils.importlib import import_module
from django.utils._os import upath from django.utils._os import upath
from django.utils import six from django.utils import six
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.utils.safestring import mark_safe
# Exclude methods starting with these strings from documentation # Exclude methods starting with these strings from documentation
MODEL_METHODS_EXCLUDE = ('_', 'add_', 'delete', 'save', 'set_') MODEL_METHODS_EXCLUDE = ('_', 'add_', 'delete', 'save', 'set_')

View File

@ -108,7 +108,9 @@ def logout(request):
def get_user_model(): def get_user_model():
"Return the User model that is active in this project" """
Returns the User model that is active in this project.
"""
from django.db.models import get_model from django.db.models import get_model
try: try:
@ -122,6 +124,10 @@ def get_user_model():
def get_user(request): def get_user(request):
"""
Returns the user model instance associated with the given request session.
If no user is retrieved an instance of `AnonymousUser` is returned.
"""
from .models import AnonymousUser from .models import AnonymousUser
try: try:
user_id = request.session[SESSION_KEY] user_id = request.session[SESSION_KEY]
@ -132,3 +138,10 @@ def get_user(request):
except (KeyError, AssertionError): except (KeyError, AssertionError):
user = AnonymousUser() user = AnonymousUser()
return user return user
def get_permission_codename(action, opts):
"""
Returns the codename of the permission for the specified action.
"""
return '%s_%s' % (action, opts.model_name)

View File

@ -1,6 +1,7 @@
from django.db import transaction from django.db import transaction
from django.conf import settings from django.conf import settings
from django.contrib import admin from django.contrib import admin
from django.contrib.admin.options import IS_POPUP_VAR
from django.contrib.auth.forms import (UserCreationForm, UserChangeForm, from django.contrib.auth.forms import (UserCreationForm, UserChangeForm,
AdminPasswordChangeForm) AdminPasswordChangeForm)
from django.contrib.auth.models import User, Group from django.contrib.auth.models import User, Group
@ -143,7 +144,7 @@ class UserAdmin(admin.ModelAdmin):
'adminForm': adminForm, 'adminForm': adminForm,
'form_url': form_url, 'form_url': form_url,
'form': form, 'form': form,
'is_popup': '_popup' in request.REQUEST, 'is_popup': IS_POPUP_VAR in request.REQUEST,
'add': True, 'add': True,
'change': False, 'change': False,
'has_delete_permission': False, 'has_delete_permission': False,
@ -170,7 +171,7 @@ class UserAdmin(admin.ModelAdmin):
# button except in two scenarios: # button except in two scenarios:
# * The user has pressed the 'Save and add another' button # * The user has pressed the 'Save and add another' button
# * We are adding a user in a popup # * We are adding a user in a popup
if '_addanother' not in request.POST and '_popup' not in request.POST: if '_addanother' not in request.POST and IS_POPUP_VAR not in request.POST:
request.POST['_continue'] = 1 request.POST['_continue'] = 1
return super(UserAdmin, self).response_add(request, obj, return super(UserAdmin, self).response_add(request, obj,
post_url_continue) post_url_continue)

View File

@ -6,8 +6,9 @@ from django import forms
from django.forms.util import flatatt from django.forms.util import flatatt
from django.template import loader from django.template import loader
from django.utils.datastructures import SortedDict from django.utils.datastructures import SortedDict
from django.utils.encoding import force_bytes
from django.utils.html import format_html, format_html_join from django.utils.html import format_html, format_html_join
from django.utils.http import int_to_base36 from django.utils.http import urlsafe_base64_encode
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.text import capfirst from django.utils.text import capfirst
from django.utils.translation import ugettext, ugettext_lazy as _ from django.utils.translation import ugettext, ugettext_lazy as _
@ -243,7 +244,7 @@ class PasswordResetForm(forms.Form):
'email': user.email, 'email': user.email,
'domain': domain, 'domain': domain,
'site_name': site_name, 'site_name': site_name,
'uid': int_to_base36(user.pk), 'uid': urlsafe_base64_encode(force_bytes(user.pk)),
'user': user, 'user': user,
'token': token_generator.make_token(user), 'token': token_generator.make_token(user),
'protocol': 'https' if use_https else 'http', 'protocol': 'https' if use_https else 'http',

View File

@ -4,10 +4,10 @@ Creates permissions for all installed apps that need permissions.
from __future__ import unicode_literals from __future__ import unicode_literals
import getpass import getpass
import locale
import unicodedata import unicodedata
from django.contrib.auth import models as auth_app, get_user_model from django.contrib.auth import (models as auth_app, get_permission_codename,
get_user_model)
from django.core import exceptions from django.core import exceptions
from django.core.management.base import CommandError from django.core.management.base import CommandError
from django.db import DEFAULT_DB_ALIAS, router from django.db import DEFAULT_DB_ALIAS, router
@ -17,10 +17,6 @@ from django.utils import six
from django.utils.six.moves import input from django.utils.six.moves import input
def _get_permission_codename(action, opts):
return '%s_%s' % (action, opts.model_name)
def _get_all_permissions(opts, ctype): def _get_all_permissions(opts, ctype):
""" """
Returns (codename, name) for all permissions in the given opts. Returns (codename, name) for all permissions in the given opts.
@ -30,16 +26,18 @@ def _get_all_permissions(opts, ctype):
_check_permission_clashing(custom, builtin, ctype) _check_permission_clashing(custom, builtin, ctype)
return builtin + custom return builtin + custom
def _get_builtin_permissions(opts): def _get_builtin_permissions(opts):
""" """
Returns (codename, name) for all autogenerated permissions. Returns (codename, name) for all autogenerated permissions.
""" """
perms = [] perms = []
for action in ('add', 'change', 'delete'): for action in ('add', 'change', 'delete'):
perms.append((_get_permission_codename(action, opts), perms.append((get_permission_codename(action, opts),
'Can %s %s' % (action, opts.verbose_name_raw))) 'Can %s %s' % (action, opts.verbose_name_raw)))
return perms return perms
def _check_permission_clashing(custom, builtin, ctype): def _check_permission_clashing(custom, builtin, ctype):
""" """
Check that permissions for a model do not clash. Raises CommandError if Check that permissions for a model do not clash. Raises CommandError if
@ -59,6 +57,7 @@ def _check_permission_clashing(custom, builtin, ctype):
(codename, ctype.app_label, ctype.model_class().__name__)) (codename, ctype.app_label, ctype.model_class().__name__))
pool.add(codename) pool.add(codename)
def create_permissions(app, created_models, verbosity, db=DEFAULT_DB_ALIAS, **kwargs): def create_permissions(app, created_models, verbosity, db=DEFAULT_DB_ALIAS, **kwargs):
try: try:
get_model('auth', 'Permission') get_model('auth', 'Permission')

View File

@ -170,7 +170,8 @@ class BaseUserManager(models.Manager):
class UserManager(BaseUserManager): class UserManager(BaseUserManager):
def create_user(self, username, email=None, password=None, **extra_fields): def _create_user(self, username, email, password,
is_staff, is_superuser, **extra_fields):
""" """
Creates and saves a User with the given username, email and password. Creates and saves a User with the given username, email and password.
""" """
@ -179,20 +180,20 @@ class UserManager(BaseUserManager):
raise ValueError('The given username must be set') raise ValueError('The given username must be set')
email = self.normalize_email(email) email = self.normalize_email(email)
user = self.model(username=username, email=email, user = self.model(username=username, email=email,
is_staff=False, is_active=True, is_superuser=False, is_staff=is_staff, is_active=True,
last_login=now, date_joined=now, **extra_fields) is_superuser=is_superuser, last_login=now,
date_joined=now, **extra_fields)
user.set_password(password) user.set_password(password)
user.save(using=self._db) user.save(using=self._db)
return user return user
def create_user(self, username, email=None, password=None, **extra_fields):
return self._create_user(username, email, password, False, False,
**extra_fields)
def create_superuser(self, username, email, password, **extra_fields): def create_superuser(self, username, email, password, **extra_fields):
u = self.create_user(username, email, password, **extra_fields) return self._create_user(username, email, password, True, True,
u.is_staff = True **extra_fields)
u.is_active = True
u.is_superuser = True
u.save(using=self._db)
return u
@python_2_unicode_compatible @python_2_unicode_compatible
@ -294,10 +295,12 @@ class PermissionsMixin(models.Model):
groups = models.ManyToManyField(Group, verbose_name=_('groups'), groups = models.ManyToManyField(Group, verbose_name=_('groups'),
blank=True, help_text=_('The groups this user belongs to. A user will ' blank=True, help_text=_('The groups this user belongs to. A user will '
'get all permissions granted to each of ' 'get all permissions granted to each of '
'his/her group.')) 'his/her group.'),
related_name="user_set", related_query_name="user")
user_permissions = models.ManyToManyField(Permission, user_permissions = models.ManyToManyField(Permission,
verbose_name=_('user permissions'), blank=True, verbose_name=_('user permissions'), blank=True,
help_text='Specific permissions for this user.') help_text='Specific permissions for this user.',
related_name="user_set", related_query_name="user")
class Meta: class Meta:
abstract = True abstract = True

View File

@ -1 +1 @@
{{ protocol }}://{{ domain }}/reset/{{ uid }}-{{ token }}/ {{ protocol }}://{{ domain }}/reset/{{ uid }}/{{ token }}/

View File

@ -4,7 +4,9 @@ from django.contrib.auth.models import (
AbstractBaseUser, AbstractBaseUser,
AbstractUser, AbstractUser,
UserManager, UserManager,
PermissionsMixin PermissionsMixin,
Group,
Permission,
) )
@ -81,6 +83,20 @@ class CustomUser(AbstractBaseUser):
return self.is_admin return self.is_admin
# At this point, temporarily remove the groups and user_permissions M2M
# fields from the AbstractUser class, so they don't clash with the related_name
# that sets.
old_au_local_m2m = AbstractUser._meta.local_many_to_many
old_pm_local_m2m = PermissionsMixin._meta.local_many_to_many
groups = models.ManyToManyField(Group, blank=True)
groups.contribute_to_class(PermissionsMixin, "groups")
user_permissions = models.ManyToManyField(Permission, blank=True)
user_permissions.contribute_to_class(PermissionsMixin, "user_permissions")
PermissionsMixin._meta.local_many_to_many = [groups, user_permissions]
AbstractUser._meta.local_many_to_many = [groups, user_permissions]
# The extension user is a simple extension of the built-in user class, # The extension user is a simple extension of the built-in user class,
# adding a required date_of_birth field. This allows us to check for # adding a required date_of_birth field. This allows us to check for
# any hard references to the name "User" in forms/handlers etc. # any hard references to the name "User" in forms/handlers etc.
@ -178,3 +194,7 @@ class CustomUserBadRequiredFields(AbstractBaseUser):
class Meta: class Meta:
app_label = 'auth' app_label = 'auth'
# Undo swap hack
AbstractUser._meta.local_many_to_many = old_au_local_m2m
PermissionsMixin._meta.local_many_to_many = old_pm_local_m2m

View File

@ -5,6 +5,7 @@ from django.contrib.auth import get_user_model
from django.contrib.auth.models import (Group, User, SiteProfileNotAvailable, from django.contrib.auth.models import (Group, User, SiteProfileNotAvailable,
UserManager) UserManager)
from django.contrib.auth.tests.utils import skipIfCustomUser from django.contrib.auth.tests.utils import skipIfCustomUser
from django.db.models.signals import post_save
from django.test import TestCase from django.test import TestCase
from django.test.utils import override_settings from django.test.utils import override_settings
from django.utils import six from django.utils import six
@ -140,3 +141,27 @@ class IsActiveTestCase(TestCase):
user_fetched = UserModel._default_manager.get(pk=user.pk) user_fetched = UserModel._default_manager.get(pk=user.pk)
# the attribute is always true for newly retrieved instance # the attribute is always true for newly retrieved instance
self.assertEqual(user_fetched.is_active, True) self.assertEqual(user_fetched.is_active, True)
@skipIfCustomUser
class TestCreateSuperUserSignals(TestCase):
"""
Simple test case for ticket #20541
"""
def post_save_listener(self, *args, **kwargs):
self.signals_count += 1
def setUp(self):
self.signals_count = 0
post_save.connect(self.post_save_listener, sender=User)
def tearDown(self):
post_save.disconnect(self.post_save_listener, sender=User)
def test_create_user(self):
User.objects.create_user("JohnDoe")
self.assertEqual(self.signals_count, 1)
def test_create_superuser(self):
User.objects.create_superuser("JohnDoe", "mail@example.com", "1")
self.assertEqual(self.signals_count, 1)

View File

@ -3,7 +3,7 @@ from datetime import datetime
from django.conf import settings from django.conf import settings
from django.contrib.auth import authenticate from django.contrib.auth import authenticate
from django.contrib.auth.backends import RemoteUserBackend from django.contrib.auth.backends import RemoteUserBackend
from django.contrib.auth.models import User, AnonymousUser from django.contrib.auth.models import User
from django.contrib.auth.tests.utils import skipIfCustomUser from django.contrib.auth.tests.utils import skipIfCustomUser
from django.test import TestCase from django.test import TestCase
from django.utils import timezone from django.utils import timezone

View File

@ -13,8 +13,7 @@ from django.core import mail
from django.core.urlresolvers import reverse, NoReverseMatch from django.core.urlresolvers import reverse, NoReverseMatch
from django.http import QueryDict, HttpRequest from django.http import QueryDict, HttpRequest
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils.html import escape from django.utils.http import int_to_base36, urlsafe_base64_decode, urlquote
from django.utils.http import urlquote
from django.utils._os import upath from django.utils._os import upath
from django.test import TestCase from django.test import TestCase
from django.test.utils import override_settings, patch_logger from django.test.utils import override_settings, patch_logger
@ -23,7 +22,7 @@ from django.contrib.sessions.middleware import SessionMiddleware
from django.contrib.auth import SESSION_KEY, REDIRECT_FIELD_NAME from django.contrib.auth import SESSION_KEY, REDIRECT_FIELD_NAME
from django.contrib.auth.forms import (AuthenticationForm, PasswordChangeForm, from django.contrib.auth.forms import (AuthenticationForm, PasswordChangeForm,
SetPasswordForm, PasswordResetForm) SetPasswordForm)
from django.contrib.auth.tests.utils import skipIfCustomUser from django.contrib.auth.tests.utils import skipIfCustomUser
from django.contrib.auth.views import login as login_view from django.contrib.auth.views import login as login_view
@ -92,7 +91,7 @@ class AuthViewNamedURLTests(AuthViewsTestCase):
('password_reset', [], {}), ('password_reset', [], {}),
('password_reset_done', [], {}), ('password_reset_done', [], {}),
('password_reset_confirm', [], { ('password_reset_confirm', [], {
'uidb36': 'aaaaaaa', 'uidb64': 'aaaaaaa',
'token': '1111-aaaaa', 'token': '1111-aaaaa',
}), }),
('password_reset_complete', [], {}), ('password_reset_complete', [], {}),
@ -194,6 +193,16 @@ class PasswordResetTest(AuthViewsTestCase):
# redirect to a 'complete' page: # redirect to a 'complete' page:
self.assertContains(response, "Please enter your new password") self.assertContains(response, "Please enter your new password")
def test_confirm_valid_base36(self):
# Remove in Django 1.7
url, path = self._test_confirm_start()
path_parts = path.strip("/").split("/")
# construct an old style (base36) URL by converting the base64 ID
path_parts[1] = int_to_base36(int(urlsafe_base64_decode(path_parts[1])))
response = self.client.get("/%s/%s-%s/" % tuple(path_parts))
# redirect to a 'complete' page:
self.assertContains(response, "Please enter your new password")
def test_confirm_invalid(self): def test_confirm_invalid(self):
url, path = self._test_confirm_start() url, path = self._test_confirm_start()
# Let's munge the token in the path, but keep the same length, # Let's munge the token in the path, but keep the same length,
@ -205,11 +214,21 @@ class PasswordResetTest(AuthViewsTestCase):
def test_confirm_invalid_user(self): def test_confirm_invalid_user(self):
# Ensure that we get a 200 response for a non-existant user, not a 404 # Ensure that we get a 200 response for a non-existant user, not a 404
response = self.client.get('/reset/123456/1-1/')
self.assertContains(response, "The password reset link was invalid")
def test_confirm_invalid_user_base36(self):
# Remove in Django 1.7
response = self.client.get('/reset/123456-1-1/') response = self.client.get('/reset/123456-1-1/')
self.assertContains(response, "The password reset link was invalid") self.assertContains(response, "The password reset link was invalid")
def test_confirm_overflow_user(self): def test_confirm_overflow_user(self):
# Ensure that we get a 200 response for a base36 user id that overflows int # Ensure that we get a 200 response for a base36 user id that overflows int
response = self.client.get('/reset/zzzzzzzzzzzzz/1-1/')
self.assertContains(response, "The password reset link was invalid")
def test_confirm_overflow_user_base36(self):
# Remove in Django 1.7
response = self.client.get('/reset/zzzzzzzzzzzzz-1-1/') response = self.client.get('/reset/zzzzzzzzzzzzz-1-1/')
self.assertContains(response, "The password reset link was invalid") self.assertContains(response, "The password reset link was invalid")

View File

@ -67,10 +67,10 @@ urlpatterns = urlpatterns + patterns('',
(r'^password_reset_from_email/$', 'django.contrib.auth.views.password_reset', dict(from_email='staffmember@example.com')), (r'^password_reset_from_email/$', 'django.contrib.auth.views.password_reset', dict(from_email='staffmember@example.com')),
(r'^password_reset/custom_redirect/$', 'django.contrib.auth.views.password_reset', dict(post_reset_redirect='/custom/')), (r'^password_reset/custom_redirect/$', 'django.contrib.auth.views.password_reset', dict(post_reset_redirect='/custom/')),
(r'^password_reset/custom_redirect/named/$', 'django.contrib.auth.views.password_reset', dict(post_reset_redirect='password_reset')), (r'^password_reset/custom_redirect/named/$', 'django.contrib.auth.views.password_reset', dict(post_reset_redirect='password_reset')),
(r'^reset/custom/(?P<uidb36>[0-9A-Za-z]{1,13})-(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', (r'^reset/custom/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
'django.contrib.auth.views.password_reset_confirm', 'django.contrib.auth.views.password_reset_confirm',
dict(post_reset_redirect='/custom/')), dict(post_reset_redirect='/custom/')),
(r'^reset/custom/named/(?P<uidb36>[0-9A-Za-z]{1,13})-(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', (r'^reset/custom/named/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
'django.contrib.auth.views.password_reset_confirm', 'django.contrib.auth.views.password_reset_confirm',
dict(post_reset_redirect='password_reset')), dict(post_reset_redirect='password_reset')),
(r'^password_change/custom/$', 'django.contrib.auth.views.password_change', dict(post_change_redirect='/custom/')), (r'^password_change/custom/$', 'django.contrib.auth.views.password_change', dict(post_change_redirect='/custom/')),
@ -88,4 +88,3 @@ urlpatterns = urlpatterns + patterns('',
(r'^custom_request_auth_login/$', custom_request_auth_login), (r'^custom_request_auth_login/$', custom_request_auth_login),
url(r'^userpage/(.+)/$', userpage, name="userpage"), url(r'^userpage/(.+)/$', userpage, name="userpage"),
) )

View File

@ -12,7 +12,10 @@ urlpatterns = patterns('',
url(r'^password_change/done/$', 'django.contrib.auth.views.password_change_done', name='password_change_done'), url(r'^password_change/done/$', 'django.contrib.auth.views.password_change_done', name='password_change_done'),
url(r'^password_reset/$', 'django.contrib.auth.views.password_reset', name='password_reset'), url(r'^password_reset/$', 'django.contrib.auth.views.password_reset', name='password_reset'),
url(r'^password_reset/done/$', 'django.contrib.auth.views.password_reset_done', name='password_reset_done'), url(r'^password_reset/done/$', 'django.contrib.auth.views.password_reset_done', name='password_reset_done'),
# Support old style base36 password reset links; remove in Django 1.7
url(r'^reset/(?P<uidb36>[0-9A-Za-z]{1,13})-(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', url(r'^reset/(?P<uidb36>[0-9A-Za-z]{1,13})-(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
'django.contrib.auth.views.password_reset_confirm_uidb36'),
url(r'^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
'django.contrib.auth.views.password_reset_confirm', 'django.contrib.auth.views.password_reset_confirm',
name='password_reset_confirm'), name='password_reset_confirm'),
url(r'^reset/done/$', 'django.contrib.auth.views.password_reset_complete', name='password_reset_complete'), url(r'^reset/done/$', 'django.contrib.auth.views.password_reset_complete', name='password_reset_complete'),

View File

@ -7,9 +7,10 @@ from django.conf import settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect, QueryDict from django.http import HttpResponseRedirect, QueryDict
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.utils.http import base36_to_int, is_safe_url from django.utils.http import base36_to_int, is_safe_url, urlsafe_base64_decode, urlsafe_base64_encode
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.shortcuts import resolve_url from django.shortcuts import resolve_url
from django.utils.encoding import force_bytes, force_text
from django.views.decorators.debug import sensitive_post_parameters from django.views.decorators.debug import sensitive_post_parameters
from django.views.decorators.cache import never_cache from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect from django.views.decorators.csrf import csrf_protect
@ -184,7 +185,7 @@ def password_reset_done(request,
# Doesn't need csrf_protect since no-one can guess the URL # Doesn't need csrf_protect since no-one can guess the URL
@sensitive_post_parameters() @sensitive_post_parameters()
@never_cache @never_cache
def password_reset_confirm(request, uidb36=None, token=None, def password_reset_confirm(request, uidb64=None, token=None,
template_name='registration/password_reset_confirm.html', template_name='registration/password_reset_confirm.html',
token_generator=default_token_generator, token_generator=default_token_generator,
set_password_form=SetPasswordForm, set_password_form=SetPasswordForm,
@ -195,15 +196,15 @@ def password_reset_confirm(request, uidb36=None, token=None,
form for entering a new password. form for entering a new password.
""" """
UserModel = get_user_model() UserModel = get_user_model()
assert uidb36 is not None and token is not None # checked by URLconf assert uidb64 is not None and token is not None # checked by URLconf
if post_reset_redirect is None: if post_reset_redirect is None:
post_reset_redirect = reverse('password_reset_complete') post_reset_redirect = reverse('password_reset_complete')
else: else:
post_reset_redirect = resolve_url(post_reset_redirect) post_reset_redirect = resolve_url(post_reset_redirect)
try: try:
uid_int = base36_to_int(uidb36) uid = urlsafe_base64_decode(uidb64)
user = UserModel._default_manager.get(pk=uid_int) user = UserModel._default_manager.get(pk=uid)
except (ValueError, OverflowError, UserModel.DoesNotExist): except (TypeError, ValueError, OverflowError, UserModel.DoesNotExist):
user = None user = None
if user is not None and token_generator.check_token(user, token): if user is not None and token_generator.check_token(user, token):
@ -227,6 +228,14 @@ def password_reset_confirm(request, uidb36=None, token=None,
return TemplateResponse(request, template_name, context, return TemplateResponse(request, template_name, context,
current_app=current_app) current_app=current_app)
def password_reset_confirm_uidb36(request, uidb36=None, **kwargs):
# Support old password reset URLs that used base36 encoded user IDs.
# Remove in Django 1.7
try:
uidb64 = force_text(urlsafe_base64_encode(force_bytes(base36_to_int(uidb36))))
except ValueError:
uidb64 = '1' # dummy invalid ID (incorrect padding for base64)
return password_reset_confirm(request, uidb64=uidb64, **kwargs)
def password_reset_complete(request, def password_reset_complete(request,
template_name='registration/password_reset_complete.html', template_name='registration/password_reset_complete.html',

View File

@ -3,7 +3,7 @@ from __future__ import unicode_literals
from django.contrib import admin from django.contrib import admin
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.comments.models import Comment from django.contrib.comments.models import Comment
from django.utils.translation import ugettext_lazy as _, ungettext, ungettext_lazy from django.utils.translation import ugettext_lazy as _, ungettext_lazy
from django.contrib.comments import get_model from django.contrib.comments import get_model
from django.contrib.comments.views.moderation import perform_flag, perform_approve, perform_delete from django.contrib.comments.views.moderation import perform_flag, perform_approve, perform_delete

View File

@ -1,5 +1,4 @@
import os import os
from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.auth.tests.utils import skipIfCustomUser from django.contrib.auth.tests.utils import skipIfCustomUser
from django.test import TestCase, Client from django.test import TestCase, Client

View File

@ -1,5 +1,4 @@
import os import os
from django.conf import settings
from django.contrib.auth.models import AnonymousUser, User from django.contrib.auth.models import AnonymousUser, User
from django.contrib.auth.tests.utils import skipIfCustomUser from django.contrib.auth.tests.utils import skipIfCustomUser
from django.template import Template, Context, TemplateSyntaxError from django.template import Template, Context, TemplateSyntaxError

View File

@ -3,15 +3,11 @@ from __future__ import unicode_literals
import datetime import datetime
import os import os
import pickle
import re
import warnings import warnings
from django import http from django import http
from django.conf import settings
from django.contrib.formtools import preview, utils from django.contrib.formtools import preview, utils
from django.test import TestCase from django.test import TestCase
from django.test.html import parse_html
from django.test.utils import override_settings from django.test.utils import override_settings
from django.utils._os import upath from django.utils._os import upath
from django.utils import unittest from django.utils import unittest

View File

@ -1,5 +1,3 @@
import json
from django.test import TestCase from django.test import TestCase
from django.core import signing from django.core import signing
from django.core.exceptions import SuspiciousOperation from django.core.exceptions import SuspiciousOperation

View File

@ -2,7 +2,6 @@ import logging
from django.forms.widgets import Textarea from django.forms.widgets import Textarea
from django.template import loader, Context from django.template import loader, Context
from django.templatetags.static import static
from django.utils import six from django.utils import six
from django.utils import translation from django.utils import translation

View File

@ -1,5 +1,4 @@
from django.conf import settings from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.db.backends.postgresql_psycopg2.creation import DatabaseCreation from django.db.backends.postgresql_psycopg2.creation import DatabaseCreation
from django.utils.functional import cached_property from django.utils.functional import cached_property

View File

@ -1,5 +1,4 @@
from django.db.models import Aggregate from django.db.models import Aggregate
from django.contrib.gis.db.models.sql import GeomField
class Collect(Aggregate): class Collect(Aggregate):
name = 'Collect' name = 'Collect'

View File

@ -4,7 +4,6 @@ import binascii
import unittest import unittest
from django.contrib.gis import memoryview from django.contrib.gis import memoryview
from django.utils import six
from django.utils.unittest import skipUnless from django.utils.unittest import skipUnless
from ..import HAS_GEOS from ..import HAS_GEOS

View File

@ -1,4 +1,3 @@
import os
from optparse import make_option from optparse import make_option
from django.contrib.gis import gdal from django.contrib.gis import gdal
from django.core.management.base import LabelCommand, CommandError from django.core.management.base import LabelCommand, CommandError

View File

@ -1,7 +1,5 @@
from __future__ import absolute_import from __future__ import absolute_import
from datetime import date
from django.contrib.gis.geos import HAS_GEOS from django.contrib.gis.geos import HAS_GEOS
from django.contrib.gis.tests.utils import HAS_SPATIAL_DB, mysql, oracle, no_mysql, no_oracle, no_spatialite from django.contrib.gis.tests.utils import HAS_SPATIAL_DB, mysql, oracle, no_mysql, no_oracle, no_spatialite
from django.test import TestCase from django.test import TestCase

View File

@ -1,4 +1,3 @@
from django.contrib.auth.models import User
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.core.cache import cache from django.core.cache import cache
from django.db import models from django.db import models

View File

@ -1,7 +1,6 @@
from datetime import datetime from datetime import datetime
from django.conf.urls import patterns, url from django.conf.urls import patterns, url
from django.contrib.sitemaps import Sitemap, GenericSitemap, FlatPageSitemap, views from django.contrib.sitemaps import Sitemap, GenericSitemap, FlatPageSitemap, views
from django.contrib.auth.models import User
from django.views.decorators.cache import cache_page from django.views.decorators.cache import cache_page
from django.contrib.sitemaps.tests.base import TestModel from django.contrib.sitemaps.tests.base import TestModel

View File

@ -1,7 +1,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import warnings import warnings
from django.core.compat_checks import django_1_6_0 from django.core.checks.compatibility import django_1_6_0
COMPAT_CHECKS = [ COMPAT_CHECKS = [

View File

@ -27,7 +27,7 @@ def check_test_runner():
def run_checks(): def run_checks():
""" """
Required by the ``checksetup`` management command, this returns a list of Required by the ``check`` management command, this returns a list of
messages from all the relevant check functions for this version of Django. messages from all the relevant check functions for this version of Django.
""" """
checks = [ checks = [

View File

@ -1,7 +1,6 @@
""" """
Global Django exception and warning classes. Global Django exception and warning classes.
""" """
import logging
from functools import reduce from functools import reduce
import operator import operator

View File

@ -51,6 +51,10 @@ def file_move_safe(old_file_name, new_file_name, chunk_size = 1024*64, allow_ove
return return
try: try:
# If the destination file exists and allow_overwrite is False then raise an IOError
if not allow_overwrite and os.access(new_file_name, os.F_OK):
raise IOError("Destination file %s exists and allow_overwrite is False" % new_file_name)
os.rename(old_file_name, new_file_name) os.rename(old_file_name, new_file_name)
return return
except OSError: except OSError:

View File

@ -103,6 +103,7 @@ class WSGIRequest(http.HttpRequest):
content_length = 0 content_length = 0
self._stream = LimitedStream(self.environ['wsgi.input'], content_length) self._stream = LimitedStream(self.environ['wsgi.input'], content_length)
self._read_started = False self._read_started = False
self.resolver_match = None
def _is_secure(self): def _is_secure(self):
return 'wsgi.url_scheme' in self.environ and self.environ['wsgi.url_scheme'] == 'https' return 'wsgi.url_scheme' in self.environ and self.environ['wsgi.url_scheme'] == 'https'

View File

@ -3,13 +3,11 @@ import os
import sys import sys
from optparse import OptionParser, NO_DEFAULT from optparse import OptionParser, NO_DEFAULT
import imp import imp
import warnings
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.core.management.base import BaseCommand, CommandError, handle_default_options from django.core.management.base import BaseCommand, CommandError, handle_default_options
from django.core.management.color import color_style from django.core.management.color import color_style
from django.utils.importlib import import_module from django.utils.importlib import import_module
from django.utils._os import upath
from django.utils import six from django.utils import six
# For backwards compatibility: get_version() used to be in this module. # For backwards compatibility: get_version() used to be in this module.

View File

@ -1,7 +1,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import warnings import warnings
from django.core.compat_checks.base import check_compatibility from django.core.checks.compatibility.base import check_compatibility
from django.core.management.base import NoArgsCommand from django.core.management.base import NoArgsCommand

View File

@ -402,11 +402,11 @@ class Command(NoArgsCommand):
if self.verbosity > 1: if self.verbosity > 1:
self.stdout.write("copying plural forms: %s\n" % m.group('value')) self.stdout.write("copying plural forms: %s\n" % m.group('value'))
lines = [] lines = []
seen = False found = False
for line in msgs.split('\n'): for line in msgs.split('\n'):
if not line and not seen: if not found and (not line or plural_forms_re.search(line)):
line = '%s\n' % m.group('value') line = '%s\n' % m.group('value')
seen = True found = True
lines.append(line) lines.append(line)
msgs = '\n'.join(lines) msgs = '\n'.join(lines)
break break

View File

@ -105,7 +105,7 @@ class TemplateCommand(BaseCommand):
base_name = '%s_name' % app_or_project base_name = '%s_name' % app_or_project
base_subdir = '%s_template' % app_or_project base_subdir = '%s_template' % app_or_project
base_directory = '%s_directory' % app_or_project base_directory = '%s_directory' % app_or_project
if django.VERSION[-1] == 0: if django.VERSION[-2] != 'final':
docs_version = 'dev' docs_version = 'dev'
else: else:
docs_version = '%d.%d' % django.VERSION[:2] docs_version = '%d.%d' % django.VERSION[:2]

View File

@ -3,7 +3,6 @@ Module for abstract serializer/unserializer base classes.
""" """
from django.db import models from django.db import models
from django.utils.encoding import smart_text
from django.utils import six from django.utils import six
class SerializerDoesNotExist(KeyError): class SerializerDoesNotExist(KeyError):

View File

@ -330,6 +330,15 @@ class BaseDatabaseWrapper(object):
self._set_autocommit(autocommit) self._set_autocommit(autocommit)
self.autocommit = autocommit self.autocommit = autocommit
def set_rollback(self, rollback):
"""
Set or unset the "needs rollback" flag -- for *advanced use* only.
"""
if not self.in_atomic_block:
raise TransactionManagementError(
"needs_rollback doesn't work outside of an 'atomic' block.")
self.needs_rollback = rollback
def validate_no_atomic_block(self): def validate_no_atomic_block(self):
""" """
Raise an error if an atomic block is active. Raise an error if an atomic block is active.
@ -623,6 +632,11 @@ class BaseDatabaseFeatures(object):
# Does it support CHECK constraints? # Does it support CHECK constraints?
supports_check_constraints = True supports_check_constraints = True
# Does the backend support 'pyformat' style ("... %(name)s ...", {'name': value})
# parameter passing? Note this can be provided by the backend even if not
# supported by the Python driver
supports_paramstyle_pyformat = True
def __init__(self, connection): def __init__(self, connection):
self.connection = connection self.connection = connection

View File

@ -762,20 +762,37 @@ class FormatStylePlaceholderCursor(object):
self.cursor.arraysize = 100 self.cursor.arraysize = 100
def _format_params(self, params): def _format_params(self, params):
return tuple([OracleParam(p, self, True) for p in params]) try:
return dict((k,OracleParam(v, self, True)) for k,v in params.items())
except AttributeError:
return tuple([OracleParam(p, self, True) for p in params])
def _guess_input_sizes(self, params_list): def _guess_input_sizes(self, params_list):
sizes = [None] * len(params_list[0]) # Try dict handling; if that fails, treat as sequence
for params in params_list: if hasattr(params_list[0], 'keys'):
for i, value in enumerate(params): sizes = {}
if value.input_size: for params in params_list:
sizes[i] = value.input_size for k, value in params.items():
self.setinputsizes(*sizes) if value.input_size:
sizes[k] = value.input_size
self.setinputsizes(**sizes)
else:
# It's not a list of dicts; it's a list of sequences
sizes = [None] * len(params_list[0])
for params in params_list:
for i, value in enumerate(params):
if value.input_size:
sizes[i] = value.input_size
self.setinputsizes(*sizes)
def _param_generator(self, params): def _param_generator(self, params):
return [p.force_bytes for p in params] # Try dict handling; if that fails, treat as sequence
if hasattr(params, 'items'):
return dict((k, v.force_bytes) for k,v in params.items())
else:
return [p.force_bytes for p in params]
def execute(self, query, params=None): def _fix_for_params(self, query, params):
# cx_Oracle wants no trailing ';' for SQL statements. For PL/SQL, it # cx_Oracle wants no trailing ';' for SQL statements. For PL/SQL, it
# it does want a trailing ';' but not a trailing '/'. However, these # it does want a trailing ';' but not a trailing '/'. However, these
# characters must be included in the original query in case the query # characters must be included in the original query in case the query
@ -785,10 +802,18 @@ class FormatStylePlaceholderCursor(object):
if params is None: if params is None:
params = [] params = []
query = convert_unicode(query, self.charset) query = convert_unicode(query, self.charset)
elif hasattr(params, 'keys'):
# Handle params as dict
args = dict((k, ":%s"%k) for k in params.keys())
query = convert_unicode(query % args, self.charset)
else: else:
params = self._format_params(params) # Handle params as sequence
args = [(':arg%d' % i) for i in range(len(params))] args = [(':arg%d' % i) for i in range(len(params))]
query = convert_unicode(query % tuple(args), self.charset) query = convert_unicode(query % tuple(args), self.charset)
return query, self._format_params(params)
def execute(self, query, params=None):
query, params = self._fix_for_params(query, params)
self._guess_input_sizes([params]) self._guess_input_sizes([params])
try: try:
return self.cursor.execute(query, self._param_generator(params)) return self.cursor.execute(query, self._param_generator(params))
@ -799,22 +824,15 @@ class FormatStylePlaceholderCursor(object):
raise raise
def executemany(self, query, params=None): def executemany(self, query, params=None):
# cx_Oracle doesn't support iterators, convert them to lists if not params:
if params is not None and not isinstance(params, (list, tuple)):
params = list(params)
try:
args = [(':arg%d' % i) for i in range(len(params[0]))]
except (IndexError, TypeError):
# No params given, nothing to do # No params given, nothing to do
return None return None
# cx_Oracle wants no trailing ';' for SQL statements. For PL/SQL, it # uniform treatment for sequences and iterables
# it does want a trailing ';' but not a trailing '/'. However, these params_iter = iter(params)
# characters must be included in the original query in case the query query, firstparams = self._fix_for_params(query, next(params_iter))
# is being passed to SQL*Plus. # we build a list of formatted params; as we're going to traverse it
if query.endswith(';') or query.endswith('/'): # more than once, we can't make it lazy by using a generator
query = query[:-1] formatted = [firstparams]+[self._format_params(p) for p in params_iter]
query = convert_unicode(query % tuple(args), self.charset)
formatted = [self._format_params(i) for i in params]
self._guess_input_sizes(formatted) self._guess_input_sizes(formatted)
try: try:
return self.cursor.executemany(query, return self.cursor.executemany(query,

View File

@ -6,7 +6,6 @@ Requires psycopg 2: http://initd.org/projects/psycopg2
import logging import logging
import sys import sys
from django.db import utils
from django.db.backends import * from django.db.backends import *
from django.db.backends.postgresql_psycopg2.operations import DatabaseOperations from django.db.backends.postgresql_psycopg2.operations import DatabaseOperations
from django.db.backends.postgresql_psycopg2.client import DatabaseClient from django.db.backends.postgresql_psycopg2.client import DatabaseClient
@ -17,7 +16,6 @@ from django.db.backends.postgresql_psycopg2.schema import DatabaseSchemaEditor
from django.utils.encoding import force_str from django.utils.encoding import force_str
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.safestring import SafeText, SafeBytes from django.utils.safestring import SafeText, SafeBytes
from django.utils import six
from django.utils.timezone import utc from django.utils.timezone import utc
try: try:

View File

@ -1,5 +1,3 @@
import psycopg2.extensions
from django.db.backends.creation import BaseDatabaseCreation from django.db.backends.creation import BaseDatabaseCreation
from django.db.backends.util import truncate_name from django.db.backends.util import truncate_name

View File

@ -69,7 +69,7 @@ class DatabaseOperations(BaseDatabaseOperations):
# Cast text lookups to text to allow things like filter(x__contains=4) # Cast text lookups to text to allow things like filter(x__contains=4)
if lookup_type in ('iexact', 'contains', 'icontains', 'startswith', if lookup_type in ('iexact', 'contains', 'icontains', 'startswith',
'istartswith', 'endswith', 'iendswith'): 'istartswith', 'endswith', 'iendswith', 'regex', 'iregex'):
lookup = "%s::text" lookup = "%s::text"
# Use UPPER(x) for case-insensitive lookups; it's faster. # Use UPPER(x) for case-insensitive lookups; it's faster.

View File

@ -20,6 +20,7 @@ from django.db.backends.sqlite3.schema import DatabaseSchemaEditor
from django.db.models import fields from django.db.models import fields
from django.db.models.sql import aggregates from django.db.models.sql import aggregates
from django.utils.dateparse import parse_date, parse_datetime, parse_time from django.utils.dateparse import parse_date, parse_datetime, parse_time
from django.utils.encoding import force_text
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.safestring import SafeBytes from django.utils.safestring import SafeBytes
from django.utils import six from django.utils import six
@ -103,6 +104,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
supports_foreign_keys = False supports_foreign_keys = False
supports_check_constraints = False supports_check_constraints = False
autocommits_when_autocommit_is_off = True autocommits_when_autocommit_is_off = True
supports_paramstyle_pyformat = False
@cached_property @cached_property
def uses_savepoints(self): def uses_savepoints(self):
@ -253,6 +255,9 @@ class DatabaseOperations(BaseDatabaseOperations):
and gets dates and datetimes wrong. and gets dates and datetimes wrong.
For consistency with other backends, coerce when required. For consistency with other backends, coerce when required.
""" """
if value is None:
return None
internal_type = field.get_internal_type() internal_type = field.get_internal_type()
if internal_type == 'DecimalField': if internal_type == 'DecimalField':
return util.typecast_decimal(field.format_number(value)) return util.typecast_decimal(field.format_number(value))
@ -526,4 +531,4 @@ def _sqlite_format_dtdelta(dt, conn, days, secs, usecs):
return str(dt) return str(dt)
def _sqlite_regexp(re_pattern, re_string): def _sqlite_regexp(re_pattern, re_string):
return bool(re.search(re_pattern, re_string)) return bool(re.search(re_pattern, force_text(re_string))) if re_string is not None else False

View File

@ -137,7 +137,7 @@ class RelatedField(Field):
# related object in a table-spanning query. It uses the lower-cased # related object in a table-spanning query. It uses the lower-cased
# object_name by default, but this can be overridden with the # object_name by default, but this can be overridden with the
# "related_name" option. # "related_name" option.
return self.rel.related_name or self.opts.model_name return self.rel.related_query_name or self.rel.related_name or self.opts.model_name
class RenameRelatedObjectDescriptorMethods(RenameMethodsBase): class RenameRelatedObjectDescriptorMethods(RenameMethodsBase):
@ -824,7 +824,7 @@ class ReverseManyRelatedObjectsDescriptor(object):
class ForeignObjectRel(object): class ForeignObjectRel(object):
def __init__(self, field, to, related_name=None, limit_choices_to=None, def __init__(self, field, to, related_name=None, limit_choices_to=None,
parent_link=False, on_delete=None): parent_link=False, on_delete=None, related_query_name=None):
try: try:
to._meta to._meta
except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
@ -833,6 +833,7 @@ class ForeignObjectRel(object):
self.field = field self.field = field
self.to = to self.to = to
self.related_name = related_name self.related_name = related_name
self.related_query_name = related_query_name
self.limit_choices_to = {} if limit_choices_to is None else limit_choices_to self.limit_choices_to = {} if limit_choices_to is None else limit_choices_to
self.multiple = True self.multiple = True
self.parent_link = parent_link self.parent_link = parent_link
@ -860,10 +861,10 @@ class ForeignObjectRel(object):
class ManyToOneRel(ForeignObjectRel): class ManyToOneRel(ForeignObjectRel):
def __init__(self, field, to, field_name, related_name=None, limit_choices_to=None, def __init__(self, field, to, field_name, related_name=None, limit_choices_to=None,
parent_link=False, on_delete=None): parent_link=False, on_delete=None, related_query_name=None):
super(ManyToOneRel, self).__init__( super(ManyToOneRel, self).__init__(
field, to, related_name=related_name, limit_choices_to=limit_choices_to, field, to, related_name=related_name, limit_choices_to=limit_choices_to,
parent_link=parent_link, on_delete=on_delete) parent_link=parent_link, on_delete=on_delete, related_query_name=related_query_name)
self.field_name = field_name self.field_name = field_name
def get_related_field(self): def get_related_field(self):
@ -883,21 +884,22 @@ class ManyToOneRel(ForeignObjectRel):
class OneToOneRel(ManyToOneRel): class OneToOneRel(ManyToOneRel):
def __init__(self, field, to, field_name, related_name=None, limit_choices_to=None, def __init__(self, field, to, field_name, related_name=None, limit_choices_to=None,
parent_link=False, on_delete=None): parent_link=False, on_delete=None, related_query_name=None):
super(OneToOneRel, self).__init__(field, to, field_name, super(OneToOneRel, self).__init__(field, to, field_name,
related_name=related_name, limit_choices_to=limit_choices_to, related_name=related_name, limit_choices_to=limit_choices_to,
parent_link=parent_link, on_delete=on_delete parent_link=parent_link, on_delete=on_delete, related_query_name=related_query_name,
) )
self.multiple = False self.multiple = False
class ManyToManyRel(object): class ManyToManyRel(object):
def __init__(self, to, related_name=None, limit_choices_to=None, def __init__(self, to, related_name=None, limit_choices_to=None,
symmetrical=True, through=None, db_constraint=True): symmetrical=True, through=None, db_constraint=True, related_query_name=None):
if through and not db_constraint: if through and not db_constraint:
raise ValueError("Can't supply a through model and db_constraint=False") raise ValueError("Can't supply a through model and db_constraint=False")
self.to = to self.to = to
self.related_name = related_name self.related_name = related_name
self.related_query_name = related_query_name
if limit_choices_to is None: if limit_choices_to is None:
limit_choices_to = {} limit_choices_to = {}
self.limit_choices_to = limit_choices_to self.limit_choices_to = limit_choices_to
@ -931,6 +933,7 @@ class ForeignObject(RelatedField):
kwargs['rel'] = ForeignObjectRel( kwargs['rel'] = ForeignObjectRel(
self, to, self, to,
related_name=kwargs.pop('related_name', None), related_name=kwargs.pop('related_name', None),
related_query_name=kwargs.pop('related_query_name', None),
limit_choices_to=kwargs.pop('limit_choices_to', None), limit_choices_to=kwargs.pop('limit_choices_to', None),
parent_link=kwargs.pop('parent_link', False), parent_link=kwargs.pop('parent_link', False),
on_delete=kwargs.pop('on_delete', CASCADE), on_delete=kwargs.pop('on_delete', CASCADE),
@ -1141,6 +1144,7 @@ class ForeignKey(ForeignObject):
kwargs['rel'] = rel_class( kwargs['rel'] = rel_class(
self, to, to_field, self, to, to_field,
related_name=kwargs.pop('related_name', None), related_name=kwargs.pop('related_name', None),
related_query_name=kwargs.pop('related_query_name', None),
limit_choices_to=kwargs.pop('limit_choices_to', None), limit_choices_to=kwargs.pop('limit_choices_to', None),
parent_link=kwargs.pop('parent_link', False), parent_link=kwargs.pop('parent_link', False),
on_delete=kwargs.pop('on_delete', CASCADE), on_delete=kwargs.pop('on_delete', CASCADE),
@ -1371,6 +1375,7 @@ class ManyToManyField(RelatedField):
kwargs['verbose_name'] = kwargs.get('verbose_name', None) kwargs['verbose_name'] = kwargs.get('verbose_name', None)
kwargs['rel'] = ManyToManyRel(to, kwargs['rel'] = ManyToManyRel(to,
related_name=kwargs.pop('related_name', None), related_name=kwargs.pop('related_name', None),
related_query_name=kwargs.pop('related_query_name', None),
limit_choices_to=kwargs.pop('limit_choices_to', None), limit_choices_to=kwargs.pop('limit_choices_to', None),
symmetrical=kwargs.pop('symmetrical', to == RECURSIVE_RELATIONSHIP_CONSTANT), symmetrical=kwargs.pop('symmetrical', to == RECURSIVE_RELATIONSHIP_CONSTANT),
through=kwargs.pop('through', None), through=kwargs.pop('through', None),
@ -1388,7 +1393,8 @@ class ManyToManyField(RelatedField):
# Handle the simpler arguments # Handle the simpler arguments
if self.rel.db_constraint is not True: if self.rel.db_constraint is not True:
kwargs['db_constraint'] = self.db_constraint kwargs['db_constraint'] = self.db_constraint
del kwargs['help_text'] if "help_text" in kwargs:
del kwargs['help_text']
# Rel needs more work. # Rel needs more work.
rel = self.rel rel = self.rel
if isinstance(self.rel.to, basestring): if isinstance(self.rel.to, basestring):

View File

@ -422,12 +422,36 @@ class Options(object):
return cache return cache
def get_add_permission(self): def get_add_permission(self):
"""
This method has been deprecated in favor of
`django.contrib.auth.get_permission_codename`. refs #20642
"""
warnings.warn(
"`Options.get_add_permission` has been deprecated in favor "
"of `django.contrib.auth.get_permission_codename`.",
PendingDeprecationWarning, stacklevel=2)
return 'add_%s' % self.model_name return 'add_%s' % self.model_name
def get_change_permission(self): def get_change_permission(self):
"""
This method has been deprecated in favor of
`django.contrib.auth.get_permission_codename`. refs #20642
"""
warnings.warn(
"`Options.get_change_permission` has been deprecated in favor "
"of `django.contrib.auth.get_permission_codename`.",
PendingDeprecationWarning, stacklevel=2)
return 'change_%s' % self.model_name return 'change_%s' % self.model_name
def get_delete_permission(self): def get_delete_permission(self):
"""
This method has been deprecated in favor of
`django.contrib.auth.get_permission_codename`. refs #20642
"""
warnings.warn(
"`Options.get_delete_permission` has been deprecated in favor "
"of `django.contrib.auth.get_permission_codename`.",
PendingDeprecationWarning, stacklevel=2)
return 'delete_%s' % self.model_name return 'delete_%s' % self.model_name
def get_all_related_objects(self, local_only=False, include_hidden=False, def get_all_related_objects(self, local_only=False, include_hidden=False,

View File

@ -1445,7 +1445,10 @@ class RawQuerySet(object):
yield instance yield instance
def __repr__(self): def __repr__(self):
return "<RawQuerySet: %r>" % (self.raw_query % tuple(self.params)) text = self.raw_query
if self.params:
text = text % (self.params if hasattr(self.params, 'keys') else tuple(self.params))
return "<RawQuerySet: %r>" % text
def __getitem__(self, k): def __getitem__(self, k):
return list(self)[k] return list(self)[k]

View File

@ -171,6 +171,26 @@ def clean_savepoints(using=None):
""" """
get_connection(using).clean_savepoints() get_connection(using).clean_savepoints()
def get_rollback(using=None):
"""
Gets the "needs rollback" flag -- for *advanced use* only.
"""
return get_connection(using).needs_rollback
def set_rollback(rollback, using=None):
"""
Sets or unsets the "needs rollback" flag -- for *advanced use* only.
When `rollback` is `True`, it triggers a rollback when exiting the
innermost enclosing atomic block that has `savepoint=True` (that's the
default). Use this to force a rollback without raising an exception.
When `rollback` is `False`, it prevents such a rollback. Use this only
after rolling back to a known-good state! Otherwise, you break the atomic
block and data corruption may occur.
"""
return get_connection(using).set_rollback(rollback)
################################# #################################
# Decorators / context managers # # Decorators / context managers #
################################# #################################

View File

@ -370,14 +370,8 @@ class DecimalField(IntegerField):
def widget_attrs(self, widget): def widget_attrs(self, widget):
attrs = super(DecimalField, self).widget_attrs(widget) attrs = super(DecimalField, self).widget_attrs(widget)
if isinstance(widget, NumberInput): if isinstance(widget, NumberInput) and self.decimal_places:
if self.max_digits is not None: attrs['step'] = '0.%s1' % ('0' * (self.decimal_places - 1))
max_length = self.max_digits + 1 # for the sign
if self.decimal_places is None or self.decimal_places > 0:
max_length += 1 # for the dot
attrs['maxlength'] = max_length
if self.decimal_places:
attrs['step'] = '0.%s1' % ('0' * (self.decimal_places-1))
return attrs return attrs

View File

@ -4,8 +4,9 @@ from django.core.exceptions import ValidationError
from django.forms import Form from django.forms import Form
from django.forms.fields import IntegerField, BooleanField from django.forms.fields import IntegerField, BooleanField
from django.forms.util import ErrorList from django.forms.util import ErrorList
from django.forms.widgets import Media, HiddenInput from django.forms.widgets import HiddenInput
from django.utils.encoding import python_2_unicode_compatible from django.utils.encoding import python_2_unicode_compatible
from django.utils.functional import cached_property
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils import six from django.utils import six
from django.utils.six.moves import xrange from django.utils.six.moves import xrange
@ -55,8 +56,6 @@ class BaseFormSet(object):
self.error_class = error_class self.error_class = error_class
self._errors = None self._errors = None
self._non_form_errors = None self._non_form_errors = None
# construct the forms in the formset
self._construct_forms()
def __str__(self): def __str__(self):
return self.as_table() return self.as_table()
@ -125,12 +124,14 @@ class BaseFormSet(object):
initial_forms = len(self.initial) if self.initial else 0 initial_forms = len(self.initial) if self.initial else 0
return initial_forms return initial_forms
def _construct_forms(self): @cached_property
# instantiate all the forms and put them in self.forms def forms(self):
self.forms = [] """
Instantiate forms at first property access.
"""
# DoS protection is included in total_form_count() # DoS protection is included in total_form_count()
for i in xrange(self.total_form_count()): forms = [self._construct_form(i) for i in xrange(self.total_form_count())]
self.forms.append(self._construct_form(i)) return forms
def _construct_form(self, i, **kwargs): def _construct_form(self, i, **kwargs):
""" """

View File

@ -3,7 +3,6 @@ from __future__ import unicode_literals
from django.conf import settings from django.conf import settings
from django.utils.html import format_html, format_html_join from django.utils.html import format_html, format_html_join
from django.utils.encoding import force_text, python_2_unicode_compatible from django.utils.encoding import force_text, python_2_unicode_compatible
from django.utils.safestring import mark_safe
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils import six from django.utils import six

View File

@ -5,7 +5,6 @@ HTML Widget classes
from __future__ import absolute_import, unicode_literals from __future__ import absolute_import, unicode_literals
import copy import copy
import datetime
from itertools import chain from itertools import chain
try: try:
from urllib.parse import urljoin from urllib.parse import urljoin
@ -16,8 +15,8 @@ import warnings
from django.conf import settings from django.conf import settings
from django.forms.util import flatatt, to_current_timezone from django.forms.util import flatatt, to_current_timezone
from django.utils.datastructures import MultiValueDict, MergeDict from django.utils.datastructures import MultiValueDict, MergeDict
from django.utils.html import conditional_escape, format_html, format_html_join from django.utils.html import conditional_escape, format_html
from django.utils.translation import ugettext, ugettext_lazy from django.utils.translation import ugettext_lazy
from django.utils.encoding import force_text, python_2_unicode_compatible from django.utils.encoding import force_text, python_2_unicode_compatible
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils import datetime_safe, formats, six from django.utils import datetime_safe, formats, six

View File

@ -39,6 +39,10 @@ class HttpRequest(object):
_upload_handlers = [] _upload_handlers = []
def __init__(self): def __init__(self):
# WARNING: The `WSGIRequest` subclass doesn't call `super`.
# Any variable assignment made here should also happen in
# `WSGIRequest.__init__()`.
self.GET, self.POST, self.COOKIES, self.META, self.FILES = {}, {}, {}, {}, {} self.GET, self.POST, self.COOKIES, self.META, self.FILES = {}, {}, {}, {}, {}
self.path = '' self.path = ''
self.path_info = '' self.path_info = ''

View File

@ -6,10 +6,8 @@ against request forgeries from other sites.
""" """
from __future__ import unicode_literals from __future__ import unicode_literals
import hashlib
import logging import logging
import re import re
import random
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import get_callable from django.core.urlresolvers import get_callable

View File

@ -1101,6 +1101,7 @@ class Library(object):
# for decorators that need it e.g. stringfilter # for decorators that need it e.g. stringfilter
if hasattr(filter_func, "_decorated_function"): if hasattr(filter_func, "_decorated_function"):
setattr(filter_func._decorated_function, attr, value) setattr(filter_func._decorated_function, attr, value)
filter_func._filter_name = name
return filter_func return filter_func
else: else:
raise InvalidTemplateLibrary("Unsupported arguments to " raise InvalidTemplateLibrary("Unsupported arguments to "

View File

@ -3,7 +3,6 @@ from __future__ import unicode_literals
import re import re
import random as random_module import random as random_module
import unicodedata
from decimal import Decimal, InvalidOperation, Context, ROUND_HALF_UP from decimal import Decimal, InvalidOperation, Context, ROUND_HALF_UP
from functools import wraps from functools import wraps
from pprint import pformat from pprint import pformat

View File

@ -665,8 +665,9 @@ def do_filter(parser, token):
_, rest = token.contents.split(None, 1) _, rest = token.contents.split(None, 1)
filter_expr = parser.compile_filter("var|%s" % (rest)) filter_expr = parser.compile_filter("var|%s" % (rest))
for func, unused in filter_expr.filters: for func, unused in filter_expr.filters:
if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'): filter_name = getattr(func, '_filter_name', None)
raise TemplateSyntaxError('"filter %s" is not permitted. Use the "autoescape" tag instead.' % func.__name__) if filter_name in ('escape', 'safe'):
raise TemplateSyntaxError('"filter %s" is not permitted. Use the "autoescape" tag instead.' % filter_name)
nodelist = parser.parse(('endfilter',)) nodelist = parser.parse(('endfilter',))
parser.delete_first_token() parser.delete_first_token()
return FilterNode(filter_expr, nodelist) return FilterNode(filter_expr, nodelist)

View File

@ -10,6 +10,7 @@ from django.conf import settings, UserSettingsHolder
from django.core import mail from django.core import mail
from django.core.signals import request_started from django.core.signals import request_started
from django.db import reset_queries from django.db import reset_queries
from django.http import request
from django.template import Template, loader, TemplateDoesNotExist from django.template import Template, loader, TemplateDoesNotExist
from django.template.loaders import cached from django.template.loaders import cached
from django.test.signals import template_rendered, setting_changed from django.test.signals import template_rendered, setting_changed
@ -87,13 +88,16 @@ def setup_test_environment():
- Set the email backend to the locmem email backend. - Set the email backend to the locmem email backend.
- Setting the active locale to match the LANGUAGE_CODE setting. - Setting the active locale to match the LANGUAGE_CODE setting.
""" """
Template.original_render = Template._render Template._original_render = Template._render
Template._render = instrumented_test_render Template._render = instrumented_test_render
mail.original_email_backend = settings.EMAIL_BACKEND # Storing previous values in the settings module itself is problematic.
# Store them in arbitrary (but related) modules instead. See #20636.
mail._original_email_backend = settings.EMAIL_BACKEND
settings.EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend' settings.EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'
settings._original_allowed_hosts = settings.ALLOWED_HOSTS request._original_allowed_hosts = settings.ALLOWED_HOSTS
settings.ALLOWED_HOSTS = ['*'] settings.ALLOWED_HOSTS = ['*']
mail.outbox = [] mail.outbox = []
@ -108,14 +112,14 @@ def teardown_test_environment():
- Restoring the email sending functions - Restoring the email sending functions
""" """
Template._render = Template.original_render Template._render = Template._original_render
del Template.original_render del Template._original_render
settings.EMAIL_BACKEND = mail.original_email_backend settings.EMAIL_BACKEND = mail._original_email_backend
del mail.original_email_backend del mail._original_email_backend
settings.ALLOWED_HOSTS = settings._original_allowed_hosts settings.ALLOWED_HOSTS = request._original_allowed_hosts
del settings._original_allowed_hosts del request._original_allowed_hosts
del mail.outbox del mail.outbox
@ -207,7 +211,6 @@ class override_settings(object):
""" """
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.options = kwargs self.options = kwargs
self.wrapped = settings._wrapped
def __enter__(self): def __enter__(self):
self.enable() self.enable()
@ -246,6 +249,7 @@ class override_settings(object):
override = UserSettingsHolder(settings._wrapped) override = UserSettingsHolder(settings._wrapped)
for key, new_value in self.options.items(): for key, new_value in self.options.items():
setattr(override, key, new_value) setattr(override, key, new_value)
self.wrapped = settings._wrapped
settings._wrapped = override settings._wrapped = override
for key, new_value in self.options.items(): for key, new_value in self.options.items():
setting_changed.send(sender=settings._wrapped.__class__, setting_changed.send(sender=settings._wrapped.__class__,
@ -253,6 +257,7 @@ class override_settings(object):
def disable(self): def disable(self):
settings._wrapped = self.wrapped settings._wrapped = self.wrapped
del self.wrapped
for key in self.options: for key in self.options:
new_value = getattr(settings, key, None) new_value = getattr(settings, key, None)
setting_changed.send(sender=settings._wrapped.__class__, setting_changed.send(sender=settings._wrapped.__class__,

View File

@ -3,7 +3,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import re import re
import string
try: try:
from urllib.parse import quote, urlsplit, urlunsplit from urllib.parse import quote, urlsplit, urlunsplit
except ImportError: # Python 2 except ImportError: # Python 2

View File

@ -1,5 +1,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import base64
import calendar import calendar
import datetime import datetime
import re import re
@ -11,7 +12,7 @@ except ImportError: # Python 2
import urlparse import urlparse
urllib_parse.urlparse = urlparse.urlparse urllib_parse.urlparse = urlparse.urlparse
from binascii import Error as BinasciiError
from email.utils import formatdate from email.utils import formatdate
from django.utils.datastructures import MultiValueDict from django.utils.datastructures import MultiValueDict
@ -202,6 +203,24 @@ def int_to_base36(i):
factor -= 1 factor -= 1
return ''.join(base36) return ''.join(base36)
def urlsafe_base64_encode(s):
"""
Encodes a bytestring in base64 for use in URLs, stripping any trailing
equal signs.
"""
return base64.urlsafe_b64encode(s).rstrip(b'\n=')
def urlsafe_base64_decode(s):
"""
Decodes a base64 encoded string, adding back any trailing equal signs that
might have been stripped.
"""
s = s.encode('utf-8') # base64encode should only return ASCII.
try:
return base64.urlsafe_b64decode(s.ljust(len(s) + len(s) % 4, b'='))
except (LookupError, BinasciiError) as e:
raise ValueError(e)
def parse_etags(etag_str): def parse_etags(etag_str):
""" """
Parses a string with one or several etags passed in If-None-Match and Parses a string with one or several etags passed in If-None-Match and

View File

@ -2,7 +2,6 @@ from __future__ import unicode_literals
import re import re
import unicodedata import unicodedata
import warnings
from gzip import GzipFile from gzip import GzipFile
from io import BytesIO from io import BytesIO

View File

@ -7,7 +7,6 @@ import sys
import types import types
from django.conf import settings from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.http import (HttpResponse, HttpResponseServerError, from django.http import (HttpResponse, HttpResponseServerError,
HttpResponseNotFound, HttpRequest, build_request_repr) HttpResponseNotFound, HttpRequest, build_request_repr)
from django.template import Template, Context, TemplateDoesNotExist from django.template import Template, Context, TemplateDoesNotExist

View File

@ -1,5 +1,3 @@
import warnings
from django.middleware.csrf import CsrfViewMiddleware, get_token from django.middleware.csrf import CsrfViewMiddleware, get_token
from django.utils.decorators import decorator_from_middleware, available_attrs from django.utils.decorators import decorator_from_middleware, available_attrs
from functools import wraps from functools import wraps

View File

@ -17,6 +17,7 @@ class FormMixin(ContextMixin):
initial = {} initial = {}
form_class = None form_class = None
success_url = None success_url = None
prefix = None
def get_initial(self): def get_initial(self):
""" """
@ -24,6 +25,12 @@ class FormMixin(ContextMixin):
""" """
return self.initial.copy() return self.initial.copy()
def get_prefix(self):
"""
Returns the prefix to use for forms on this view
"""
return self.prefix
def get_form_class(self): def get_form_class(self):
""" """
Returns the form class to use in this view Returns the form class to use in this view
@ -40,7 +47,11 @@ class FormMixin(ContextMixin):
""" """
Returns the keyword arguments for instantiating the form. Returns the keyword arguments for instantiating the form.
""" """
kwargs = {'initial': self.get_initial()} kwargs = {
'initial': self.get_initial(),
'prefix': self.get_prefix(),
}
if self.request.method in ('POST', 'PUT'): if self.request.method in ('POST', 'PUT'):
kwargs.update({ kwargs.update({
'data': self.request.POST, 'data': self.request.POST,
@ -78,6 +89,7 @@ class ModelFormMixin(FormMixin, SingleObjectMixin):
""" """
A mixin that provides a way to show and handle a modelform in a request. A mixin that provides a way to show and handle a modelform in a request.
""" """
fields = None
def get_form_class(self): def get_form_class(self):
""" """
@ -98,13 +110,12 @@ class ModelFormMixin(FormMixin, SingleObjectMixin):
# from that # from that
model = self.get_queryset().model model = self.get_queryset().model
fields = getattr(self, 'fields', None) if self.fields is None:
if fields is None:
warnings.warn("Using ModelFormMixin (base class of %s) without " warnings.warn("Using ModelFormMixin (base class of %s) without "
"the 'fields' attribute is deprecated." % self.__class__.__name__, "the 'fields' attribute is deprecated." % self.__class__.__name__,
PendingDeprecationWarning) PendingDeprecationWarning)
return model_forms.modelform_factory(model, fields=fields) return model_forms.modelform_factory(model, fields=self.fields)
def get_form_kwargs(self): def get_form_kwargs(self):
""" """

View File

@ -55,7 +55,7 @@ copyright = 'Django Software Foundation and contributors'
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y version.
version = '1.6' version = '1.7'
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
try: try:
from django import VERSION, get_version from django import VERSION, get_version
@ -71,7 +71,7 @@ else:
release = django_release() release = django_release()
# The "development version" of Django # The "development version" of Django
django_next_version = '1.6' django_next_version = '1.7'
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.

View File

@ -16,9 +16,8 @@ How do I get started?
What are Django's prerequisites? What are Django's prerequisites?
-------------------------------- --------------------------------
Django requires Python, specifically Python 2.6.5 - 2.7.x. No other Python Django requires Python, specifically Python 2.6.5 - 2.7.x, or 3.2.3 and above.
libraries are required for basic Django usage. Django 1.5 also has No other Python libraries are required for basic Django usage.
experimental support for Python 3.2.3 and above.
For a development environment -- if you just want to experiment with Django -- 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 you don't need to have a separate Web server installed; Django comes with its
@ -43,7 +42,7 @@ Do I lose anything by using Python 2.6 versus newer Python versions, such as Pyt
---------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------
Not in the core framework. Currently, Django itself officially supports Not in the core framework. Currently, Django itself officially supports
Python 2.6 (2.6.5 or higher) and 2.7. However, newer versions of Python 2.6 (2.6.5 or higher), 2.7, 3.2.3 or higher. However, newer versions of
Python are often faster, have more features, and are better supported. If you Python are often faster, have more features, and are better supported. If you
use a newer version of Python you will also have access to some APIs that use a newer version of Python you will also have access to some APIs that
aren't available under older versions of Python. aren't available under older versions of Python.
@ -51,12 +50,9 @@ aren't available under older versions of Python.
Third-party applications for use with Django are, of course, free to set their Third-party applications for use with Django are, of course, free to set their
own version requirements. own version requirements.
All else being equal, we recommend that you use the latest 2.x release All else being equal, we recommend that you use the latest 2.7 or 3.x release.
(currently Python 2.7). This will let you take advantage of the numerous This will let you take advantage of the numerous improvements and optimizations
improvements and optimizations to the Python language since version 2.6. 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? What Python version can I use with Django?
------------------------------------------ ------------------------------------------
@ -77,15 +73,12 @@ Django version Python versions
Can I use Django with Python 3? Can I use Django with Python 3?
------------------------------- -------------------------------
Django 1.5 introduces experimental support for Python 3.2.3 and above. However, Yes, you can!
we don't yet suggest that you use Django and Python 3 in production.
Python 3 support should be considered a "preview". It's offered to bootstrap Django 1.5 introduced experimental support for Python 3.2.3 and above.
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. As of Django 1.6, Python 3 support is considered stable and you can safely use
it in production. See also :doc:`/topics/python3`.
Will Django run under shared hosting (like TextDrive or Dreamhost)? Will Django run under shared hosting (like TextDrive or Dreamhost)?
------------------------------------------------------------------- -------------------------------------------------------------------

View File

@ -31,7 +31,7 @@ Our example object
Creating custom fields requires a bit of attention to detail. To make things Creating custom fields requires a bit of attention to detail. To make things
easier to follow, we'll use a consistent example throughout this document: easier to follow, we'll use a consistent example throughout this document:
wrapping a Python object representing the deal of cards in a hand of Bridge_. wrapping a Python object representing the deal of cards in a hand of Bridge_.
Don't worry, you don't have know how to play Bridge to follow this example. Don't worry, you don't have to know how to play Bridge to follow this example.
You only need to know that 52 cards are dealt out equally to four players, who You only need to know that 52 cards are dealt out equally to four players, who
are traditionally called *north*, *east*, *south* and *west*. Our class looks are traditionally called *north*, *east*, *south* and *west*. Our class looks
something like this:: something like this::

View File

@ -25,7 +25,8 @@ Basic configuration
=================== ===================
Once you've got mod_wsgi installed and activated, edit your Apache server's Once you've got mod_wsgi installed and activated, edit your Apache server's
``httpd.conf`` file and add ``httpd.conf`` file and add the following. If you are using a version of Apache
older than 2.4, replace ``Require all granted`` with ``Allow from all``.
.. code-block:: apache .. code-block:: apache
@ -35,7 +36,7 @@ Once you've got mod_wsgi installed and activated, edit your Apache server's
<Directory /path/to/mysite.com/mysite> <Directory /path/to/mysite.com/mysite>
<Files wsgi.py> <Files wsgi.py>
Order deny,allow Order deny,allow
Allow from all Require all granted
</Files> </Files>
</Directory> </Directory>

View File

@ -68,7 +68,7 @@ details on how ``staticfiles`` finds your files.
Now we *might* be able to get away with putting our static files directly Now we *might* be able to get away with putting our static files directly
in ``my_app/static/`` (rather than creating another ``my_app`` in ``my_app/static/`` (rather than creating another ``my_app``
subdirectory), but it would actually be a bad idea. Django will use the subdirectory), but it would actually be a bad idea. Django will use the
last static file it finds whose name matches, and if you had a static file first static file it finds whose name matches, and if you had a static file
with the same name in a *different* application, Django would be unable to with the same name in a *different* application, Django would be unable to
distinguish between them. We need to be able to point Django at the right distinguish between them. We need to be able to point Django at the right
one, and the easiest way to ensure this is by *namespacing* them. That is, one, and the easiest way to ensure this is by *namespacing* them. That is,

View File

@ -255,7 +255,11 @@ Keywords
~~~~~~~~ ~~~~~~~~
With this field you may label a ticket with multiple keywords. This can be With this field you may label a ticket with multiple keywords. This can be
useful, for example, to group several tickets of a same theme. useful, for example, to group several tickets of a same theme. Keywords can
either be comma or space separated. Keyword search finds the keyword string
anywhere in the keywords. For example, clicking on a ticket with the keyword
"form" will yield similar tickets tagged with keywords containing strings such
as "formset", "modelformset", and "ManagementForm".
.. _closing-tickets: .. _closing-tickets:

View File

@ -326,6 +326,14 @@ these changes.
remove calls to this method, and instead ensure that their auth related views remove calls to this method, and instead ensure that their auth related views
are CSRF protected, which ensures that cookies are enabled. are CSRF protected, which ensures that cookies are enabled.
* The version of :func:`django.contrib.auth.views.password_reset_confirm` that
supports base36 encoded user IDs
(``django.contrib.auth.views.password_reset_confirm_uidb36``) will be
removed. If your site has been running Django 1.6 for more than
:setting:`PASSWORD_RESET_TIMEOUT_DAYS`, this change will have no effect. If
not, then any password reset links generated before you upgrade to Django 1.7
won't work after the upgrade.
1.8 1.8
--- ---

View File

@ -67,7 +67,7 @@ After the previous tutorials, our project should look like this::
admin.py admin.py
models.py models.py
static/ static/
polls polls/
images/ images/
background.gif background.gif
style.css style.css

View File

@ -339,14 +339,14 @@ Put the following code in that template:
Now let's update our ``index`` view in ``polls/views.py`` to use the template:: Now let's update our ``index`` view in ``polls/views.py`` to use the template::
from django.http import HttpResponse from django.http import HttpResponse
from django.template import Context, loader from django.template import RequestContext, loader
from polls.models import Poll from polls.models import Poll
def index(request): def index(request):
latest_poll_list = Poll.objects.order_by('-pub_date')[:5] latest_poll_list = Poll.objects.order_by('-pub_date')[:5]
template = loader.get_template('polls/index.html') template = loader.get_template('polls/index.html')
context = Context({ context = RequestContext(request, {
'latest_poll_list': latest_poll_list, 'latest_poll_list': latest_poll_list,
}) })
return HttpResponse(template.render(context)) return HttpResponse(template.render(context))
@ -377,7 +377,7 @@ rewritten::
return render(request, 'polls/index.html', context) return render(request, 'polls/index.html', context)
Note that once we've done this in all these views, we no longer need to import Note that once we've done this in all these views, we no longer need to import
:mod:`~django.template.loader`, :class:`~django.template.Context` and :mod:`~django.template.loader`, :class:`~django.template.RequestContext` and
:class:`~django.http.HttpResponse` (you'll want to keep ``HttpResponse`` if you :class:`~django.http.HttpResponse` (you'll want to keep ``HttpResponse`` if you
still have the stub methods for ``detail``, ``results``, and ``vote``). still have the stub methods for ``detail``, ``results``, and ``vote``).

View File

@ -151,6 +151,7 @@ FormView
* :attr:`~django.views.generic.edit.FormMixin.form_class` [:meth:`~django.views.generic.edit.FormMixin.get_form_class`] * :attr:`~django.views.generic.edit.FormMixin.form_class` [:meth:`~django.views.generic.edit.FormMixin.get_form_class`]
* :attr:`~django.views.generic.base.View.http_method_names` * :attr:`~django.views.generic.base.View.http_method_names`
* :attr:`~django.views.generic.edit.FormMixin.initial` [:meth:`~django.views.generic.edit.FormMixin.get_initial`] * :attr:`~django.views.generic.edit.FormMixin.initial` [:meth:`~django.views.generic.edit.FormMixin.get_initial`]
* :attr:`~django.views.generic.edit.FormMixin.prefix` [:meth:`~django.views.generic.edit.FormMixin.get_prefix`]
* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` [:meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`] * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` [:meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`]
* :attr:`~django.views.generic.edit.FormMixin.success_url` [:meth:`~django.views.generic.edit.FormMixin.get_success_url`] * :attr:`~django.views.generic.edit.FormMixin.success_url` [:meth:`~django.views.generic.edit.FormMixin.get_success_url`]
* :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`] * :attr:`~django.views.generic.base.TemplateResponseMixin.template_name` [:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names`]
@ -177,11 +178,13 @@ CreateView
* :attr:`~django.views.generic.base.TemplateResponseMixin.content_type` * :attr:`~django.views.generic.base.TemplateResponseMixin.content_type`
* :attr:`~django.views.generic.detail.SingleObjectMixin.context_object_name` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_context_object_name`] * :attr:`~django.views.generic.detail.SingleObjectMixin.context_object_name` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_context_object_name`]
* :attr:`~django.views.generic.edit.ModelFormMixin.fields`
* :attr:`~django.views.generic.edit.FormMixin.form_class` [:meth:`~django.views.generic.edit.FormMixin.get_form_class`] * :attr:`~django.views.generic.edit.FormMixin.form_class` [:meth:`~django.views.generic.edit.FormMixin.get_form_class`]
* :attr:`~django.views.generic.base.View.http_method_names` * :attr:`~django.views.generic.base.View.http_method_names`
* :attr:`~django.views.generic.edit.FormMixin.initial` [:meth:`~django.views.generic.edit.FormMixin.get_initial`] * :attr:`~django.views.generic.edit.FormMixin.initial` [:meth:`~django.views.generic.edit.FormMixin.get_initial`]
* :attr:`~django.views.generic.detail.SingleObjectMixin.model` * :attr:`~django.views.generic.detail.SingleObjectMixin.model`
* :attr:`~django.views.generic.detail.SingleObjectMixin.pk_url_kwarg` * :attr:`~django.views.generic.detail.SingleObjectMixin.pk_url_kwarg`
* :attr:`~django.views.generic.edit.FormMixin.prefix` [:meth:`~django.views.generic.edit.FormMixin.get_prefix`]
* :attr:`~django.views.generic.detail.SingleObjectMixin.queryset` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_queryset`] * :attr:`~django.views.generic.detail.SingleObjectMixin.queryset` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_queryset`]
* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` [:meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`] * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` [:meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`]
* :attr:`~django.views.generic.detail.SingleObjectMixin.slug_field` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_slug_field`] * :attr:`~django.views.generic.detail.SingleObjectMixin.slug_field` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_slug_field`]
@ -216,11 +219,13 @@ UpdateView
* :attr:`~django.views.generic.base.TemplateResponseMixin.content_type` * :attr:`~django.views.generic.base.TemplateResponseMixin.content_type`
* :attr:`~django.views.generic.detail.SingleObjectMixin.context_object_name` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_context_object_name`] * :attr:`~django.views.generic.detail.SingleObjectMixin.context_object_name` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_context_object_name`]
* :attr:`~django.views.generic.edit.ModelFormMixin.fields`
* :attr:`~django.views.generic.edit.FormMixin.form_class` [:meth:`~django.views.generic.edit.FormMixin.get_form_class`] * :attr:`~django.views.generic.edit.FormMixin.form_class` [:meth:`~django.views.generic.edit.FormMixin.get_form_class`]
* :attr:`~django.views.generic.base.View.http_method_names` * :attr:`~django.views.generic.base.View.http_method_names`
* :attr:`~django.views.generic.edit.FormMixin.initial` [:meth:`~django.views.generic.edit.FormMixin.get_initial`] * :attr:`~django.views.generic.edit.FormMixin.initial` [:meth:`~django.views.generic.edit.FormMixin.get_initial`]
* :attr:`~django.views.generic.detail.SingleObjectMixin.model` * :attr:`~django.views.generic.detail.SingleObjectMixin.model`
* :attr:`~django.views.generic.detail.SingleObjectMixin.pk_url_kwarg` * :attr:`~django.views.generic.detail.SingleObjectMixin.pk_url_kwarg`
* :attr:`~django.views.generic.edit.FormMixin.prefix` [:meth:`~django.views.generic.edit.FormMixin.get_prefix`]
* :attr:`~django.views.generic.detail.SingleObjectMixin.queryset` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_queryset`] * :attr:`~django.views.generic.detail.SingleObjectMixin.queryset` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_queryset`]
* :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` [:meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`] * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` [:meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`]
* :attr:`~django.views.generic.detail.SingleObjectMixin.slug_field` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_slug_field`] * :attr:`~django.views.generic.detail.SingleObjectMixin.slug_field` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_slug_field`]

View File

@ -35,6 +35,12 @@ FormMixin
The URL to redirect to when the form is successfully processed. The URL to redirect to when the form is successfully processed.
.. attribute:: prefix
.. versionadded:: 1.6
The :attr:`~django.forms.Form.prefix` for the generated form.
.. method:: get_initial() .. method:: get_initial()
Retrieve initial data for the form. By default, returns a copy of Retrieve initial data for the form. By default, returns a copy of
@ -58,6 +64,13 @@ FormMixin
request is a ``POST`` or ``PUT``, the request data (``request.POST`` request is a ``POST`` or ``PUT``, the request data (``request.POST``
and ``request.FILES``) will also be provided. and ``request.FILES``) will also be provided.
.. method:: get_prefix()
.. versionadded:: 1.6
Determine the :attr:`~django.forms.Form.prefix` for the generated form.
Returns :attr:`~django.views.generic.edit.FormMixin.prefix` by default.
.. method:: get_success_url() .. method:: get_success_url()
Determine the URL to redirect to when the form is successfully Determine the URL to redirect to when the form is successfully

View File

@ -2278,9 +2278,14 @@ your URLconf. Specifically, add these four patterns:
url(r'^admin/password_reset/$', 'django.contrib.auth.views.password_reset', name='admin_password_reset'), url(r'^admin/password_reset/$', 'django.contrib.auth.views.password_reset', name='admin_password_reset'),
url(r'^admin/password_reset/done/$', 'django.contrib.auth.views.password_reset_done', name='password_reset_done'), url(r'^admin/password_reset/done/$', 'django.contrib.auth.views.password_reset_done', name='password_reset_done'),
url(r'^reset/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$', 'django.contrib.auth.views.password_reset_confirm', name='password_reset_confirm'), url(r'^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>.+)/$', 'django.contrib.auth.views.password_reset_confirm', name='password_reset_confirm'),
url(r'^reset/done/$', 'django.contrib.auth.views.password_reset_complete', name='password_reset_complete'), url(r'^reset/done/$', 'django.contrib.auth.views.password_reset_complete', name='password_reset_complete'),
.. versionchanged:: 1.6
The pattern for :func:`~django.contrib.auth.views.password_reset_confirm`
changed as the ``uid`` is now base 64 encoded.
(This assumes you've added the admin at ``admin/`` and requires that you put (This assumes you've added the admin at ``admin/`` and requires that you put
the URLs starting with ``^admin/`` before the line that includes the admin app the URLs starting with ``^admin/`` before the line that includes the admin app
itself). itself).

View File

@ -358,7 +358,7 @@ with a caching decorator -- you must name your sitemap view and pass
from django.views.decorators.cache import cache_page from django.views.decorators.cache import cache_page
urlpatterns = patterns('', urlpatterns = patterns('',
url(r'^sitemap.xml$', url(r'^sitemap\.xml$',
cache_page(86400)(sitemaps_views.index), cache_page(86400)(sitemaps_views.index),
{'sitemaps': sitemaps, 'sitemap_url_name': 'sitemaps'}), {'sitemaps': sitemaps, 'sitemap_url_name': 'sitemaps'}),
url(r'^sitemap-(?P<section>.+)\.xml$', url(r'^sitemap-(?P<section>.+)\.xml$',

View File

@ -623,6 +623,14 @@ If you're getting this error, you can solve it by:
SQLite does not support the ``SELECT ... FOR UPDATE`` syntax. Calling it will SQLite does not support the ``SELECT ... FOR UPDATE`` syntax. Calling it will
have no effect. have no effect.
"pyformat" parameter style in raw queries not supported
-------------------------------------------------------
For most backends, raw queries (``Manager.raw()`` or ``cursor.execute()``)
can use the "pyformat" parameter style, where placeholders in the query
are given as ``'%(name)s'`` and the parameters are passed as a dictionary
rather than a list. SQLite does not support this.
.. _sqlite-connection-queries: .. _sqlite-connection-queries:
Parameters not quoted in ``connection.queries`` Parameters not quoted in ``connection.queries``

View File

@ -1083,6 +1083,22 @@ define the details of how the relation works.
user = models.ForeignKey(User, related_name='+') user = models.ForeignKey(User, related_name='+')
.. attribute:: ForeignKey.related_query_name
.. versionadded:: 1.6
The name to use for the reverse filter name from the target model.
Defaults to the value of :attr:`related_name` if it is set, otherwise it
defaults to the name of the model::
# Declare the ForeignKey with related_query_name
class Tag(models.Model):
article = models.ForeignKey(Article, related_name="tags", related_query_name="tag")
name = models.CharField(max_length=255)
# That's now the name of the reverse filter
article_instance.filter(tag__name="important")
.. attribute:: ForeignKey.to_field .. attribute:: ForeignKey.to_field
The field on the related object that the relation is to. By default, Django The field on the related object that the relation is to. By default, Django
@ -1207,6 +1223,12 @@ that control how the relationship functions.
users = models.ManyToManyField(User, related_name='u+') users = models.ManyToManyField(User, related_name='u+')
referents = models.ManyToManyField(User, related_name='ref+') referents = models.ManyToManyField(User, related_name='ref+')
.. attribute:: ForeignKey.related_query_name
.. versionadded:: 1.6
Same as :attr:`ForeignKey.related_query_name`.
.. attribute:: ManyToManyField.limit_choices_to .. attribute:: ManyToManyField.limit_choices_to
Same as :attr:`ForeignKey.limit_choices_to`. Same as :attr:`ForeignKey.limit_choices_to`.

View File

@ -679,8 +679,11 @@ For every :class:`~django.db.models.DateField` and
returns the next and previous object with respect to the date field, raising returns the next and previous object with respect to the date field, raising
a :exc:`~django.core.exceptions.DoesNotExist` exception when appropriate. a :exc:`~django.core.exceptions.DoesNotExist` exception when appropriate.
Both methods accept optional keyword arguments, which should be in the format Both of these methods will perform their queries using the default
described in :ref:`Field lookups <field-lookups>`. manager for the model. If you need to emulate filtering used by a
custom manager, or want to perform one-off custom filtering, both
methods also accept optional keyword arguments, which should be in the
format described in :ref:`Field lookups <field-lookups>`.
Note that in the case of identical date values, these methods will use the Note that in the case of identical date values, these methods will use the
primary key as a tie-breaker. This guarantees that no records are skipped or primary key as a tie-breaker. This guarantees that no records are skipped or

View File

@ -1350,8 +1350,14 @@ A data structure containing configuration information. The contents of
this data structure will be passed as the argument to the this data structure will be passed as the argument to the
configuration method described in :setting:`LOGGING_CONFIG`. configuration method described in :setting:`LOGGING_CONFIG`.
The default logging configuration passes HTTP 500 server errors to an Among other things, the default logging configuration passes HTTP 500 server
email log handler; all other log messages are given to a NullHandler. errors to an email log handler when :setting:`DEBUG` is ``False``. See also
:ref:`configuring-logging`.
You can see the default logging configuration by looking in
``django/utils/log.py`` (or view the `online source`__).
__ https://github.com/django/django/blob/master/django/utils/log.py
.. setting:: LOGGING_CONFIG .. setting:: LOGGING_CONFIG
@ -2564,7 +2570,9 @@ various locations.
The default will find files stored in the :setting:`STATICFILES_DIRS` setting The default will find files stored in the :setting:`STATICFILES_DIRS` setting
(using ``django.contrib.staticfiles.finders.FileSystemFinder``) and in a (using ``django.contrib.staticfiles.finders.FileSystemFinder``) and in a
``static`` subdirectory of each app (using ``static`` subdirectory of each app (using
``django.contrib.staticfiles.finders.AppDirectoriesFinder``) ``django.contrib.staticfiles.finders.AppDirectoriesFinder``). If multiple
files with the same name are present, the first file that is found will be
used.
One finder is disabled by default: One finder is disabled by default:
``django.contrib.staticfiles.finders.DefaultStorageFinder``. If added to ``django.contrib.staticfiles.finders.DefaultStorageFinder``. If added to

View File

@ -255,7 +255,7 @@ Arguments sent with this signal:
``pk_set`` ``pk_set``
For the ``pre_add``, ``post_add``, ``pre_remove`` and ``post_remove`` For the ``pre_add``, ``post_add``, ``pre_remove`` and ``post_remove``
actions, this is a list of primary key values that have been added to actions, this is a set of primary key values that have been added to
or removed from the relation. or removed from the relation.
For the ``pre_clear`` and ``post_clear`` actions, this is ``None``. For the ``pre_clear`` and ``post_clear`` actions, this is ``None``.
@ -284,7 +284,7 @@ If we connected a handler like this::
and then did something like this:: and then did something like this::
>>> p = Pizza.object.create(...) >>> p = Pizza.objects.create(...)
>>> t = Topping.objects.create(...) >>> t = Topping.objects.create(...)
>>> p.toppings.add(t) >>> p.toppings.add(t)
@ -307,7 +307,7 @@ Argument Value
``model`` ``Topping`` (the class of the objects added to the ``model`` ``Topping`` (the class of the objects added to the
``Pizza``) ``Pizza``)
``pk_set`` ``[t.id]`` (since only ``Topping t`` was added to the relation) ``pk_set`` ``set([t.id])`` (since only ``Topping t`` was added to the relation)
``using`` ``"default"`` (since the default router sends writes here) ``using`` ``"default"`` (since the default router sends writes here)
============== ============================================================ ============== ============================================================
@ -334,7 +334,7 @@ Argument Value
``model`` ``Pizza`` (the class of the objects removed from the ``model`` ``Pizza`` (the class of the objects removed from the
``Topping``) ``Topping``)
``pk_set`` ``[p.id]`` (since only ``Pizza p`` was removed from the ``pk_set`` ``set([p.id])`` (since only ``Pizza p`` was removed from the
relation) relation)
``using`` ``"default"`` (since the default router sends writes here) ``using`` ``"default"`` (since the default router sends writes here)

View File

@ -649,6 +649,20 @@ escaping HTML.
Converts a positive integer to a base 36 string. On Python 2 ``i`` must be Converts a positive integer to a base 36 string. On Python 2 ``i`` must be
smaller than :data:`sys.maxint`. smaller than :data:`sys.maxint`.
.. function:: urlsafe_base64_encode(s)
.. versionadded:: 1.6
Encodes a bytestring in base64 for use in URLs, stripping any trailing
equal signs.
.. function:: urlsafe_base64_decode(s)
.. versionadded:: 1.6
Decodes a base64 encoded string, adding back any trailing equal signs that
might have been stripped.
``django.utils.module_loading`` ``django.utils.module_loading``
=============================== ===============================

View File

@ -121,10 +121,10 @@ GeoDjango now provides :ref:`form fields and widgets <ref-gis-forms-api>` for
its geo-specialized fields. They are OpenLayers-based by default, but they can its geo-specialized fields. They are OpenLayers-based by default, but they can
be customized to use any other JS framework. be customized to use any other JS framework.
``checksetup`` management command added for verifying compatibility ``check`` management command added for verifying compatibility
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A ``checksetup`` management command was added, enabling you to verify if your A ``check`` management command was added, enabling you to verify if your
current configuration (currently oriented at settings) is compatible with the current configuration (currently oriented at settings) is compatible with the
current version of Django. current version of Django.
@ -330,6 +330,19 @@ Minor features
behavior of clearing filters by setting the behavior of clearing filters by setting the
:attr:`~django.contrib.admin.ModelAdmin.preserve_filters` attribute to ``False``. :attr:`~django.contrib.admin.ModelAdmin.preserve_filters` attribute to ``False``.
* Added
:meth:`FormMixin.get_prefix<django.views.generic.edit.FormMixin.get_prefix>`
(which returns
:attr:`FormMixin.prefix<django.views.generic.edit.FormMixin.prefix>` by
default) to allow customizing the :attr:`~django.forms.Form.prefix` of the
form.
* Raw queries (``Manager.raw()`` or ``cursor.execute()``) can now use the
"pyformat" parameter style, where placeholders in the query are given as
``'%(name)s'`` and the parameters are passed as a dictionary rather than
a list (except on SQLite). This has long been possible (but not officially
supported) on MySQL and PostgreSQL, and is now also available on Oracle.
Backwards incompatible changes in 1.6 Backwards incompatible changes in 1.6
===================================== =====================================
@ -649,6 +662,59 @@ rely on the previous URLs. If you want to revert to the original behavior you
can set the can set the
:attr:`~django.contrib.admin.ModelAdmin.preserve_filters` attribute to ``False``. :attr:`~django.contrib.admin.ModelAdmin.preserve_filters` attribute to ``False``.
``django.contrib.auth`` password reset uses base 64 encoding of ``User`` PK
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Past versions of Django used base 36 encoding of the ``User`` primary key in
the password reset views and URLs
(:func:`django.contrib.auth.views.password_reset_confirm`). Base 36 encoding is
sufficient if the user primary key is an integer, however, with the
introduction of custom user models in Django 1.5, that assumption may no longer
be true.
:func:`django.contrib.auth.views.password_reset_confirm` has been modified to
take a ``uidb64`` parameter instead of ``uidb36``. If you are reversing this
view, for example in a custom ``password_reset_email.html`` template, be sure
to update your code.
A temporary shim for :func:`django.contrib.auth.views.password_reset_confirm`
that will allow password reset links generated prior to Django 1.6 to continue
to work has been added to provide backwards compatibility; this will be removed
in Django 1.7. Thus, as long as your site has been running Django 1.6 for more
than :setting:`PASSWORD_RESET_TIMEOUT_DAYS`, this change will have no effect.
If not (for example, if you upgrade directly from Django 1.5 to Django 1.7),
then any password reset links generated before you upgrade to Django 1.7 or
later won't work after the upgrade.
In addition, if you have any custom password reset URLs, you will need to
update them by replacing ``uidb36`` with ``uidb64`` and the dash that follows
that pattern with a slash. Also add ``_\-`` to the list of characters that may
match the ``uidb64`` pattern.
For example::
url(r'^reset/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$',
'django.contrib.auth.views.password_reset_confirm',
name='password_reset_confirm'),
becomes::
url(r'^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>.+)/$',
'django.contrib.auth.views.password_reset_confirm',
name='password_reset_confirm'),
You may also want to add the shim to support the old style reset links. Using
the example above, you would modify the existing url by replacing
``django.contrib.auth.views.password_reset_confirm`` with
``django.contrib.auth.views.password_reset_confirm_uidb36`` and also remove
the ``name`` argument so it doesn't conflict with the new url::
url(r'^reset/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$',
'django.contrib.auth.views.password_reset_confirm_uidb36'),
You can remove this url pattern after your app has been deployed with Django
1.6 for :setting:`PASSWORD_RESET_TIMEOUT_DAYS`.
Miscellaneous Miscellaneous
~~~~~~~~~~~~~ ~~~~~~~~~~~~~
@ -725,6 +791,12 @@ Miscellaneous
returned ``False`` for blank passwords. This has been corrected in this returned ``False`` for blank passwords. This has been corrected in this
release: blank passwords are now valid. release: blank passwords are now valid.
* The admin :attr:`~django.contrib.admin.ModelAdmin.changelist_view` previously
accepted a ``pop`` GET parameter to signify it was to be displayed in a popup.
This parameter has been renamed to ``_popup`` to be consistent with the rest
of the admin views. You should update your custom templates if they use the
previous parameter name.
Features deprecated in 1.6 Features deprecated in 1.6
========================== ==========================
@ -842,6 +914,13 @@ on a widget, you should now define this method on the form field itself.
``Model._meta.module_name`` was renamed to ``model_name``. Despite being a ``Model._meta.module_name`` was renamed to ``model_name``. Despite being a
private API, it will go through a regular deprecation path. private API, it will go through a regular deprecation path.
``get_(add|change|delete)_permission`` model _meta methods
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``Model._meta.get_(add|change|delete)_permission`` methods were deprecated.
Even if they were not part of the public API they'll also go through
a regular deprecation path.
``get_query_set`` and similar methods renamed to ``get_queryset`` ``get_query_set`` and similar methods renamed to ``get_queryset``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -817,7 +817,7 @@ patterns.
* ``protocol``: http or https * ``protocol``: http or https
* ``uid``: The user's id encoded in base 36. * ``uid``: The user's primary key encoded in base 64.
* ``token``: Token to check that the reset link is valid. * ``token``: Token to check that the reset link is valid.
@ -826,7 +826,12 @@ patterns.
.. code-block:: html+django .. code-block:: html+django
Someone asked for password reset for email {{ email }}. Follow the link below: Someone asked for password reset for email {{ email }}. Follow the link below:
{{ protocol}}://{{ domain }}{% url 'password_reset_confirm' uidb36=uid token=token %} {{ protocol}}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
.. versionchanged:: 1.6
Reversing ``password_reset_confirm`` takes a ``uidb64`` argument instead
of ``uidb36``.
The same template context is used for subject template. Subject must be The same template context is used for subject template. Subject must be
single line plain text string. single line plain text string.
@ -846,7 +851,7 @@ patterns.
Defaults to :file:`registration/password_reset_done.html` if not Defaults to :file:`registration/password_reset_done.html` if not
supplied. supplied.
.. function:: password_reset_confirm(request[, uidb36, token, template_name, token_generator, set_password_form, post_reset_redirect]) .. function:: password_reset_confirm(request[, uidb64, token, template_name, token_generator, set_password_form, post_reset_redirect])
Presents a form for entering a new password. Presents a form for entering a new password.
@ -854,7 +859,12 @@ patterns.
**Optional arguments:** **Optional arguments:**
* ``uidb36``: The user's id encoded in base 36. Defaults to ``None``. * ``uidb64``: The user's id encoded in base 64. Defaults to ``None``.
.. versionchanged:: 1.6
The ``uidb64`` parameter was previously base 36 encoded and named
``uidb36``.
* ``token``: Token to check that the password is valid. Defaults to * ``token``: Token to check that the password is valid. Defaults to
``None``. ``None``.
@ -877,8 +887,8 @@ patterns.
* ``form``: The form (see ``set_password_form`` above) for setting the * ``form``: The form (see ``set_password_form`` above) for setting the
new user's password. new user's password.
* ``validlink``: Boolean, True if the link (combination of uidb36 and * ``validlink``: Boolean, True if the link (combination of ``uidb64`` and
token) is valid or unused yet. ``token``) is valid or unused yet.
.. function:: password_reset_complete(request[,template_name]) .. function:: password_reset_complete(request[,template_name])

View File

@ -92,6 +92,15 @@ We'll be using these models::
def __unicode__(self): def __unicode__(self):
return self.name return self.name
class Author(models.Model):
salutation = models.CharField(max_length=10)
name = models.CharField(max_length=200)
email = models.EmailField()
headshot = models.ImageField(upload_to='author_headshots')
def __unicode__(self):
return self.name
class Book(models.Model): class Book(models.Model):
title = models.CharField(max_length=100) title = models.CharField(max_length=100)
authors = models.ManyToManyField('Author') authors = models.ManyToManyField('Author')
@ -132,11 +141,11 @@ bit is just the lowercased version of the model's name.
enabled in :setting:`TEMPLATE_LOADERS`, a template location could be: enabled in :setting:`TEMPLATE_LOADERS`, a template location could be:
/path/to/project/books/templates/books/publisher_list.html /path/to/project/books/templates/books/publisher_list.html
.. highlightlang:: html+django
This template will be rendered against a context containing a variable called This template will be rendered against a context containing a variable called
``object_list`` that contains all the publisher objects. A very simple template ``object_list`` that contains all the publisher objects. A very simple template
might look like the following:: might look like the following:
.. code-block:: html+django
{% extends "base.html" %} {% extends "base.html" %}
@ -159,8 +168,6 @@ consider some of the common ways you might customize and extend generic views.
Making "friendly" template contexts Making "friendly" template contexts
----------------------------------- -----------------------------------
.. highlightlang:: python
You might have noticed that our sample publisher list template stores all the You might have noticed that our sample publisher list template stores all the
publishers in a variable named ``object_list``. While this works just fine, it publishers in a variable named ``object_list``. While this works just fine, it
isn't all that "friendly" to template authors: they have to "just know" that isn't all that "friendly" to template authors: they have to "just know" that
@ -198,15 +205,12 @@ provided by the generic view. For example, think of showing a list of
all the books on each publisher detail page. The all the books on each publisher detail page. The
:class:`~django.views.generic.detail.DetailView` generic view provides :class:`~django.views.generic.detail.DetailView` generic view provides
the publisher to the context, but how do we get additional information the publisher to the context, but how do we get additional information
in that template. in that template?
However, there is; you can subclass The answer is to subclass :class:`~django.views.generic.detail.DetailView`
:class:`~django.views.generic.detail.DetailView` and provide your own and provide your own implementation of the ``get_context_data`` method.
implementation of the ``get_context_data`` method. The default The default implementation simply adds the object being displayed to the
implementation of this that comes with template, but you can override it to send more::
:class:`~django.views.generic.detail.DetailView` simply adds in the
object being displayed to the template, but you can override it to send
more::
from django.views.generic import DetailView from django.views.generic import DetailView
from books.models import Publisher, Book from books.models import Publisher, Book
@ -224,10 +228,10 @@ more::
.. note:: .. note::
Generally, get_context_data will merge the context data of all parent Generally, ``get_context_data`` will merge the context data of all parent
classes with those of the current class. To preserve this behavior in your classes with those of the current class. To preserve this behavior in your
own classes where you want to alter the context, you should be sure to call own classes where you want to alter the context, you should be sure to call
get_context_data on the super class. When no two classes try to define the ``get_context_data`` on the super class. When no two classes try to define the
same key, this will give the expected results. However if any class same key, this will give the expected results. However if any class
attempts to override a key after parent classes have set it (after the call attempts to override a key after parent classes have set it (after the call
to super), any children of that class will also need to explicitly set it to super), any children of that class will also need to explicitly set it
@ -372,7 +376,7 @@ Performing extra work
The last common pattern we'll look at involves doing some extra work before The last common pattern we'll look at involves doing some extra work before
or after calling the generic view. or after calling the generic view.
Imagine we had a ``last_accessed`` field on our ``Author`` object that we were Imagine we had a ``last_accessed`` field on our ``Author`` model that we were
using to keep track of the last time anybody looked at that author:: using to keep track of the last time anybody looked at that author::
# models.py # models.py
@ -382,7 +386,7 @@ using to keep track of the last time anybody looked at that author::
salutation = models.CharField(max_length=10) salutation = models.CharField(max_length=10)
name = models.CharField(max_length=200) name = models.CharField(max_length=200)
email = models.EmailField() email = models.EmailField()
headshot = models.ImageField(upload_to='/tmp') headshot = models.ImageField(upload_to='author_headshots')
last_accessed = models.DateTimeField() last_accessed = models.DateTimeField()
The generic ``DetailView`` class, of course, wouldn't know anything about this The generic ``DetailView`` class, of course, wouldn't know anything about this

View File

@ -190,8 +190,8 @@ the foreign key relation to the model::
# ... # ...
In the view, ensure that you exclude ``created_by`` in the list of fields to In the view, ensure that you don't include ``created_by`` in the list of fields
edit, and override to edit, and override
:meth:`~django.views.generic.edit.ModelFormMixin.form_valid()` to add the user:: :meth:`~django.views.generic.edit.ModelFormMixin.form_valid()` to add the user::
# views.py # views.py
@ -256,3 +256,4 @@ works for AJAX requests as well as 'normal' form POSTs::
class AuthorCreate(AjaxableResponseMixin, CreateView): class AuthorCreate(AjaxableResponseMixin, CreateView):
model = Author model = Author
fields = ['name']

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