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/>
|
||||
batiste@dosimple.ch
|
||||
Batman
|
||||
Oliver Beattie <oliver@obeattie.com>
|
||||
Brian Beck <http://blog.brianbeck.com/>
|
||||
Shannon -jj Behrens <http://jjinux.blogspot.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>
|
||||
Michal Chruszcz <troll@pld-linux.org>
|
||||
Can Burak Çilingir <canburak@cs.bilgi.edu.tr>
|
||||
Andrew Clark <amclark7@gmail.com>
|
||||
Ian Clelland <clelland@gmail.com>
|
||||
Travis Cline <travis.cline@gmail.com>
|
||||
Russell Cloran <russell@rucus.net>
|
||||
|
@ -248,6 +250,7 @@ answer newbie questions, and generally made Django that much better:
|
|||
martin.glueck@gmail.com
|
||||
Ben Godfrey <http://aftnn.org>
|
||||
GomoX <gomo@datafull.com>
|
||||
Gil Gonçalves <lursty@gmail.com>
|
||||
Guilherme Mesquita Gondim <semente@taurinus.org>
|
||||
Mario Gonzalez <gonzalemario@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>
|
||||
Brant Harris
|
||||
Ronny Haryanto <http://ronny.haryan.to/>
|
||||
Axel Haustant <noirbizarre@gmail.com>
|
||||
Hawkeye
|
||||
Kent Hauser <kent@khauser.net>
|
||||
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/>
|
||||
Lee Reilly <lee@leereilly.net>
|
||||
Łukasz Rekucki <lrekucki@gmail.com>
|
||||
remco@diji.biz
|
||||
Remco Wendt <remco.wendt@gmail.com>
|
||||
Marc Remolt <m.remolt@webmasters.de>
|
||||
Bruno Renié <buburno@gmail.com>
|
||||
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>
|
||||
Dan Watson <http://danwatson.net/>
|
||||
Joel Watts <joel@joelwatts.com>
|
||||
Russ Webber
|
||||
Lakin Wecker <lakin@structuredabstraction.com>
|
||||
Chris Wesseling <Chris.Wesseling@cwi.nl>
|
||||
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):
|
||||
# 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
|
||||
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
|
||||
app_label = opts.app_label
|
||||
|
|
|
@ -4,18 +4,15 @@ from functools import partial, reduce, update_wrapper
|
|||
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
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.contrib.contenttypes.models import ContentType
|
||||
from django.contrib import messages
|
||||
from django.contrib.admin import widgets, helpers
|
||||
from django.contrib.admin.util import (unquote, flatten_fieldsets, get_deleted_objects,
|
||||
model_format_dict, NestedObjects, lookup_needs_distinct)
|
||||
from django.contrib.admin import validation
|
||||
from django.contrib.admin.templatetags.admin_static import static
|
||||
from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
|
||||
from django.contrib import messages
|
||||
from django.views.decorators.csrf import csrf_protect
|
||||
from django.contrib.auth import get_permission_codename
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import PermissionDenied, ValidationError, FieldError
|
||||
from django.core.paginator import Paginator
|
||||
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.fields import BLANK_CHOICE_DASH, FieldDoesNotExist
|
||||
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.shortcuts import get_object_or_404
|
||||
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 ungettext
|
||||
from django.utils.encoding import force_text
|
||||
from django.views.decorators.csrf import csrf_protect
|
||||
|
||||
|
||||
IS_POPUP_VAR = '_popup'
|
||||
|
||||
HORIZONTAL, VERTICAL = 1, 2
|
||||
# returns the <ul> class for a given radio_admin field
|
||||
|
@ -350,7 +354,8 @@ class BaseModelAdmin(six.with_metaclass(RenameBaseModelAdminMethods)):
|
|||
Can be overridden by the user in subclasses.
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
|
@ -364,7 +369,8 @@ class BaseModelAdmin(six.with_metaclass(RenameBaseModelAdminMethods)):
|
|||
request has permission to change *any* object of the given type.
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
|
@ -378,7 +384,9 @@ class BaseModelAdmin(six.with_metaclass(RenameBaseModelAdminMethods)):
|
|||
request has permission to delete *any* object of the given type.
|
||||
"""
|
||||
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):
|
||||
"Encapsulates all admin options and functionality for a given model."
|
||||
|
@ -660,8 +668,8 @@ class ModelAdmin(BaseModelAdmin):
|
|||
"""
|
||||
# If self.actions is explicitly set to None that means that we don't
|
||||
# want *any* actions enabled on this page.
|
||||
from django.contrib.admin.views.main import IS_POPUP_VAR
|
||||
if self.actions is None or IS_POPUP_VAR in request.GET:
|
||||
from django.contrib.admin.views.main import _is_changelist_popup
|
||||
if self.actions is None or _is_changelist_popup(request):
|
||||
return SortedDict()
|
||||
|
||||
actions = []
|
||||
|
@ -908,12 +916,11 @@ class ModelAdmin(BaseModelAdmin):
|
|||
msg_dict = {'name': force_text(opts.verbose_name), 'obj': force_text(obj)}
|
||||
# Here, we distinguish between different save types by checking for
|
||||
# the presence of keys in request.POST.
|
||||
if "_popup" in request.POST:
|
||||
return HttpResponse(
|
||||
'<!DOCTYPE html><html><head><title></title></head><body>'
|
||||
'<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script></body></html>' % \
|
||||
# escape() calls force_text.
|
||||
(escape(pk_value), escapejs(obj)))
|
||||
if IS_POPUP_VAR in request.POST:
|
||||
return SimpleTemplateResponse('admin/popup_response.html', {
|
||||
'pk_value': escape(pk_value),
|
||||
'obj': escapejs(obj)
|
||||
})
|
||||
|
||||
elif "_continue" in request.POST:
|
||||
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():
|
||||
action = action_form.cleaned_data['action']
|
||||
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
|
||||
# perform an action on it, so bail. Except we want to perform
|
||||
|
@ -1158,7 +1165,7 @@ class ModelAdmin(BaseModelAdmin):
|
|||
context = {
|
||||
'title': _('Add %s') % force_text(opts.verbose_name),
|
||||
'adminform': adminForm,
|
||||
'is_popup': "_popup" in request.REQUEST,
|
||||
'is_popup': IS_POPUP_VAR in request.REQUEST,
|
||||
'media': media,
|
||||
'inline_admin_formsets': inline_admin_formsets,
|
||||
'errors': helpers.AdminErrorList(form, formsets),
|
||||
|
@ -1251,7 +1258,7 @@ class ModelAdmin(BaseModelAdmin):
|
|||
'adminform': adminForm,
|
||||
'object_id': object_id,
|
||||
'original': obj,
|
||||
'is_popup': "_popup" in request.REQUEST,
|
||||
'is_popup': IS_POPUP_VAR in request.REQUEST,
|
||||
'media': media,
|
||||
'inline_admin_formsets': inline_admin_formsets,
|
||||
'errors': helpers.AdminErrorList(form, formsets),
|
||||
|
@ -1429,7 +1436,10 @@ class ModelAdmin(BaseModelAdmin):
|
|||
raise PermissionDenied
|
||||
|
||||
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)
|
||||
|
||||
|
@ -1456,7 +1466,9 @@ class ModelAdmin(BaseModelAdmin):
|
|||
(opts.app_label, opts.model_name),
|
||||
current_app=self.admin_site.name)
|
||||
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:
|
||||
post_url = reverse('admin:index',
|
||||
current_app=self.admin_site.name)
|
||||
|
@ -1522,6 +1534,7 @@ class ModelAdmin(BaseModelAdmin):
|
|||
"admin/object_history.html"
|
||||
], context, current_app=self.admin_site.name)
|
||||
|
||||
|
||||
class InlineModelAdmin(BaseModelAdmin):
|
||||
"""
|
||||
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
|
||||
# be able to do anything with the intermediate model.
|
||||
return self.has_change_permission(request)
|
||||
return request.user.has_perm(
|
||||
self.opts.app_label + '.' + self.opts.get_add_permission())
|
||||
return super(InlineModelAdmin, self).has_add_permission(request)
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
opts = self.opts
|
||||
|
@ -1677,8 +1689,8 @@ class InlineModelAdmin(BaseModelAdmin):
|
|||
if field.rel and field.rel.to != self.parent_model:
|
||||
opts = field.rel.to._meta
|
||||
break
|
||||
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):
|
||||
if self.opts.auto_created:
|
||||
|
@ -1687,8 +1699,7 @@ class InlineModelAdmin(BaseModelAdmin):
|
|||
# to have the change permission for the related model in order to
|
||||
# be able to do anything with the intermediate model.
|
||||
return self.has_change_permission(request, obj)
|
||||
return request.user.has_perm(
|
||||
self.opts.app_label + '.' + self.opts.get_delete_permission())
|
||||
return super(InlineModelAdmin, self).has_delete_permission(request, obj)
|
||||
|
||||
|
||||
class StackedInline(InlineModelAdmin):
|
||||
|
|
|
@ -32,9 +32,9 @@ function showRelatedObjectLookupPopup(triggeringLink) {
|
|||
name = id_to_windowname(name);
|
||||
var href;
|
||||
if (triggeringLink.href.search(/\?/) >= 0) {
|
||||
href = triggeringLink.href + '&pop=1';
|
||||
href = triggeringLink.href + '&_popup=1';
|
||||
} 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');
|
||||
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="submit" value="{% trans 'Search' %}" />
|
||||
{% 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 %}
|
||||
{% for pair in cl.params.items %}
|
||||
{% 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:" %}
|
||||
{% block reset_link %}
|
||||
{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb36=uid token=token %}
|
||||
{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
|
||||
{% endblock %}
|
||||
{% 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.db import models
|
||||
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.text import capfirst
|
||||
from django.utils.translation import ugettext as _
|
||||
|
@ -226,12 +226,12 @@ def items_for_result(cl, result, form):
|
|||
else:
|
||||
attr = pk
|
||||
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}>',
|
||||
table_tag,
|
||||
row_class,
|
||||
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 '',
|
||||
result_repr,
|
||||
table_tag)
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
from django.utils.http import urlencode
|
||||
|
||||
try:
|
||||
from urllib.parse import parse_qsl, urlparse, urlunparse
|
||||
except ImportError:
|
||||
|
@ -8,6 +6,7 @@ except ImportError:
|
|||
from django import template
|
||||
from django.contrib.admin.util import quote
|
||||
from django.core.urlresolvers import resolve, Resolver404
|
||||
from django.utils.http import urlencode
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
@ -47,7 +46,8 @@ def add_preserved_filters(context, url, popup=False):
|
|||
merged_qs.update(preserved_filters)
|
||||
|
||||
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)
|
||||
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.db import models
|
||||
from django.db.models.fields import FieldDoesNotExist
|
||||
from django.forms.models import (BaseModelForm, BaseModelFormSet, fields_for_model,
|
||||
_get_foreign_key)
|
||||
from django.forms.models import BaseModelForm, BaseModelFormSet, _get_foreign_key
|
||||
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.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,
|
||||
lookup_needs_distinct, prepare_lookup_value)
|
||||
|
||||
|
@ -26,7 +26,6 @@ ORDER_TYPE_VAR = 'ot'
|
|||
PAGE_VAR = 'p'
|
||||
SEARCH_VAR = 'q'
|
||||
TO_FIELD_VAR = 't'
|
||||
IS_POPUP_VAR = 'pop'
|
||||
ERROR_FLAG = 'e'
|
||||
|
||||
IGNORED_PARAMS = (
|
||||
|
@ -36,6 +35,29 @@ IGNORED_PARAMS = (
|
|||
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):
|
||||
renamed_methods = (
|
||||
('get_query_set', 'get_queryset', PendingDeprecationWarning),
|
||||
|
@ -67,7 +89,7 @@ class ChangeList(six.with_metaclass(RenameChangeListMethods)):
|
|||
except ValueError:
|
||||
self.page_num = 0
|
||||
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.params = dict(request.GET.items())
|
||||
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 import six
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
# Exclude methods starting with these strings from documentation
|
||||
MODEL_METHODS_EXCLUDE = ('_', 'add_', 'delete', 'save', 'set_')
|
||||
|
|
|
@ -108,7 +108,9 @@ def logout(request):
|
|||
|
||||
|
||||
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
|
||||
|
||||
try:
|
||||
|
@ -122,6 +124,10 @@ def get_user_model():
|
|||
|
||||
|
||||
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
|
||||
try:
|
||||
user_id = request.session[SESSION_KEY]
|
||||
|
@ -132,3 +138,10 @@ def get_user(request):
|
|||
except (KeyError, AssertionError):
|
||||
user = AnonymousUser()
|
||||
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.conf import settings
|
||||
from django.contrib import admin
|
||||
from django.contrib.admin.options import IS_POPUP_VAR
|
||||
from django.contrib.auth.forms import (UserCreationForm, UserChangeForm,
|
||||
AdminPasswordChangeForm)
|
||||
from django.contrib.auth.models import User, Group
|
||||
|
@ -143,7 +144,7 @@ class UserAdmin(admin.ModelAdmin):
|
|||
'adminForm': adminForm,
|
||||
'form_url': form_url,
|
||||
'form': form,
|
||||
'is_popup': '_popup' in request.REQUEST,
|
||||
'is_popup': IS_POPUP_VAR in request.REQUEST,
|
||||
'add': True,
|
||||
'change': False,
|
||||
'has_delete_permission': False,
|
||||
|
@ -170,7 +171,7 @@ class UserAdmin(admin.ModelAdmin):
|
|||
# button except in two scenarios:
|
||||
# * The user has pressed the 'Save and add another' button
|
||||
# * 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
|
||||
return super(UserAdmin, self).response_add(request, obj,
|
||||
post_url_continue)
|
||||
|
|
|
@ -6,8 +6,9 @@ from django import forms
|
|||
from django.forms.util import flatatt
|
||||
from django.template import loader
|
||||
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.http import int_to_base36
|
||||
from django.utils.http import urlsafe_base64_encode
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.text import capfirst
|
||||
from django.utils.translation import ugettext, ugettext_lazy as _
|
||||
|
@ -243,7 +244,7 @@ class PasswordResetForm(forms.Form):
|
|||
'email': user.email,
|
||||
'domain': domain,
|
||||
'site_name': site_name,
|
||||
'uid': int_to_base36(user.pk),
|
||||
'uid': urlsafe_base64_encode(force_bytes(user.pk)),
|
||||
'user': user,
|
||||
'token': token_generator.make_token(user),
|
||||
'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
|
||||
|
||||
import getpass
|
||||
import locale
|
||||
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.management.base import CommandError
|
||||
from django.db import DEFAULT_DB_ALIAS, router
|
||||
|
@ -17,10 +17,6 @@ from django.utils import six
|
|||
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):
|
||||
"""
|
||||
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)
|
||||
return builtin + custom
|
||||
|
||||
|
||||
def _get_builtin_permissions(opts):
|
||||
"""
|
||||
Returns (codename, name) for all autogenerated permissions.
|
||||
"""
|
||||
perms = []
|
||||
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)))
|
||||
return perms
|
||||
|
||||
|
||||
def _check_permission_clashing(custom, builtin, ctype):
|
||||
"""
|
||||
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__))
|
||||
pool.add(codename)
|
||||
|
||||
|
||||
def create_permissions(app, created_models, verbosity, db=DEFAULT_DB_ALIAS, **kwargs):
|
||||
try:
|
||||
get_model('auth', 'Permission')
|
||||
|
|
|
@ -170,7 +170,8 @@ class BaseUserManager(models.Manager):
|
|||
|
||||
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.
|
||||
"""
|
||||
|
@ -179,20 +180,20 @@ class UserManager(BaseUserManager):
|
|||
raise ValueError('The given username must be set')
|
||||
email = self.normalize_email(email)
|
||||
user = self.model(username=username, email=email,
|
||||
is_staff=False, is_active=True, is_superuser=False,
|
||||
last_login=now, date_joined=now, **extra_fields)
|
||||
|
||||
is_staff=is_staff, is_active=True,
|
||||
is_superuser=is_superuser, last_login=now,
|
||||
date_joined=now, **extra_fields)
|
||||
user.set_password(password)
|
||||
user.save(using=self._db)
|
||||
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):
|
||||
u = self.create_user(username, email, password, **extra_fields)
|
||||
u.is_staff = True
|
||||
u.is_active = True
|
||||
u.is_superuser = True
|
||||
u.save(using=self._db)
|
||||
return u
|
||||
return self._create_user(username, email, password, True, True,
|
||||
**extra_fields)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
|
@ -294,10 +295,12 @@ class PermissionsMixin(models.Model):
|
|||
groups = models.ManyToManyField(Group, verbose_name=_('groups'),
|
||||
blank=True, help_text=_('The groups this user belongs to. A user will '
|
||||
'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,
|
||||
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:
|
||||
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,
|
||||
AbstractUser,
|
||||
UserManager,
|
||||
PermissionsMixin
|
||||
PermissionsMixin,
|
||||
Group,
|
||||
Permission,
|
||||
)
|
||||
|
||||
|
||||
|
@ -81,6 +83,20 @@ class CustomUser(AbstractBaseUser):
|
|||
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,
|
||||
# adding a required date_of_birth field. This allows us to check for
|
||||
# any hard references to the name "User" in forms/handlers etc.
|
||||
|
@ -178,3 +194,7 @@ class CustomUserBadRequiredFields(AbstractBaseUser):
|
|||
|
||||
class Meta:
|
||||
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,
|
||||
UserManager)
|
||||
from django.contrib.auth.tests.utils import skipIfCustomUser
|
||||
from django.db.models.signals import post_save
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
from django.utils import six
|
||||
|
@ -140,3 +141,27 @@ class IsActiveTestCase(TestCase):
|
|||
user_fetched = UserModel._default_manager.get(pk=user.pk)
|
||||
# the attribute is always true for newly retrieved instance
|
||||
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.contrib.auth import authenticate
|
||||
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.test import TestCase
|
||||
from django.utils import timezone
|
||||
|
|
|
@ -13,8 +13,7 @@ from django.core import mail
|
|||
from django.core.urlresolvers import reverse, NoReverseMatch
|
||||
from django.http import QueryDict, HttpRequest
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.html import escape
|
||||
from django.utils.http import urlquote
|
||||
from django.utils.http import int_to_base36, urlsafe_base64_decode, urlquote
|
||||
from django.utils._os import upath
|
||||
from django.test import TestCase
|
||||
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.forms import (AuthenticationForm, PasswordChangeForm,
|
||||
SetPasswordForm, PasswordResetForm)
|
||||
SetPasswordForm)
|
||||
from django.contrib.auth.tests.utils import skipIfCustomUser
|
||||
from django.contrib.auth.views import login as login_view
|
||||
|
||||
|
@ -92,7 +91,7 @@ class AuthViewNamedURLTests(AuthViewsTestCase):
|
|||
('password_reset', [], {}),
|
||||
('password_reset_done', [], {}),
|
||||
('password_reset_confirm', [], {
|
||||
'uidb36': 'aaaaaaa',
|
||||
'uidb64': 'aaaaaaa',
|
||||
'token': '1111-aaaaa',
|
||||
}),
|
||||
('password_reset_complete', [], {}),
|
||||
|
@ -194,6 +193,16 @@ class PasswordResetTest(AuthViewsTestCase):
|
|||
# redirect to a 'complete' page:
|
||||
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):
|
||||
url, path = self._test_confirm_start()
|
||||
# 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):
|
||||
# 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/')
|
||||
self.assertContains(response, "The password reset link was invalid")
|
||||
|
||||
def test_confirm_overflow_user(self):
|
||||
# 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/')
|
||||
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/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'^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',
|
||||
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',
|
||||
dict(post_reset_redirect='password_reset')),
|
||||
(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),
|
||||
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_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'),
|
||||
# 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})/$',
|
||||
'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',
|
||||
name='password_reset_confirm'),
|
||||
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.http import HttpResponseRedirect, QueryDict
|
||||
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.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.cache import never_cache
|
||||
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
|
||||
@sensitive_post_parameters()
|
||||
@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',
|
||||
token_generator=default_token_generator,
|
||||
set_password_form=SetPasswordForm,
|
||||
|
@ -195,15 +196,15 @@ def password_reset_confirm(request, uidb36=None, token=None,
|
|||
form for entering a new password.
|
||||
"""
|
||||
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:
|
||||
post_reset_redirect = reverse('password_reset_complete')
|
||||
else:
|
||||
post_reset_redirect = resolve_url(post_reset_redirect)
|
||||
try:
|
||||
uid_int = base36_to_int(uidb36)
|
||||
user = UserModel._default_manager.get(pk=uid_int)
|
||||
except (ValueError, OverflowError, UserModel.DoesNotExist):
|
||||
uid = urlsafe_base64_decode(uidb64)
|
||||
user = UserModel._default_manager.get(pk=uid)
|
||||
except (TypeError, ValueError, OverflowError, UserModel.DoesNotExist):
|
||||
user = None
|
||||
|
||||
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,
|
||||
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,
|
||||
template_name='registration/password_reset_complete.html',
|
||||
|
|
|
@ -3,7 +3,7 @@ from __future__ import unicode_literals
|
|||
from django.contrib import admin
|
||||
from django.contrib.auth import get_user_model
|
||||
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.views.moderation import perform_flag, perform_approve, perform_delete
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import os
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.tests.utils import skipIfCustomUser
|
||||
from django.test import TestCase, Client
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import os
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import AnonymousUser, User
|
||||
from django.contrib.auth.tests.utils import skipIfCustomUser
|
||||
from django.template import Template, Context, TemplateSyntaxError
|
||||
|
|
|
@ -3,15 +3,11 @@ from __future__ import unicode_literals
|
|||
|
||||
import datetime
|
||||
import os
|
||||
import pickle
|
||||
import re
|
||||
import warnings
|
||||
|
||||
from django import http
|
||||
from django.conf import settings
|
||||
from django.contrib.formtools import preview, utils
|
||||
from django.test import TestCase
|
||||
from django.test.html import parse_html
|
||||
from django.test.utils import override_settings
|
||||
from django.utils._os import upath
|
||||
from django.utils import unittest
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import json
|
||||
|
||||
from django.test import TestCase
|
||||
from django.core import signing
|
||||
from django.core.exceptions import SuspiciousOperation
|
||||
|
|
|
@ -2,7 +2,6 @@ import logging
|
|||
|
||||
from django.forms.widgets import Textarea
|
||||
from django.template import loader, Context
|
||||
from django.templatetags.static import static
|
||||
from django.utils import six
|
||||
from django.utils import translation
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.db.backends.postgresql_psycopg2.creation import DatabaseCreation
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
from django.db.models import Aggregate
|
||||
from django.contrib.gis.db.models.sql import GeomField
|
||||
|
||||
class Collect(Aggregate):
|
||||
name = 'Collect'
|
||||
|
|
|
@ -4,7 +4,6 @@ import binascii
|
|||
import unittest
|
||||
|
||||
from django.contrib.gis import memoryview
|
||||
from django.utils import six
|
||||
from django.utils.unittest import skipUnless
|
||||
|
||||
from ..import HAS_GEOS
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import os
|
||||
from optparse import make_option
|
||||
from django.contrib.gis import gdal
|
||||
from django.core.management.base import LabelCommand, CommandError
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from datetime import date
|
||||
|
||||
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.test import TestCase
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
from django.contrib.auth.models import User
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core.cache import cache
|
||||
from django.db import models
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
from datetime import datetime
|
||||
from django.conf.urls import patterns, url
|
||||
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.contrib.sitemaps.tests.base import TestModel
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from __future__ import unicode_literals
|
||||
import warnings
|
||||
|
||||
from django.core.compat_checks import django_1_6_0
|
||||
from django.core.checks.compatibility import django_1_6_0
|
||||
|
||||
|
||||
COMPAT_CHECKS = [
|
|
@ -27,7 +27,7 @@ def check_test_runner():
|
|||
|
||||
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.
|
||||
"""
|
||||
checks = [
|
|
@ -1,7 +1,6 @@
|
|||
"""
|
||||
Global Django exception and warning classes.
|
||||
"""
|
||||
import logging
|
||||
from functools import reduce
|
||||
import operator
|
||||
|
||||
|
|
|
@ -51,6 +51,10 @@ def file_move_safe(old_file_name, new_file_name, chunk_size = 1024*64, allow_ove
|
|||
return
|
||||
|
||||
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)
|
||||
return
|
||||
except OSError:
|
||||
|
|
|
@ -103,6 +103,7 @@ class WSGIRequest(http.HttpRequest):
|
|||
content_length = 0
|
||||
self._stream = LimitedStream(self.environ['wsgi.input'], content_length)
|
||||
self._read_started = False
|
||||
self.resolver_match = None
|
||||
|
||||
def _is_secure(self):
|
||||
return 'wsgi.url_scheme' in self.environ and self.environ['wsgi.url_scheme'] == 'https'
|
||||
|
|
|
@ -3,13 +3,11 @@ import os
|
|||
import sys
|
||||
from optparse import OptionParser, NO_DEFAULT
|
||||
import imp
|
||||
import warnings
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.management.base import BaseCommand, CommandError, handle_default_options
|
||||
from django.core.management.color import color_style
|
||||
from django.utils.importlib import import_module
|
||||
from django.utils._os import upath
|
||||
from django.utils import six
|
||||
|
||||
# For backwards compatibility: get_version() used to be in this module.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from __future__ import unicode_literals
|
||||
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
|
||||
|
||||
|
|
@ -402,11 +402,11 @@ class Command(NoArgsCommand):
|
|||
if self.verbosity > 1:
|
||||
self.stdout.write("copying plural forms: %s\n" % m.group('value'))
|
||||
lines = []
|
||||
seen = False
|
||||
found = False
|
||||
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')
|
||||
seen = True
|
||||
found = True
|
||||
lines.append(line)
|
||||
msgs = '\n'.join(lines)
|
||||
break
|
||||
|
|
|
@ -105,7 +105,7 @@ class TemplateCommand(BaseCommand):
|
|||
base_name = '%s_name' % app_or_project
|
||||
base_subdir = '%s_template' % app_or_project
|
||||
base_directory = '%s_directory' % app_or_project
|
||||
if django.VERSION[-1] == 0:
|
||||
if django.VERSION[-2] != 'final':
|
||||
docs_version = 'dev'
|
||||
else:
|
||||
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.utils.encoding import smart_text
|
||||
from django.utils import six
|
||||
|
||||
class SerializerDoesNotExist(KeyError):
|
||||
|
|
|
@ -330,6 +330,15 @@ class BaseDatabaseWrapper(object):
|
|||
self._set_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):
|
||||
"""
|
||||
Raise an error if an atomic block is active.
|
||||
|
@ -623,6 +632,11 @@ class BaseDatabaseFeatures(object):
|
|||
# Does it support CHECK constraints?
|
||||
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):
|
||||
self.connection = connection
|
||||
|
||||
|
|
|
@ -762,9 +762,22 @@ class FormatStylePlaceholderCursor(object):
|
|||
self.cursor.arraysize = 100
|
||||
|
||||
def _format_params(self, 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):
|
||||
# Try dict handling; if that fails, treat as sequence
|
||||
if hasattr(params_list[0], 'keys'):
|
||||
sizes = {}
|
||||
for params in params_list:
|
||||
for k, value in params.items():
|
||||
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):
|
||||
|
@ -773,9 +786,13 @@ class FormatStylePlaceholderCursor(object):
|
|||
self.setinputsizes(*sizes)
|
||||
|
||||
def _param_generator(self, 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
|
||||
# it does want a trailing ';' but not a trailing '/'. However, these
|
||||
# characters must be included in the original query in case the query
|
||||
|
@ -785,10 +802,18 @@ class FormatStylePlaceholderCursor(object):
|
|||
if params is None:
|
||||
params = []
|
||||
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:
|
||||
params = self._format_params(params)
|
||||
# Handle params as sequence
|
||||
args = [(':arg%d' % i) for i in range(len(params))]
|
||||
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])
|
||||
try:
|
||||
return self.cursor.execute(query, self._param_generator(params))
|
||||
|
@ -799,22 +824,15 @@ class FormatStylePlaceholderCursor(object):
|
|||
raise
|
||||
|
||||
def executemany(self, query, params=None):
|
||||
# cx_Oracle doesn't support iterators, convert them to lists
|
||||
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):
|
||||
if not params:
|
||||
# No params given, nothing to do
|
||||
return None
|
||||
# cx_Oracle wants no trailing ';' for SQL statements. For PL/SQL, it
|
||||
# it does want a trailing ';' but not a trailing '/'. However, these
|
||||
# characters must be included in the original query in case the query
|
||||
# is being passed to SQL*Plus.
|
||||
if query.endswith(';') or query.endswith('/'):
|
||||
query = query[:-1]
|
||||
query = convert_unicode(query % tuple(args), self.charset)
|
||||
formatted = [self._format_params(i) for i in params]
|
||||
# uniform treatment for sequences and iterables
|
||||
params_iter = iter(params)
|
||||
query, firstparams = self._fix_for_params(query, next(params_iter))
|
||||
# we build a list of formatted params; as we're going to traverse it
|
||||
# more than once, we can't make it lazy by using a generator
|
||||
formatted = [firstparams]+[self._format_params(p) for p in params_iter]
|
||||
self._guess_input_sizes(formatted)
|
||||
try:
|
||||
return self.cursor.executemany(query,
|
||||
|
|
|
@ -6,7 +6,6 @@ Requires psycopg 2: http://initd.org/projects/psycopg2
|
|||
import logging
|
||||
import sys
|
||||
|
||||
from django.db import utils
|
||||
from django.db.backends import *
|
||||
from django.db.backends.postgresql_psycopg2.operations import DatabaseOperations
|
||||
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.functional import cached_property
|
||||
from django.utils.safestring import SafeText, SafeBytes
|
||||
from django.utils import six
|
||||
from django.utils.timezone import utc
|
||||
|
||||
try:
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import psycopg2.extensions
|
||||
|
||||
from django.db.backends.creation import BaseDatabaseCreation
|
||||
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)
|
||||
if lookup_type in ('iexact', 'contains', 'icontains', 'startswith',
|
||||
'istartswith', 'endswith', 'iendswith'):
|
||||
'istartswith', 'endswith', 'iendswith', 'regex', 'iregex'):
|
||||
lookup = "%s::text"
|
||||
|
||||
# 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.sql import aggregates
|
||||
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.safestring import SafeBytes
|
||||
from django.utils import six
|
||||
|
@ -103,6 +104,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
|||
supports_foreign_keys = False
|
||||
supports_check_constraints = False
|
||||
autocommits_when_autocommit_is_off = True
|
||||
supports_paramstyle_pyformat = False
|
||||
|
||||
@cached_property
|
||||
def uses_savepoints(self):
|
||||
|
@ -253,6 +255,9 @@ class DatabaseOperations(BaseDatabaseOperations):
|
|||
and gets dates and datetimes wrong.
|
||||
For consistency with other backends, coerce when required.
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
internal_type = field.get_internal_type()
|
||||
if internal_type == 'DecimalField':
|
||||
return util.typecast_decimal(field.format_number(value))
|
||||
|
@ -526,4 +531,4 @@ def _sqlite_format_dtdelta(dt, conn, days, secs, usecs):
|
|||
return str(dt)
|
||||
|
||||
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
|
||||
# object_name by default, but this can be overridden with the
|
||||
# "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):
|
||||
|
@ -824,7 +824,7 @@ class ReverseManyRelatedObjectsDescriptor(object):
|
|||
|
||||
class ForeignObjectRel(object):
|
||||
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:
|
||||
to._meta
|
||||
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.to = to
|
||||
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.multiple = True
|
||||
self.parent_link = parent_link
|
||||
|
@ -860,10 +861,10 @@ class ForeignObjectRel(object):
|
|||
|
||||
class ManyToOneRel(ForeignObjectRel):
|
||||
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__(
|
||||
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
|
||||
|
||||
def get_related_field(self):
|
||||
|
@ -883,21 +884,22 @@ class ManyToOneRel(ForeignObjectRel):
|
|||
|
||||
class OneToOneRel(ManyToOneRel):
|
||||
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,
|
||||
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
|
||||
|
||||
|
||||
class ManyToManyRel(object):
|
||||
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:
|
||||
raise ValueError("Can't supply a through model and db_constraint=False")
|
||||
self.to = to
|
||||
self.related_name = related_name
|
||||
self.related_query_name = related_query_name
|
||||
if limit_choices_to is None:
|
||||
limit_choices_to = {}
|
||||
self.limit_choices_to = limit_choices_to
|
||||
|
@ -931,6 +933,7 @@ class ForeignObject(RelatedField):
|
|||
kwargs['rel'] = ForeignObjectRel(
|
||||
self, to,
|
||||
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),
|
||||
parent_link=kwargs.pop('parent_link', False),
|
||||
on_delete=kwargs.pop('on_delete', CASCADE),
|
||||
|
@ -1141,6 +1144,7 @@ class ForeignKey(ForeignObject):
|
|||
kwargs['rel'] = rel_class(
|
||||
self, to, to_field,
|
||||
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),
|
||||
parent_link=kwargs.pop('parent_link', False),
|
||||
on_delete=kwargs.pop('on_delete', CASCADE),
|
||||
|
@ -1371,6 +1375,7 @@ class ManyToManyField(RelatedField):
|
|||
kwargs['verbose_name'] = kwargs.get('verbose_name', None)
|
||||
kwargs['rel'] = ManyToManyRel(to,
|
||||
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),
|
||||
symmetrical=kwargs.pop('symmetrical', to == RECURSIVE_RELATIONSHIP_CONSTANT),
|
||||
through=kwargs.pop('through', None),
|
||||
|
@ -1388,6 +1393,7 @@ class ManyToManyField(RelatedField):
|
|||
# Handle the simpler arguments
|
||||
if self.rel.db_constraint is not True:
|
||||
kwargs['db_constraint'] = self.db_constraint
|
||||
if "help_text" in kwargs:
|
||||
del kwargs['help_text']
|
||||
# Rel needs more work.
|
||||
rel = self.rel
|
||||
|
|
|
@ -422,12 +422,36 @@ class Options(object):
|
|||
return cache
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
def get_all_related_objects(self, local_only=False, include_hidden=False,
|
||||
|
|
|
@ -1445,7 +1445,10 @@ class RawQuerySet(object):
|
|||
yield instance
|
||||
|
||||
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):
|
||||
return list(self)[k]
|
||||
|
|
|
@ -171,6 +171,26 @@ def clean_savepoints(using=None):
|
|||
"""
|
||||
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 #
|
||||
#################################
|
||||
|
|
|
@ -370,13 +370,7 @@ class DecimalField(IntegerField):
|
|||
|
||||
def widget_attrs(self, widget):
|
||||
attrs = super(DecimalField, self).widget_attrs(widget)
|
||||
if isinstance(widget, NumberInput):
|
||||
if self.max_digits is not None:
|
||||
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:
|
||||
if isinstance(widget, NumberInput) and self.decimal_places:
|
||||
attrs['step'] = '0.%s1' % ('0' * (self.decimal_places - 1))
|
||||
return attrs
|
||||
|
||||
|
|
|
@ -4,8 +4,9 @@ from django.core.exceptions import ValidationError
|
|||
from django.forms import Form
|
||||
from django.forms.fields import IntegerField, BooleanField
|
||||
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.functional import cached_property
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils import six
|
||||
from django.utils.six.moves import xrange
|
||||
|
@ -55,8 +56,6 @@ class BaseFormSet(object):
|
|||
self.error_class = error_class
|
||||
self._errors = None
|
||||
self._non_form_errors = None
|
||||
# construct the forms in the formset
|
||||
self._construct_forms()
|
||||
|
||||
def __str__(self):
|
||||
return self.as_table()
|
||||
|
@ -125,12 +124,14 @@ class BaseFormSet(object):
|
|||
initial_forms = len(self.initial) if self.initial else 0
|
||||
return initial_forms
|
||||
|
||||
def _construct_forms(self):
|
||||
# instantiate all the forms and put them in self.forms
|
||||
self.forms = []
|
||||
@cached_property
|
||||
def forms(self):
|
||||
"""
|
||||
Instantiate forms at first property access.
|
||||
"""
|
||||
# DoS protection is included in total_form_count()
|
||||
for i in xrange(self.total_form_count()):
|
||||
self.forms.append(self._construct_form(i))
|
||||
forms = [self._construct_form(i) for i in xrange(self.total_form_count())]
|
||||
return forms
|
||||
|
||||
def _construct_form(self, i, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -3,7 +3,6 @@ from __future__ import unicode_literals
|
|||
from django.conf import settings
|
||||
from django.utils.html import format_html, format_html_join
|
||||
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.translation import ugettext_lazy as _
|
||||
from django.utils import six
|
||||
|
|
|
@ -5,7 +5,6 @@ HTML Widget classes
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import copy
|
||||
import datetime
|
||||
from itertools import chain
|
||||
try:
|
||||
from urllib.parse import urljoin
|
||||
|
@ -16,8 +15,8 @@ import warnings
|
|||
from django.conf import settings
|
||||
from django.forms.util import flatatt, to_current_timezone
|
||||
from django.utils.datastructures import MultiValueDict, MergeDict
|
||||
from django.utils.html import conditional_escape, format_html, format_html_join
|
||||
from django.utils.translation import ugettext, ugettext_lazy
|
||||
from django.utils.html import conditional_escape, format_html
|
||||
from django.utils.translation import ugettext_lazy
|
||||
from django.utils.encoding import force_text, python_2_unicode_compatible
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils import datetime_safe, formats, six
|
||||
|
|
|
@ -39,6 +39,10 @@ class HttpRequest(object):
|
|||
_upload_handlers = []
|
||||
|
||||
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.path = ''
|
||||
self.path_info = ''
|
||||
|
|
|
@ -6,10 +6,8 @@ against request forgeries from other sites.
|
|||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import hashlib
|
||||
import logging
|
||||
import re
|
||||
import random
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import get_callable
|
||||
|
|
|
@ -1101,6 +1101,7 @@ class Library(object):
|
|||
# for decorators that need it e.g. stringfilter
|
||||
if hasattr(filter_func, "_decorated_function"):
|
||||
setattr(filter_func._decorated_function, attr, value)
|
||||
filter_func._filter_name = name
|
||||
return filter_func
|
||||
else:
|
||||
raise InvalidTemplateLibrary("Unsupported arguments to "
|
||||
|
|
|
@ -3,7 +3,6 @@ from __future__ import unicode_literals
|
|||
|
||||
import re
|
||||
import random as random_module
|
||||
import unicodedata
|
||||
from decimal import Decimal, InvalidOperation, Context, ROUND_HALF_UP
|
||||
from functools import wraps
|
||||
from pprint import pformat
|
||||
|
|
|
@ -665,8 +665,9 @@ def do_filter(parser, token):
|
|||
_, rest = token.contents.split(None, 1)
|
||||
filter_expr = parser.compile_filter("var|%s" % (rest))
|
||||
for func, unused in filter_expr.filters:
|
||||
if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'):
|
||||
raise TemplateSyntaxError('"filter %s" is not permitted. Use the "autoescape" tag instead.' % func.__name__)
|
||||
filter_name = getattr(func, '_filter_name', None)
|
||||
if filter_name in ('escape', 'safe'):
|
||||
raise TemplateSyntaxError('"filter %s" is not permitted. Use the "autoescape" tag instead.' % filter_name)
|
||||
nodelist = parser.parse(('endfilter',))
|
||||
parser.delete_first_token()
|
||||
return FilterNode(filter_expr, nodelist)
|
||||
|
|
|
@ -10,6 +10,7 @@ from django.conf import settings, UserSettingsHolder
|
|||
from django.core import mail
|
||||
from django.core.signals import request_started
|
||||
from django.db import reset_queries
|
||||
from django.http import request
|
||||
from django.template import Template, loader, TemplateDoesNotExist
|
||||
from django.template.loaders import cached
|
||||
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.
|
||||
- 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
|
||||
|
||||
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._original_allowed_hosts = settings.ALLOWED_HOSTS
|
||||
request._original_allowed_hosts = settings.ALLOWED_HOSTS
|
||||
settings.ALLOWED_HOSTS = ['*']
|
||||
|
||||
mail.outbox = []
|
||||
|
@ -108,14 +112,14 @@ def teardown_test_environment():
|
|||
- Restoring the email sending functions
|
||||
|
||||
"""
|
||||
Template._render = Template.original_render
|
||||
del Template.original_render
|
||||
Template._render = Template._original_render
|
||||
del Template._original_render
|
||||
|
||||
settings.EMAIL_BACKEND = mail.original_email_backend
|
||||
del mail.original_email_backend
|
||||
settings.EMAIL_BACKEND = mail._original_email_backend
|
||||
del mail._original_email_backend
|
||||
|
||||
settings.ALLOWED_HOSTS = settings._original_allowed_hosts
|
||||
del settings._original_allowed_hosts
|
||||
settings.ALLOWED_HOSTS = request._original_allowed_hosts
|
||||
del request._original_allowed_hosts
|
||||
|
||||
del mail.outbox
|
||||
|
||||
|
@ -207,7 +211,6 @@ class override_settings(object):
|
|||
"""
|
||||
def __init__(self, **kwargs):
|
||||
self.options = kwargs
|
||||
self.wrapped = settings._wrapped
|
||||
|
||||
def __enter__(self):
|
||||
self.enable()
|
||||
|
@ -246,6 +249,7 @@ class override_settings(object):
|
|||
override = UserSettingsHolder(settings._wrapped)
|
||||
for key, new_value in self.options.items():
|
||||
setattr(override, key, new_value)
|
||||
self.wrapped = settings._wrapped
|
||||
settings._wrapped = override
|
||||
for key, new_value in self.options.items():
|
||||
setting_changed.send(sender=settings._wrapped.__class__,
|
||||
|
@ -253,6 +257,7 @@ class override_settings(object):
|
|||
|
||||
def disable(self):
|
||||
settings._wrapped = self.wrapped
|
||||
del self.wrapped
|
||||
for key in self.options:
|
||||
new_value = getattr(settings, key, None)
|
||||
setting_changed.send(sender=settings._wrapped.__class__,
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
import string
|
||||
try:
|
||||
from urllib.parse import quote, urlsplit, urlunsplit
|
||||
except ImportError: # Python 2
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import base64
|
||||
import calendar
|
||||
import datetime
|
||||
import re
|
||||
|
@ -11,7 +12,7 @@ except ImportError: # Python 2
|
|||
import urlparse
|
||||
urllib_parse.urlparse = urlparse.urlparse
|
||||
|
||||
|
||||
from binascii import Error as BinasciiError
|
||||
from email.utils import formatdate
|
||||
|
||||
from django.utils.datastructures import MultiValueDict
|
||||
|
@ -202,6 +203,24 @@ def int_to_base36(i):
|
|||
factor -= 1
|
||||
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):
|
||||
"""
|
||||
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 unicodedata
|
||||
import warnings
|
||||
from gzip import GzipFile
|
||||
from io import BytesIO
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ import sys
|
|||
import types
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.http import (HttpResponse, HttpResponseServerError,
|
||||
HttpResponseNotFound, HttpRequest, build_request_repr)
|
||||
from django.template import Template, Context, TemplateDoesNotExist
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import warnings
|
||||
|
||||
from django.middleware.csrf import CsrfViewMiddleware, get_token
|
||||
from django.utils.decorators import decorator_from_middleware, available_attrs
|
||||
from functools import wraps
|
||||
|
|
|
@ -17,6 +17,7 @@ class FormMixin(ContextMixin):
|
|||
initial = {}
|
||||
form_class = None
|
||||
success_url = None
|
||||
prefix = None
|
||||
|
||||
def get_initial(self):
|
||||
"""
|
||||
|
@ -24,6 +25,12 @@ class FormMixin(ContextMixin):
|
|||
"""
|
||||
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):
|
||||
"""
|
||||
Returns the form class to use in this view
|
||||
|
@ -40,7 +47,11 @@ class FormMixin(ContextMixin):
|
|||
"""
|
||||
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'):
|
||||
kwargs.update({
|
||||
'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.
|
||||
"""
|
||||
fields = None
|
||||
|
||||
def get_form_class(self):
|
||||
"""
|
||||
|
@ -98,13 +110,12 @@ class ModelFormMixin(FormMixin, SingleObjectMixin):
|
|||
# from that
|
||||
model = self.get_queryset().model
|
||||
|
||||
fields = getattr(self, 'fields', None)
|
||||
if fields is None:
|
||||
if self.fields is None:
|
||||
warnings.warn("Using ModelFormMixin (base class of %s) without "
|
||||
"the 'fields' attribute is deprecated." % self.__class__.__name__,
|
||||
PendingDeprecationWarning)
|
||||
|
||||
return model_forms.modelform_factory(model, fields=fields)
|
||||
return model_forms.modelform_factory(model, fields=self.fields)
|
||||
|
||||
def get_form_kwargs(self):
|
||||
"""
|
||||
|
|
|
@ -55,7 +55,7 @@ copyright = 'Django Software Foundation and contributors'
|
|||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '1.6'
|
||||
version = '1.7'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
try:
|
||||
from django import VERSION, get_version
|
||||
|
@ -71,7 +71,7 @@ else:
|
|||
release = django_release()
|
||||
|
||||
# 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
|
||||
# for a list of supported languages.
|
||||
|
|
|
@ -16,9 +16,8 @@ How do I get started?
|
|||
What are Django's prerequisites?
|
||||
--------------------------------
|
||||
|
||||
Django requires Python, specifically Python 2.6.5 - 2.7.x. No other Python
|
||||
libraries are required for basic Django usage. Django 1.5 also has
|
||||
experimental support for Python 3.2.3 and above.
|
||||
Django requires Python, specifically Python 2.6.5 - 2.7.x, or 3.2.3 and above.
|
||||
No other Python libraries are required for basic Django usage.
|
||||
|
||||
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
|
||||
|
@ -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
|
||||
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
|
||||
use a newer version of Python you will also have access to some APIs that
|
||||
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
|
||||
own version requirements.
|
||||
|
||||
All else being equal, we recommend that you use the latest 2.x release
|
||||
(currently Python 2.7). This will let you take advantage of the numerous
|
||||
improvements and optimizations to the Python language since version 2.6.
|
||||
|
||||
Generally speaking, we don't recommend running Django on Python 3 yet; see
|
||||
below for more.
|
||||
All else being equal, we recommend that you use the latest 2.7 or 3.x release.
|
||||
This will let you take advantage of the numerous improvements and optimizations
|
||||
to the Python language since version 2.6.
|
||||
|
||||
What Python version can I use with Django?
|
||||
------------------------------------------
|
||||
|
@ -77,15 +73,12 @@ Django version Python versions
|
|||
Can I use Django with Python 3?
|
||||
-------------------------------
|
||||
|
||||
Django 1.5 introduces experimental support for Python 3.2.3 and above. However,
|
||||
we don't yet suggest that you use Django and Python 3 in production.
|
||||
Yes, you can!
|
||||
|
||||
Python 3 support should be considered a "preview". It's offered to bootstrap
|
||||
the transition of the Django ecosystem to Python 3, and to help you start
|
||||
porting your apps for future Python 3 compatibility. But we're not yet
|
||||
confident enough to promise stability in production.
|
||||
Django 1.5 introduced experimental support for Python 3.2.3 and above.
|
||||
|
||||
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)?
|
||||
-------------------------------------------------------------------
|
||||
|
|
|
@ -31,7 +31,7 @@ Our example object
|
|||
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:
|
||||
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
|
||||
are traditionally called *north*, *east*, *south* and *west*. Our class looks
|
||||
something like this::
|
||||
|
|
|
@ -25,7 +25,8 @@ Basic configuration
|
|||
===================
|
||||
|
||||
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
|
||||
|
||||
|
@ -35,7 +36,7 @@ Once you've got mod_wsgi installed and activated, edit your Apache server's
|
|||
<Directory /path/to/mysite.com/mysite>
|
||||
<Files wsgi.py>
|
||||
Order deny,allow
|
||||
Allow from all
|
||||
Require all granted
|
||||
</Files>
|
||||
</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
|
||||
in ``my_app/static/`` (rather than creating another ``my_app``
|
||||
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
|
||||
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,
|
||||
|
|
|
@ -255,7 +255,11 @@ Keywords
|
|||
~~~~~~~~
|
||||
|
||||
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:
|
||||
|
||||
|
|
|
@ -326,6 +326,14 @@ these changes.
|
|||
remove calls to this method, and instead ensure that their auth related views
|
||||
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
|
||||
---
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ After the previous tutorials, our project should look like this::
|
|||
admin.py
|
||||
models.py
|
||||
static/
|
||||
polls
|
||||
polls/
|
||||
images/
|
||||
background.gif
|
||||
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::
|
||||
|
||||
from django.http import HttpResponse
|
||||
from django.template import Context, loader
|
||||
from django.template import RequestContext, loader
|
||||
|
||||
from polls.models import Poll
|
||||
|
||||
def index(request):
|
||||
latest_poll_list = Poll.objects.order_by('-pub_date')[:5]
|
||||
template = loader.get_template('polls/index.html')
|
||||
context = Context({
|
||||
context = RequestContext(request, {
|
||||
'latest_poll_list': latest_poll_list,
|
||||
})
|
||||
return HttpResponse(template.render(context))
|
||||
|
@ -377,7 +377,7 @@ rewritten::
|
|||
return render(request, 'polls/index.html', context)
|
||||
|
||||
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
|
||||
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.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.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.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`]
|
||||
|
@ -177,11 +178,13 @@ CreateView
|
|||
|
||||
* :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.edit.ModelFormMixin.fields`
|
||||
* :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.edit.FormMixin.initial` [:meth:`~django.views.generic.edit.FormMixin.get_initial`]
|
||||
* :attr:`~django.views.generic.detail.SingleObjectMixin.model`
|
||||
* :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.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`]
|
||||
|
@ -216,11 +219,13 @@ UpdateView
|
|||
|
||||
* :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.edit.ModelFormMixin.fields`
|
||||
* :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.edit.FormMixin.initial` [:meth:`~django.views.generic.edit.FormMixin.get_initial`]
|
||||
* :attr:`~django.views.generic.detail.SingleObjectMixin.model`
|
||||
* :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.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`]
|
||||
|
|
|
@ -35,6 +35,12 @@ FormMixin
|
|||
|
||||
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()
|
||||
|
||||
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``
|
||||
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()
|
||||
|
||||
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/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'),
|
||||
|
||||
.. 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
|
||||
the URLs starting with ``^admin/`` before the line that includes the admin app
|
||||
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
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^sitemap.xml$',
|
||||
url(r'^sitemap\.xml$',
|
||||
cache_page(86400)(sitemaps_views.index),
|
||||
{'sitemaps': sitemaps, 'sitemap_url_name': 'sitemaps'}),
|
||||
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
|
||||
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:
|
||||
|
||||
Parameters not quoted in ``connection.queries``
|
||||
|
|
|
@ -1083,6 +1083,22 @@ define the details of how the relation works.
|
|||
|
||||
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
|
||||
|
||||
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+')
|
||||
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
|
||||
|
||||
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
|
||||
a :exc:`~django.core.exceptions.DoesNotExist` exception when appropriate.
|
||||
|
||||
Both methods accept optional keyword arguments, which should be in the format
|
||||
described in :ref:`Field lookups <field-lookups>`.
|
||||
Both of these methods will perform their queries using the default
|
||||
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
|
||||
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
|
||||
configuration method described in :setting:`LOGGING_CONFIG`.
|
||||
|
||||
The default logging configuration passes HTTP 500 server errors to an
|
||||
email log handler; all other log messages are given to a NullHandler.
|
||||
Among other things, the default logging configuration passes HTTP 500 server
|
||||
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
|
||||
|
||||
|
@ -2564,7 +2570,9 @@ various locations.
|
|||
The default will find files stored in the :setting:`STATICFILES_DIRS` setting
|
||||
(using ``django.contrib.staticfiles.finders.FileSystemFinder``) and in a
|
||||
``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:
|
||||
``django.contrib.staticfiles.finders.DefaultStorageFinder``. If added to
|
||||
|
|
|
@ -255,7 +255,7 @@ Arguments sent with this signal:
|
|||
|
||||
``pk_set``
|
||||
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.
|
||||
|
||||
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::
|
||||
|
||||
>>> p = Pizza.object.create(...)
|
||||
>>> p = Pizza.objects.create(...)
|
||||
>>> t = Topping.objects.create(...)
|
||||
>>> p.toppings.add(t)
|
||||
|
||||
|
@ -307,7 +307,7 @@ Argument Value
|
|||
``model`` ``Topping`` (the class of the objects added to the
|
||||
``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)
|
||||
============== ============================================================
|
||||
|
@ -334,7 +334,7 @@ Argument Value
|
|||
``model`` ``Pizza`` (the class of the objects removed from the
|
||||
``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)
|
||||
|
||||
``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
|
||||
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``
|
||||
===============================
|
||||
|
||||
|
|
|
@ -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
|
||||
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 version of Django.
|
||||
|
||||
|
@ -330,6 +330,19 @@ Minor features
|
|||
behavior of clearing filters by setting the
|
||||
: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
|
||||
=====================================
|
||||
|
||||
|
@ -649,6 +662,59 @@ rely on the previous URLs. If you want to revert to the original behavior you
|
|||
can set the
|
||||
: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
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
|
@ -725,6 +791,12 @@ Miscellaneous
|
|||
returned ``False`` for blank passwords. This has been corrected in this
|
||||
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
|
||||
==========================
|
||||
|
||||
|
@ -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
|
||||
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``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -817,7 +817,7 @@ patterns.
|
|||
|
||||
* ``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.
|
||||
|
||||
|
@ -826,7 +826,12 @@ patterns.
|
|||
.. code-block:: html+django
|
||||
|
||||
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
|
||||
single line plain text string.
|
||||
|
@ -846,7 +851,7 @@ patterns.
|
|||
Defaults to :file:`registration/password_reset_done.html` if not
|
||||
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.
|
||||
|
||||
|
@ -854,7 +859,12 @@ patterns.
|
|||
|
||||
**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
|
||||
``None``.
|
||||
|
@ -877,8 +887,8 @@ patterns.
|
|||
* ``form``: The form (see ``set_password_form`` above) for setting the
|
||||
new user's password.
|
||||
|
||||
* ``validlink``: Boolean, True if the link (combination of uidb36 and
|
||||
token) is valid or unused yet.
|
||||
* ``validlink``: Boolean, True if the link (combination of ``uidb64`` and
|
||||
``token``) is valid or unused yet.
|
||||
|
||||
.. function:: password_reset_complete(request[,template_name])
|
||||
|
||||
|
|
|
@ -92,6 +92,15 @@ We'll be using these models::
|
|||
def __unicode__(self):
|
||||
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):
|
||||
title = models.CharField(max_length=100)
|
||||
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:
|
||||
/path/to/project/books/templates/books/publisher_list.html
|
||||
|
||||
.. highlightlang:: html+django
|
||||
|
||||
This template will be rendered against a context containing a variable called
|
||||
``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" %}
|
||||
|
||||
|
@ -159,8 +168,6 @@ consider some of the common ways you might customize and extend generic views.
|
|||
Making "friendly" template contexts
|
||||
-----------------------------------
|
||||
|
||||
.. highlightlang:: python
|
||||
|
||||
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
|
||||
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
|
||||
:class:`~django.views.generic.detail.DetailView` generic view provides
|
||||
the publisher to the context, but how do we get additional information
|
||||
in that template.
|
||||
in that template?
|
||||
|
||||
However, there is; you can subclass
|
||||
:class:`~django.views.generic.detail.DetailView` and provide your own
|
||||
implementation of the ``get_context_data`` method. The default
|
||||
implementation of this that comes with
|
||||
:class:`~django.views.generic.detail.DetailView` simply adds in the
|
||||
object being displayed to the template, but you can override it to send
|
||||
more::
|
||||
The answer is to subclass :class:`~django.views.generic.detail.DetailView`
|
||||
and provide your own implementation of the ``get_context_data`` method.
|
||||
The default implementation simply adds the object being displayed to the
|
||||
template, but you can override it to send more::
|
||||
|
||||
from django.views.generic import DetailView
|
||||
from books.models import Publisher, Book
|
||||
|
@ -224,10 +228,10 @@ more::
|
|||
|
||||
.. 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
|
||||
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
|
||||
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
|
||||
|
@ -372,7 +376,7 @@ Performing extra work
|
|||
The last common pattern we'll look at involves doing some extra work before
|
||||
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::
|
||||
|
||||
# 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)
|
||||
name = models.CharField(max_length=200)
|
||||
email = models.EmailField()
|
||||
headshot = models.ImageField(upload_to='/tmp')
|
||||
headshot = models.ImageField(upload_to='author_headshots')
|
||||
last_accessed = models.DateTimeField()
|
||||
|
||||
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
|
||||
edit, and override
|
||||
In the view, ensure that you don't include ``created_by`` in the list of fields
|
||||
to edit, and override
|
||||
:meth:`~django.views.generic.edit.ModelFormMixin.form_valid()` to add the user::
|
||||
|
||||
# views.py
|
||||
|
@ -256,3 +256,4 @@ works for AJAX requests as well as 'normal' form POSTs::
|
|||
|
||||
class AuthorCreate(AjaxableResponseMixin, CreateView):
|
||||
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