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:
commit
7a47ba6f6a
7
AUTHORS
7
AUTHORS
|
@ -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>
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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>
|
|
@ -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 %}
|
||||||
|
|
|
@ -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 }}
|
||||||
|
|
||||||
|
|
|
@ -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, '{0}'); return false;"', result_id)
|
||||||
if cl.is_popup else '',
|
if cl.is_popup else '',
|
||||||
result_repr,
|
result_repr,
|
||||||
table_tag)
|
table_tag)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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_')
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
{{ protocol }}://{{ domain }}/reset/{{ uid }}-{{ token }}/
|
{{ protocol }}://{{ domain }}/reset/{{ uid }}/{{ token }}/
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
|
|
||||||
|
|
|
@ -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"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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'),
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 = [
|
|
@ -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 = [
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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 #
|
||||||
#################################
|
#################################
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 = ''
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 "
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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__,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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)?
|
||||||
-------------------------------------------------------------------
|
-------------------------------------------------------------------
|
||||||
|
|
|
@ -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::
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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:
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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``).
|
||||||
|
|
||||||
|
|
|
@ -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`]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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).
|
||||||
|
|
|
@ -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$',
|
||||||
|
|
|
@ -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``
|
||||||
|
|
|
@ -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`.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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``
|
||||||
===============================
|
===============================
|
||||||
|
|
||||||
|
|
|
@ -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``
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -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])
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
Loading…
Reference in New Issue