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

Conflicts:
	django/core/management/commands/flush.py
	django/core/management/commands/syncdb.py
	django/db/models/loading.py
	docs/internals/deprecation.txt
	docs/ref/django-admin.txt
	docs/releases/1.7.txt
This commit is contained in:
Andrew Godwin 2013-08-09 14:17:30 +01:00
commit de64c4d6e9
489 changed files with 3840 additions and 1593 deletions

View File

@ -161,6 +161,7 @@ answer newbie questions, and generally made Django that much better:
Paul Collier <paul@paul-collier.com>
Paul Collins <paul.collins.iii@gmail.com>
Robert Coup
Alex Couper <http://alexcouper.com/>
Deric Crago <deric.crago@gmail.com>
Brian Fabian Crain <http://www.bfc.do/>
David Cramer <dcramer@gmail.com>
@ -416,6 +417,7 @@ answer newbie questions, and generally made Django that much better:
Zain Memon
Christian Metts
michal@plovarna.cz
Justin Michalicek <jmichalicek@gmail.com>
Slawek Mikula <slawek dot mikula at gmail dot com>
Katie Miller <katie@sub50.com>
Shawn Milochik <shawn@milochik.com>
@ -542,6 +544,7 @@ answer newbie questions, and generally made Django that much better:
smurf@smurf.noris.de
Vsevolod Solovyov
George Song <george@damacy.net>
Jimmy Song <jaejoon@gmail.com>
sopel
Leo Soto <leo.soto@gmail.com>
Thomas Sorrel

View File

@ -6,6 +6,7 @@ variable, and then from django.conf.global_settings; see the global settings fil
a list of all possible variables.
"""
import importlib
import logging
import os
import sys
@ -15,7 +16,6 @@ import warnings
from django.conf import global_settings
from django.core.exceptions import ImproperlyConfigured
from django.utils.functional import LazyObject, empty
from django.utils import importlib
from django.utils.module_loading import import_by_path
from django.utils import six
@ -107,6 +107,9 @@ class BaseSettings(object):
elif name == "ALLOWED_INCLUDE_ROOTS" and isinstance(value, six.string_types):
raise ValueError("The ALLOWED_INCLUDE_ROOTS setting must be set "
"to a tuple, not a string.")
elif name == "INSTALLED_APPS" and len(value) != len(set(value)):
raise ImproperlyConfigured("The INSTALLED_APPS setting must contain unique values.")
object.__setattr__(self, name, value)

View File

@ -1,7 +1,8 @@
from importlib import import_module
from django.core.urlresolvers import (RegexURLPattern,
RegexURLResolver, LocaleRegexURLResolver)
from django.core.exceptions import ImproperlyConfigured
from django.utils.importlib import import_module
from django.utils import six

View File

@ -17,8 +17,8 @@ def autodiscover():
"""
import copy
from importlib import import_module
from django.conf import settings
from django.utils.importlib import import_module
from django.utils.module_loading import module_has_submodule
for app in settings.INSTALLED_APPS:

View File

@ -87,7 +87,7 @@ class SimpleListFilter(ListFilter):
def lookups(self, request, model_admin):
"""
Must be overriden to return a list of tuples (value, verbose value)
Must be overridden to return a list of tuples (value, verbose value)
"""
raise NotImplementedError

View File

@ -125,14 +125,16 @@ class AdminField(object):
contents = conditional_escape(force_text(self.field.label))
if self.is_checkbox:
classes.append('vCheckboxLabel')
else:
contents += ':'
if self.field.field.required:
classes.append('required')
if not self.is_first:
classes.append('inline')
attrs = {'class': ' '.join(classes)} if classes else {}
return self.field.label_tag(contents=mark_safe(contents), attrs=attrs)
# checkboxes should not have a label suffix as the checkbox appears
# to the left of the label.
return self.field.label_tag(contents=mark_safe(contents), attrs=attrs,
label_suffix='' if self.is_checkbox else None)
def errors(self):
return mark_safe(self.field.errors.as_ul())

View File

@ -4,7 +4,7 @@ from django.db import models
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.contrib.admin.util import quote
from django.core.urlresolvers import reverse
from django.core.urlresolvers import reverse, NoReverseMatch
from django.utils.translation import ugettext, ugettext_lazy as _
from django.utils.encoding import smart_text
from django.utils.encoding import python_2_unicode_compatible
@ -74,5 +74,8 @@ class LogEntry(models.Model):
"""
if self.content_type and self.object_id:
url_name = 'admin:%s_%s_change' % (self.content_type.app_label, self.content_type.model)
return reverse(url_name, args=(quote(self.object_id),))
try:
return reverse(url_name, args=(quote(self.object_id),))
except NoReverseMatch:
pass
return None

View File

@ -1,6 +1,8 @@
from collections import OrderedDict
import copy
import operator
from functools import partial, reduce, update_wrapper
import warnings
from django import forms
from django.conf import settings
@ -29,7 +31,6 @@ from django.http.response import HttpResponseBase
from django.shortcuts import get_object_or_404
from django.template.response import SimpleTemplateResponse, TemplateResponse
from django.utils.decorators import method_decorator
from django.utils.datastructures import SortedDict
from django.utils.html import escape, escapejs
from django.utils.safestring import mark_safe
from django.utils import six
@ -69,6 +70,7 @@ FORMFIELD_FOR_DBFIELD_DEFAULTS = {
models.CharField: {'widget': widgets.AdminTextInputWidget},
models.ImageField: {'widget': widgets.AdminFileWidget},
models.FileField: {'widget': widgets.AdminFileWidget},
models.EmailField: {'widget': widgets.AdminEmailInputWidget},
}
csrf_protect_m = method_decorator(csrf_protect)
@ -237,13 +239,49 @@ class BaseModelAdmin(six.with_metaclass(RenameBaseModelAdminMethods)):
return db_field.formfield(**kwargs)
def _declared_fieldsets(self):
@property
def declared_fieldsets(self):
warnings.warn(
"ModelAdmin.declared_fieldsets is deprecated and "
"will be removed in Django 1.9.",
PendingDeprecationWarning, stacklevel=2
)
if self.fieldsets:
return self.fieldsets
elif self.fields:
return [(None, {'fields': self.fields})]
return None
declared_fieldsets = property(_declared_fieldsets)
def get_fields(self, request, obj=None):
"""
Hook for specifying fields.
"""
return self.fields
def get_fieldsets(self, request, obj=None):
"""
Hook for specifying fieldsets.
"""
# We access the property and check if it triggers a warning.
# If it does, then it's ours and we can safely ignore it, but if
# it doesn't then it has been overriden so we must warn about the
# deprecation.
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
declared_fieldsets = self.declared_fieldsets
if len(w) != 1 or not issubclass(w[0].category, PendingDeprecationWarning):
warnings.warn(
"ModelAdmin.declared_fieldsets is deprecated and "
"will be removed in Django 1.9.",
PendingDeprecationWarning
)
if declared_fieldsets:
return declared_fieldsets
if self.fieldsets:
return self.fieldsets
return [(None, {'fields': self.get_fields(request, obj)})]
def get_ordering(self, request):
"""
@ -263,34 +301,6 @@ class BaseModelAdmin(six.with_metaclass(RenameBaseModelAdminMethods)):
"""
return self.prepopulated_fields
def get_search_results(self, request, queryset, search_term):
# Apply keyword searches.
def construct_search(field_name):
if field_name.startswith('^'):
return "%s__istartswith" % field_name[1:]
elif field_name.startswith('='):
return "%s__iexact" % field_name[1:]
elif field_name.startswith('@'):
return "%s__search" % field_name[1:]
else:
return "%s__icontains" % field_name
use_distinct = False
if self.search_fields and search_term:
orm_lookups = [construct_search(str(search_field))
for search_field in self.search_fields]
for bit in search_term.split():
or_queries = [models.Q(**{orm_lookup: bit})
for orm_lookup in orm_lookups]
queryset = queryset.filter(reduce(operator.or_, or_queries))
if not use_distinct:
for search_spec in orm_lookups:
if lookup_needs_distinct(self.opts, search_spec):
use_distinct = True
break
return queryset, use_distinct
def get_queryset(self, request):
"""
Returns a QuerySet of all model instances that can be edited by the
@ -505,13 +515,11 @@ class ModelAdmin(BaseModelAdmin):
'delete': self.has_delete_permission(request),
}
def get_fieldsets(self, request, obj=None):
"Hook for specifying fieldsets for the add form."
if self.declared_fieldsets:
return self.declared_fieldsets
def get_fields(self, request, obj=None):
if self.fields:
return self.fields
form = self.get_form(request, obj, fields=None)
fields = list(form.base_fields) + list(self.get_readonly_fields(request, obj))
return [(None, {'fields': fields})]
return list(form.base_fields) + list(self.get_readonly_fields(request, obj))
def get_form(self, request, obj=None, **kwargs):
"""
@ -670,7 +678,7 @@ class ModelAdmin(BaseModelAdmin):
# want *any* actions enabled on this page.
from django.contrib.admin.views.main import _is_changelist_popup
if self.actions is None or _is_changelist_popup(request):
return SortedDict()
return OrderedDict()
actions = []
@ -691,8 +699,8 @@ class ModelAdmin(BaseModelAdmin):
# get_action might have returned None, so filter any of those out.
actions = filter(None, actions)
# Convert the actions into a SortedDict keyed by name.
actions = SortedDict([
# Convert the actions into an OrderedDict keyed by name.
actions = OrderedDict([
(name, (func, name, desc))
for func, name, desc in actions
])
@ -766,11 +774,50 @@ class ModelAdmin(BaseModelAdmin):
"""
return self.list_filter
def get_search_fields(self, request):
"""
Returns a sequence containing the fields to be searched whenever
somebody submits a search query.
"""
return self.search_fields
def get_search_results(self, request, queryset, search_term):
"""
Returns a tuple containing a queryset to implement the search,
and a boolean indicating if the results may contain duplicates.
"""
# Apply keyword searches.
def construct_search(field_name):
if field_name.startswith('^'):
return "%s__istartswith" % field_name[1:]
elif field_name.startswith('='):
return "%s__iexact" % field_name[1:]
elif field_name.startswith('@'):
return "%s__search" % field_name[1:]
else:
return "%s__icontains" % field_name
use_distinct = False
search_fields = self.get_search_fields(request)
if search_fields and search_term:
orm_lookups = [construct_search(str(search_field))
for search_field in search_fields]
for bit in search_term.split():
or_queries = [models.Q(**{orm_lookup: bit})
for orm_lookup in orm_lookups]
queryset = queryset.filter(reduce(operator.or_, or_queries))
if not use_distinct:
for search_spec in orm_lookups:
if lookup_needs_distinct(self.opts, search_spec):
use_distinct = True
break
return queryset, use_distinct
def get_preserved_filters(self, request):
"""
Returns the preserved filters querystring.
"""
match = request.resolver_match
if self.preserve_filters and match:
opts = self.model._meta
@ -1106,17 +1153,7 @@ class ModelAdmin(BaseModelAdmin):
else:
form_validated = False
new_object = self.model()
prefixes = {}
for FormSet, inline in zip(self.get_formsets(request), inline_instances):
prefix = FormSet.get_default_prefix()
prefixes[prefix] = prefixes.get(prefix, 0) + 1
if prefixes[prefix] != 1 or not prefix:
prefix = "%s-%s" % (prefix, prefixes[prefix])
formset = FormSet(data=request.POST, files=request.FILES,
instance=new_object,
save_as_new="_saveasnew" in request.POST,
prefix=prefix, queryset=inline.get_queryset(request))
formsets.append(formset)
formsets = self._create_formsets(request, new_object, inline_instances)
if all_valid(formsets) and form_validated:
self.save_model(request, new_object, form, False)
self.save_related(request, form, formsets, False)
@ -1134,15 +1171,7 @@ class ModelAdmin(BaseModelAdmin):
if isinstance(f, models.ManyToManyField):
initial[k] = initial[k].split(",")
form = ModelForm(initial=initial)
prefixes = {}
for FormSet, inline in zip(self.get_formsets(request), inline_instances):
prefix = FormSet.get_default_prefix()
prefixes[prefix] = prefixes.get(prefix, 0) + 1
if prefixes[prefix] != 1 or not prefix:
prefix = "%s-%s" % (prefix, prefixes[prefix])
formset = FormSet(instance=self.model(), prefix=prefix,
queryset=inline.get_queryset(request))
formsets.append(formset)
formsets = self._create_formsets(request, self.model(), inline_instances)
adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)),
self.get_prepopulated_fields(request),
@ -1194,7 +1223,6 @@ class ModelAdmin(BaseModelAdmin):
current_app=self.admin_site.name))
ModelForm = self.get_form(request, obj)
formsets = []
inline_instances = self.get_inline_instances(request, obj)
if request.method == 'POST':
form = ModelForm(request.POST, request.FILES, instance=obj)
@ -1204,18 +1232,7 @@ class ModelAdmin(BaseModelAdmin):
else:
form_validated = False
new_object = obj
prefixes = {}
for FormSet, inline in zip(self.get_formsets(request, new_object), inline_instances):
prefix = FormSet.get_default_prefix()
prefixes[prefix] = prefixes.get(prefix, 0) + 1
if prefixes[prefix] != 1 or not prefix:
prefix = "%s-%s" % (prefix, prefixes[prefix])
formset = FormSet(request.POST, request.FILES,
instance=new_object, prefix=prefix,
queryset=inline.get_queryset(request))
formsets.append(formset)
formsets = self._create_formsets(request, new_object, inline_instances)
if all_valid(formsets) and form_validated:
self.save_model(request, new_object, form, True)
self.save_related(request, form, formsets, True)
@ -1225,15 +1242,7 @@ class ModelAdmin(BaseModelAdmin):
else:
form = ModelForm(instance=obj)
prefixes = {}
for FormSet, inline in zip(self.get_formsets(request, obj), inline_instances):
prefix = FormSet.get_default_prefix()
prefixes[prefix] = prefixes.get(prefix, 0) + 1
if prefixes[prefix] != 1 or not prefix:
prefix = "%s-%s" % (prefix, prefixes[prefix])
formset = FormSet(instance=obj, prefix=prefix,
queryset=inline.get_queryset(request))
formsets.append(formset)
formsets = self._create_formsets(request, obj, inline_instances)
adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj),
self.get_prepopulated_fields(request, obj),
@ -1280,6 +1289,7 @@ class ModelAdmin(BaseModelAdmin):
list_display = self.get_list_display(request)
list_display_links = self.get_list_display_links(request, list_display)
list_filter = self.get_list_filter(request)
search_fields = self.get_search_fields(request)
# Check actions to see if any are available on this changelist
actions = self.get_actions(request)
@ -1291,9 +1301,9 @@ class ModelAdmin(BaseModelAdmin):
try:
cl = ChangeList(request, self.model, list_display,
list_display_links, list_filter, self.date_hierarchy,
self.search_fields, self.list_select_related,
self.list_per_page, self.list_max_show_all, self.list_editable,
self)
search_fields, self.list_select_related, self.list_per_page,
self.list_max_show_all, self.list_editable, self)
except IncorrectLookupParameters:
# Wacky lookup parameters were given, so redirect to the main
# changelist page, without parameters, and pass an 'invalid=1'
@ -1532,6 +1542,32 @@ class ModelAdmin(BaseModelAdmin):
"admin/object_history.html"
], context, current_app=self.admin_site.name)
def _create_formsets(self, request, obj, inline_instances):
"Helper function to generate formsets for add/change_view."
formsets = []
prefixes = {}
get_formsets_args = [request]
if obj.pk:
get_formsets_args.append(obj)
for FormSet, inline in zip(self.get_formsets(*get_formsets_args), inline_instances):
prefix = FormSet.get_default_prefix()
prefixes[prefix] = prefixes.get(prefix, 0) + 1
if prefixes[prefix] != 1 or not prefix:
prefix = "%s-%s" % (prefix, prefixes[prefix])
formset_params = {
'instance': obj,
'prefix': prefix,
'queryset': inline.get_queryset(request),
}
if request.method == 'POST':
formset_params.update({
'data': request.POST,
'files': request.FILES,
'save_as_new': '_saveasnew' in request.POST
})
formsets.append(FormSet(**formset_params))
return formsets
class InlineModelAdmin(BaseModelAdmin):
"""
@ -1656,12 +1692,11 @@ class InlineModelAdmin(BaseModelAdmin):
return inlineformset_factory(self.parent_model, self.model, **defaults)
def get_fieldsets(self, request, obj=None):
if self.declared_fieldsets:
return self.declared_fieldsets
def get_fields(self, request, obj=None):
if self.fields:
return self.fields
form = self.get_formset(request, obj, fields=None).form
fields = list(form.base_fields) + list(self.get_readonly_fields(request, obj))
return [(None, {'fields': fields})]
return list(form.base_fields) + list(self.get_readonly_fields(request, obj))
def get_queryset(self, request):
queryset = super(InlineModelAdmin, self).get_queryset(request)

View File

@ -661,45 +661,34 @@ a.deletelink:hover {
.object-tools li {
display: block;
float: left;
background: url(../img/tool-left.gif) 0 0 no-repeat;
padding: 0 0 0 8px;
margin-left: 2px;
margin-left: 5px;
height: 16px;
}
.object-tools li:hover {
background: url(../img/tool-left_over.gif) 0 0 no-repeat;
.object-tools a {
border-radius: 15px;
}
.object-tools a:link, .object-tools a:visited {
display: block;
float: left;
color: white;
padding: .1em 14px .1em 8px;
height: 14px;
background: #999 url(../img/tool-right.gif) 100% 0 no-repeat;
padding: .2em 10px;
background: #999;
}
.object-tools a:hover, .object-tools li:hover a {
background: #5b80b2 url(../img/tool-right_over.gif) 100% 0 no-repeat;
background-color: #5b80b2;
}
.object-tools a.viewsitelink, .object-tools a.golink {
background: #999 url(../img/tooltag-arrowright.gif) top right no-repeat;
padding-right: 28px;
}
.object-tools a.viewsitelink:hover, .object-tools a.golink:hover {
background: #5b80b2 url(../img/tooltag-arrowright_over.gif) top right no-repeat;
background: #999 url(../img/tooltag-arrowright.png) 95% center no-repeat;
padding-right: 26px;
}
.object-tools a.addlink {
background: #999 url(../img/tooltag-add.gif) top right no-repeat;
padding-right: 28px;
}
.object-tools a.addlink:hover {
background: #5b80b2 url(../img/tooltag-add_over.gif) top right no-repeat;
background: #999 url(../img/tooltag-add.png) 95% center no-repeat;
padding-right: 26px;
}
/* OBJECT HISTORY */
@ -837,4 +826,3 @@ table#change-history tbody th {
background: #eee url(../img/nav-bg.gif) bottom left repeat-x;
color: #666;
}

View File

@ -54,8 +54,8 @@
.selector ul.selector-chooser {
float: left;
width: 22px;
height: 50px;
background: url(../img/chooser-bg.gif) top center no-repeat;
background-color: #eee;
border-radius: 10px;
margin: 10em 5px 0 5px;
padding: 0;
}
@ -169,7 +169,8 @@ a.active.selector-clearall {
height: 22px;
width: 50px;
margin: 0 0 3px 40%;
background: url(../img/chooser_stacked-bg.gif) top center no-repeat;
background-color: #eee;
border-radius: 10px;
}
.stacked .selector-chooser li {
@ -575,4 +576,3 @@ ul.orderer li.deleted:hover, ul.orderer li.deleted a.selector:hover {
font-size: 11px;
border-top: 1px solid #ddd;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 199 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 203 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 932 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 967 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 336 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 351 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 354 B

View File

@ -1,2 +1,2 @@
(function(a){a(document).ready(function(){a("fieldset.collapse").each(function(c,b){0==a(b).find("div.errors").length&&a(b).addClass("collapsed").find("h2").first().append(' (<a id="fieldsetcollapser'+c+'" class="collapse-toggle" href="#">'+gettext("Show")+"</a>)")});a("fieldset.collapse a.collapse-toggle").click(function(){a(this).closest("fieldset").hasClass("collapsed")?a(this).text(gettext("Hide")).closest("fieldset").removeClass("collapsed").trigger("show.fieldset",[a(this).attr("id")]):a(this).text(gettext("Show")).closest("fieldset").addClass("collapsed").trigger("hide.fieldset",
[a(this).attr("id")]);return!1})})})(django.jQuery);
(function(a){a(document).ready(function(){a("fieldset.collapse").each(function(c,b){a(b).find("div.errors").length==0&&a(b).addClass("collapsed").find("h2").first().append(' (<a id="fieldsetcollapser'+c+'" class="collapse-toggle" href="#">'+gettext("Show")+"</a>)")});a("fieldset.collapse a.collapse-toggle").click(function(){a(this).closest("fieldset").hasClass("collapsed")?a(this).text(gettext("Hide")).closest("fieldset").removeClass("collapsed").trigger("show.fieldset",[a(this).attr("id")]):a(this).text(gettext("Show")).closest("fieldset").addClass("collapsed").trigger("hide.fieldset",
[a(this).attr("id")]);return false})})})(django.jQuery);

View File

@ -1,9 +1,9 @@
(function(b){b.fn.formset=function(d){var a=b.extend({},b.fn.formset.defaults,d),c=b(this),d=c.parent(),i=function(a,e,g){var d=RegExp("("+e+"-(\\d+|__prefix__))"),e=e+"-"+g;b(a).prop("for")&&b(a).prop("for",b(a).prop("for").replace(d,e));a.id&&(a.id=a.id.replace(d,e));a.name&&(a.name=a.name.replace(d,e))},f=b("#id_"+a.prefix+"-TOTAL_FORMS").prop("autocomplete","off"),g=parseInt(f.val(),10),e=b("#id_"+a.prefix+"-MAX_NUM_FORMS").prop("autocomplete","off"),f=""===e.val()||0<e.val()-f.val();c.each(function(){b(this).not("."+
a.emptyCssClass).addClass(a.formCssClass)});if(c.length&&f){var h;"TR"==c.prop("tagName")?(c=this.eq(-1).children().length,d.append('<tr class="'+a.addCssClass+'"><td colspan="'+c+'"><a href="javascript:void(0)">'+a.addText+"</a></tr>"),h=d.find("tr:last a")):(c.filter(":last").after('<div class="'+a.addCssClass+'"><a href="javascript:void(0)">'+a.addText+"</a></div>"),h=c.filter(":last").next().find("a"));h.click(function(d){d.preventDefault();var f=b("#id_"+a.prefix+"-TOTAL_FORMS"),d=b("#"+a.prefix+
"-empty"),c=d.clone(true);c.removeClass(a.emptyCssClass).addClass(a.formCssClass).attr("id",a.prefix+"-"+g);c.is("tr")?c.children(":last").append('<div><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+a.deleteText+"</a></div>"):c.is("ul")||c.is("ol")?c.append('<li><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+a.deleteText+"</a></li>"):c.children(":first").append('<span><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+a.deleteText+"</a></span>");c.find("*").each(function(){i(this,
a.prefix,f.val())});c.insertBefore(b(d));b(f).val(parseInt(f.val(),10)+1);g=g+1;e.val()!==""&&e.val()-f.val()<=0&&h.parent().hide();c.find("a."+a.deleteCssClass).click(function(d){d.preventDefault();d=b(this).parents("."+a.formCssClass);d.remove();g=g-1;a.removed&&a.removed(d);d=b("."+a.formCssClass);b("#id_"+a.prefix+"-TOTAL_FORMS").val(d.length);(e.val()===""||e.val()-d.length>0)&&h.parent().show();for(var c=0,f=d.length;c<f;c++){i(b(d).get(c),a.prefix,c);b(d.get(c)).find("*").each(function(){i(this,
a.prefix,c)})}});a.added&&a.added(c)})}return this};b.fn.formset.defaults={prefix:"form",addText:"add another",deleteText:"remove",addCssClass:"add-row",deleteCssClass:"delete-row",emptyCssClass:"empty-row",formCssClass:"dynamic-form",added:null,removed:null};b.fn.tabularFormset=function(d){var a=b(this),c=function(){b(a.selector).not(".add-row").removeClass("row1 row2").filter(":even").addClass("row1").end().filter(":odd").addClass("row2")};a.formset({prefix:d.prefix,addText:d.addText,formCssClass:"dynamic-"+
d.prefix,deleteCssClass:"inline-deletelink",deleteText:d.deleteText,emptyCssClass:"empty-form",removed:c,added:function(a){a.find(".prepopulated_field").each(function(){var d=b(this).find("input, select, textarea"),c=d.data("dependency_list")||[],e=[];b.each(c,function(d,b){e.push("#"+a.find(".field-"+b).find("input, select, textarea").attr("id"))});e.length&&d.prepopulate(e,d.attr("maxlength"))});"undefined"!=typeof DateTimeShortcuts&&(b(".datetimeshortcuts").remove(),DateTimeShortcuts.init());"undefined"!=
typeof SelectFilter&&(b(".selectfilter").each(function(a,b){var c=b.name.split("-");SelectFilter.init(b.id,c[c.length-1],false,d.adminStaticPrefix)}),b(".selectfilterstacked").each(function(a,b){var c=b.name.split("-");SelectFilter.init(b.id,c[c.length-1],true,d.adminStaticPrefix)}));c(a)}});return a};b.fn.stackedFormset=function(d){var a=b(this),c=function(){b(a.selector).find(".inline_label").each(function(a){a+=1;b(this).html(b(this).html().replace(/(#\d+)/g,"#"+a))})};a.formset({prefix:d.prefix,
addText:d.addText,formCssClass:"dynamic-"+d.prefix,deleteCssClass:"inline-deletelink",deleteText:d.deleteText,emptyCssClass:"empty-form",removed:c,added:function(a){a.find(".prepopulated_field").each(function(){var d=b(this).find("input, select, textarea"),c=d.data("dependency_list")||[],e=[];b.each(c,function(d,b){e.push("#"+a.find(".form-row .field-"+b).find("input, select, textarea").attr("id"))});e.length&&d.prepopulate(e,d.attr("maxlength"))});"undefined"!=typeof DateTimeShortcuts&&(b(".datetimeshortcuts").remove(),
DateTimeShortcuts.init());"undefined"!=typeof SelectFilter&&(b(".selectfilter").each(function(a,b){var c=b.name.split("-");SelectFilter.init(b.id,c[c.length-1],false,d.adminStaticPrefix)}),b(".selectfilterstacked").each(function(a,b){var c=b.name.split("-");SelectFilter.init(b.id,c[c.length-1],true,d.adminStaticPrefix)}));c(a)}});return a}})(django.jQuery);
(function(a){a.fn.formset=function(g){var b=a.extend({},a.fn.formset.defaults,g),i=a(this);g=i.parent();var m=function(e,k,h){var j=RegExp("("+k+"-(\\d+|__prefix__))");k=k+"-"+h;a(e).prop("for")&&a(e).prop("for",a(e).prop("for").replace(j,k));if(e.id)e.id=e.id.replace(j,k);if(e.name)e.name=e.name.replace(j,k)},l=a("#id_"+b.prefix+"-TOTAL_FORMS").prop("autocomplete","off"),d=parseInt(l.val(),10),c=a("#id_"+b.prefix+"-MAX_NUM_FORMS").prop("autocomplete","off");l=c.val()===""||c.val()-l.val()>0;i.each(function(){a(this).not("."+
b.emptyCssClass).addClass(b.formCssClass)});if(i.length&&l){var f;if(i.prop("tagName")=="TR"){i=this.eq(-1).children().length;g.append('<tr class="'+b.addCssClass+'"><td colspan="'+i+'"><a href="javascript:void(0)">'+b.addText+"</a></tr>");f=g.find("tr:last a")}else{i.filter(":last").after('<div class="'+b.addCssClass+'"><a href="javascript:void(0)">'+b.addText+"</a></div>");f=i.filter(":last").next().find("a")}f.click(function(e){e.preventDefault();var k=a("#id_"+b.prefix+"-TOTAL_FORMS");e=a("#"+
b.prefix+"-empty");var h=e.clone(true);h.removeClass(b.emptyCssClass).addClass(b.formCssClass).attr("id",b.prefix+"-"+d);if(h.is("tr"))h.children(":last").append('<div><a class="'+b.deleteCssClass+'" href="javascript:void(0)">'+b.deleteText+"</a></div>");else h.is("ul")||h.is("ol")?h.append('<li><a class="'+b.deleteCssClass+'" href="javascript:void(0)">'+b.deleteText+"</a></li>"):h.children(":first").append('<span><a class="'+b.deleteCssClass+'" href="javascript:void(0)">'+b.deleteText+"</a></span>");
h.find("*").each(function(){m(this,b.prefix,k.val())});h.insertBefore(a(e));a(k).val(parseInt(k.val(),10)+1);d+=1;c.val()!==""&&c.val()-k.val()<=0&&f.parent().hide();h.find("a."+b.deleteCssClass).click(function(j){j.preventDefault();j=a(this).parents("."+b.formCssClass);j.remove();d-=1;b.removed&&b.removed(j);j=a("."+b.formCssClass);a("#id_"+b.prefix+"-TOTAL_FORMS").val(j.length);if(c.val()===""||c.val()-j.length>0)f.parent().show();for(var n=0,o=j.length;n<o;n++){m(a(j).get(n),b.prefix,n);a(j.get(n)).find("*").each(function(){m(this,
b.prefix,n)})}});b.added&&b.added(h)})}return this};a.fn.formset.defaults={prefix:"form",addText:"add another",deleteText:"remove",addCssClass:"add-row",deleteCssClass:"delete-row",emptyCssClass:"empty-row",formCssClass:"dynamic-form",added:null,removed:null};a.fn.tabularFormset=function(g){var b=a(this),i=function(){a(b.selector).not(".add-row").removeClass("row1 row2").filter(":even").addClass("row1").end().filter(":odd").addClass("row2")},m=function(){if(typeof SelectFilter!="undefined"){a(".selectfilter").each(function(d,
c){var f=c.name.split("-");SelectFilter.init(c.id,f[f.length-1],false,g.adminStaticPrefix)});a(".selectfilterstacked").each(function(d,c){var f=c.name.split("-");SelectFilter.init(c.id,f[f.length-1],true,g.adminStaticPrefix)})}},l=function(d){d.find(".prepopulated_field").each(function(){var c=a(this).find("input, select, textarea"),f=c.data("dependency_list")||[],e=[];a.each(f,function(k,h){e.push("#"+d.find(".field-"+h).find("input, select, textarea").attr("id"))});e.length&&c.prepopulate(e,c.attr("maxlength"))})};
b.formset({prefix:g.prefix,addText:g.addText,formCssClass:"dynamic-"+g.prefix,deleteCssClass:"inline-deletelink",deleteText:g.deleteText,emptyCssClass:"empty-form",removed:i,added:function(d){l(d);if(typeof DateTimeShortcuts!="undefined"){a(".datetimeshortcuts").remove();DateTimeShortcuts.init()}m();i(d)}});return b};a.fn.stackedFormset=function(g){var b=a(this),i=function(){a(b.selector).find(".inline_label").each(function(d){d=d+1;a(this).html(a(this).html().replace(/(#\d+)/g,"#"+d))})},m=function(){if(typeof SelectFilter!=
"undefined"){a(".selectfilter").each(function(d,c){var f=c.name.split("-");SelectFilter.init(c.id,f[f.length-1],false,g.adminStaticPrefix)});a(".selectfilterstacked").each(function(d,c){var f=c.name.split("-");SelectFilter.init(c.id,f[f.length-1],true,g.adminStaticPrefix)})}},l=function(d){d.find(".prepopulated_field").each(function(){var c=a(this).find("input, select, textarea"),f=c.data("dependency_list")||[],e=[];a.each(f,function(k,h){e.push("#"+d.find(".form-row .field-"+h).find("input, select, textarea").attr("id"))});
e.length&&c.prepopulate(e,c.attr("maxlength"))})};b.formset({prefix:g.prefix,addText:g.addText,formCssClass:"dynamic-"+g.prefix,deleteCssClass:"inline-deletelink",deleteText:g.deleteText,emptyCssClass:"empty-form",removed:i,added:function(d){l(d);if(typeof DateTimeShortcuts!="undefined"){a(".datetimeshortcuts").remove();DateTimeShortcuts.init()}m();i(d)}});return b}})(django.jQuery);

View File

@ -3,32 +3,37 @@
/*
Depends on urlify.js
Populates a selected field with the values of the dependent fields,
URLifies and shortens the string.
dependencies - array of dependent fields id's
maxLength - maximum length of the URLify'd string
URLifies and shortens the string.
dependencies - array of dependent fields ids
maxLength - maximum length of the URLify'd string
*/
return this.each(function() {
var field = $(this);
field.data('_changed', false);
field.change(function() {
field.data('_changed', true);
});
var prepopulatedField = $(this);
var populate = function () {
// Bail if the fields value has changed
if (field.data('_changed') == true) return;
// Bail if the field's value has been changed by the user
if (prepopulatedField.data('_changed')) {
return;
}
var values = [];
$.each(dependencies, function(i, field) {
if ($(field).val().length > 0) {
values.push($(field).val());
}
})
field.val(URLify(values.join(' '), maxLength));
field = $(field);
if (field.val().length > 0) {
values.push(field.val());
}
});
prepopulatedField.val(URLify(values.join(' '), maxLength));
};
$(dependencies.join(',')).keyup(populate).change(populate).focus(populate);
prepopulatedField.data('_changed', false);
prepopulatedField.change(function() {
prepopulatedField.data('_changed', true);
});
if (!prepopulatedField.val()) {
$(dependencies.join(',')).keyup(populate).change(populate).focus(populate);
}
});
};
})(django.jQuery);

View File

@ -1 +1 @@
(function(a){a.fn.prepopulate=function(d,g){return this.each(function(){var b=a(this);b.data("_changed",false);b.change(function(){b.data("_changed",true)});var c=function(){if(b.data("_changed")!=true){var e=[];a.each(d,function(h,f){a(f).val().length>0&&e.push(a(f).val())});b.val(URLify(e.join(" "),g))}};a(d.join(",")).keyup(c).change(c).focus(c)})}})(django.jQuery);
(function(b){b.fn.prepopulate=function(e,g){return this.each(function(){var a=b(this),d=function(){if(!a.data("_changed")){var f=[];b.each(e,function(h,c){c=b(c);c.val().length>0&&f.push(c.val())});a.val(URLify(f.join(" "),g))}};a.data("_changed",false);a.change(function(){a.data("_changed",true)});a.val()||b(e.join(",")).keyup(d).change(d).focus(d)})}})(django.jQuery);

View File

@ -8,12 +8,8 @@
</div>
{% endblock %}
{% block title %}{% trans 'Password change successful' %}{% endblock %}
{% block title %}{{ title }}{% endblock %}
{% block content_title %}<h1>{{ title }}</h1>{% endblock %}
{% block content %}
<h1>{% trans 'Password change successful' %}</h1>
<p>{% trans 'Your password was changed.' %}</p>
{% endblock %}

View File

@ -9,7 +9,8 @@
</div>
{% endblock %}
{% block title %}{% trans 'Password change' %}{% endblock %}
{% block title %}{{ title }}{% endblock %}
{% block content_title %}<h1>{{ title }}</h1>{% endblock %}
{% block content %}<div id="content-main">
@ -21,7 +22,6 @@
</p>
{% endif %}
<h1>{% trans 'Password change' %}</h1>
<p>{% trans "Please enter your old password, for security's sake, and then enter your new password twice so we can verify you typed it in correctly." %}</p>

View File

@ -8,12 +8,11 @@
</div>
{% endblock %}
{% block title %}{% trans 'Password reset complete' %}{% endblock %}
{% block title %}{{ title }}{% endblock %}
{% block content_title %}<h1>{{ title }}</h1>{% endblock %}
{% block content %}
<h1>{% trans 'Password reset complete' %}</h1>
<p>{% trans "Your password has been set. You may go ahead and log in now." %}</p>
<p><a href="{{ login_url }}">{% trans 'Log in' %}</a></p>

View File

@ -8,14 +8,12 @@
</div>
{% endblock %}
{% block title %}{% trans 'Password reset' %}{% endblock %}
{% block title %}{{ title }}{% endblock %}
{% block content_title %}<h1>{{ title }}</h1>{% endblock %}
{% block content %}
{% if validlink %}
<h1>{% trans 'Enter new password' %}</h1>
<p>{% trans "Please enter your new password twice so we can verify you typed it in correctly." %}</p>
<form action="" method="post">{% csrf_token %}
@ -28,8 +26,6 @@
{% else %}
<h1>{% trans 'Password reset unsuccessful' %}</h1>
<p>{% trans "The password reset link was invalid, possibly because it has already been used. Please request a new password reset." %}</p>
{% endif %}

View File

@ -8,12 +8,10 @@
</div>
{% endblock %}
{% block title %}{% trans 'Password reset successful' %}{% endblock %}
{% block title %}{{ title }}{% endblock %}
{% block content_title %}<h1>{{ title }}</h1>{% endblock %}
{% block content %}
<h1>{% trans 'Password reset successful' %}</h1>
<p>{% trans "We've emailed you instructions for setting your password. You should be receiving them shortly." %}</p>
<p>{% trans "If you don't receive an email, please make sure you've entered the address you registered with, and check your spam folder." %}</p>

View File

@ -8,12 +8,10 @@
</div>
{% endblock %}
{% block title %}{% trans "Password reset" %}{% endblock %}
{% block title %}{{ title }}{% endblock %}
{% block content_title %}<h1>{{ title }}</h1>{% endblock %}
{% block content %}
<h1>{% trans "Password reset" %}</h1>
<p>{% trans "Forgotten your password? Enter your email address below, and we'll email instructions for setting a new one." %}</p>
<form action="" method="post">{% csrf_token %}

View File

@ -180,7 +180,7 @@ def items_for_result(cl, result, form):
first = True
pk = cl.lookup_opts.pk.attname
for field_name in cl.list_display:
row_class = ''
row_classes = ['field-%s' % field_name]
try:
f, attr, value = lookup_field(field_name, result, cl.model_admin)
except ObjectDoesNotExist:
@ -188,7 +188,7 @@ def items_for_result(cl, result, form):
else:
if f is None:
if field_name == 'action_checkbox':
row_class = mark_safe(' class="action-checkbox"')
row_classes = ['action-checkbox']
allow_tags = getattr(attr, 'allow_tags', False)
boolean = getattr(attr, 'boolean', False)
if boolean:
@ -199,7 +199,7 @@ def items_for_result(cl, result, form):
if allow_tags:
result_repr = mark_safe(result_repr)
if isinstance(value, (datetime.date, datetime.time)):
row_class = mark_safe(' class="nowrap"')
row_classes.append('nowrap')
else:
if isinstance(f.rel, models.ManyToOneRel):
field_val = getattr(result, f.name)
@ -210,9 +210,10 @@ def items_for_result(cl, result, form):
else:
result_repr = display_for_field(value, f)
if isinstance(f, (models.DateField, models.TimeField, models.ForeignKey)):
row_class = mark_safe(' class="nowrap"')
row_classes.append('nowrap')
if force_text(result_repr) == '':
result_repr = mark_safe('&nbsp;')
row_class = mark_safe(' class="%s"' % ' '.join(row_classes))
# If list_display_links not defined, add the link tag to the first field
if (first and not cl.list_display_links) or field_name in cl.list_display_links:
table_tag = {True:'th', False:'td'}[first]

View File

@ -9,7 +9,7 @@ def prepopulated_fields_js(context):
the prepopulated fields for both the admin form and inlines.
"""
prepopulated_fields = []
if context['add'] and 'adminform' in context:
if 'adminform' in context:
prepopulated_fields.extend(context['adminform'].prepopulated_fields)
if 'inline_admin_formsets' in context:
for inline_admin_formset in context['inline_admin_formsets']:

View File

@ -1,3 +1,4 @@
from collections import OrderedDict
import sys
import warnings
@ -7,7 +8,6 @@ from django.core.urlresolvers import reverse
from django.db import models
from django.db.models.fields import FieldDoesNotExist
from django.utils import six
from django.utils.datastructures import SortedDict
from django.utils.deprecation import RenameMethodsBase
from django.utils.encoding import force_str, force_text
from django.utils.translation import ugettext, ugettext_lazy
@ -319,13 +319,13 @@ class ChangeList(six.with_metaclass(RenameChangeListMethods)):
def get_ordering_field_columns(self):
"""
Returns a SortedDict of ordering field column numbers and asc/desc
Returns an OrderedDict of ordering field column numbers and asc/desc
"""
# We must cope with more than one column having the same underlying sort
# field, so we base things on column numbers.
ordering = self._get_default_ordering()
ordering_fields = SortedDict()
ordering_fields = OrderedDict()
if ORDER_VAR not in self.params:
# for ordering specified on ModelAdmin or model Meta, we don't know
# the right column numbers absolutely, because there might be more

View File

@ -116,6 +116,8 @@ def url_params_from_lookup_dict(lookups):
if lookups and hasattr(lookups, 'items'):
items = []
for k, v in lookups.items():
if callable(v):
v = v()
if isinstance(v, (tuple, list)):
v = ','.join([str(x) for x in v])
elif isinstance(v, bool):
@ -285,7 +287,14 @@ class AdminTextInputWidget(forms.TextInput):
final_attrs.update(attrs)
super(AdminTextInputWidget, self).__init__(attrs=final_attrs)
class AdminURLFieldWidget(forms.TextInput):
class AdminEmailInputWidget(forms.EmailInput):
def __init__(self, attrs=None):
final_attrs = {'class': 'vTextField'}
if attrs is not None:
final_attrs.update(attrs)
super(AdminEmailInputWidget, self).__init__(attrs=final_attrs)
class AdminURLFieldWidget(forms.URLInput):
def __init__(self, attrs=None):
final_attrs = {'class': 'vURLField'}
if attrs is not None:

View File

@ -1,4 +1,4 @@
from __future__ import absolute_import, unicode_literals
from __future__ import unicode_literals
import unittest

View File

@ -1,3 +1,4 @@
from importlib import import_module
import inspect
import os
import re
@ -13,7 +14,6 @@ from django.http import Http404
from django.core import urlresolvers
from django.contrib.admindocs import utils
from django.contrib.sites.models import Site
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 _
@ -319,7 +319,7 @@ def load_all_installed_template_libraries():
libraries = []
for library_name in libraries:
try:
lib = template.get_library(library_name)
template.get_library(library_name)
except template.InvalidTemplateLibrary:
pass

View File

@ -17,6 +17,7 @@ from django.views.decorators.csrf import csrf_protect
from django.views.decorators.debug import sensitive_post_parameters
csrf_protect_m = method_decorator(csrf_protect)
sensitive_post_parameters_m = method_decorator(sensitive_post_parameters())
class GroupAdmin(admin.ModelAdmin):
@ -87,7 +88,7 @@ class UserAdmin(admin.ModelAdmin):
return False
return super(UserAdmin, self).lookup_allowed(lookup, value)
@sensitive_post_parameters()
@sensitive_post_parameters_m
@csrf_protect_m
@transaction.atomic
def add_view(self, request, form_url='', extra_context=None):
@ -118,7 +119,7 @@ class UserAdmin(admin.ModelAdmin):
return super(UserAdmin, self).add_view(request, form_url,
extra_context)
@sensitive_post_parameters()
@sensitive_post_parameters_m
def user_change_password(self, request, id, form_url=''):
if not self.has_change_permission(request):
raise PermissionDenied
@ -127,6 +128,8 @@ class UserAdmin(admin.ModelAdmin):
form = self.change_password_form(user, request.POST)
if form.is_valid():
form.save()
change_message = self.construct_change_message(request, form, None)
self.log_change(request, request.user, change_message)
msg = ugettext('Password changed successfully.')
messages.success(request, msg)
return HttpResponseRedirect('..')

View File

@ -17,7 +17,9 @@ class ModelBackend(object):
if user.check_password(password):
return user
except UserModel.DoesNotExist:
return None
# Run the default password hasher once to reduce the timing
# difference between an existing and a non-existing user (#20760).
UserModel().set_password(password)
def get_group_permissions(self, user_obj, obj=None):
"""

View File

@ -1,9 +1,10 @@
from __future__ import unicode_literals
from collections import OrderedDict
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 urlsafe_base64_encode
@ -191,13 +192,28 @@ class AuthenticationForm(forms.Form):
code='invalid_login',
params={'username': self.username_field.verbose_name},
)
elif not self.user_cache.is_active:
raise forms.ValidationError(
self.error_messages['inactive'],
code='inactive',
)
else:
self.confirm_login_allowed(self.user_cache)
return self.cleaned_data
def confirm_login_allowed(self, user):
"""
Controls whether the given User may log in. This is a policy setting,
independent of end-user authentication. This default behavior is to
allow login by active users, and reject login by inactive users.
If the given user cannot log in, this method should raise a
``forms.ValidationError``.
If the given user may log in, this method should return None.
"""
if not user.is_active:
raise forms.ValidationError(
self.error_messages['inactive'],
code='inactive',
)
def get_user_id(self):
if self.user_cache:
return self.user_cache.id
@ -214,7 +230,7 @@ class PasswordResetForm(forms.Form):
subject_template_name='registration/password_reset_subject.txt',
email_template_name='registration/password_reset_email.html',
use_https=False, token_generator=default_token_generator,
from_email=None, request=None):
from_email=None, request=None, html_email_template_name=None):
"""
Generates a one-use only link for resetting password and sends to the
user.
@ -247,7 +263,12 @@ class PasswordResetForm(forms.Form):
# Email subject *must not* contain newlines
subject = ''.join(subject.splitlines())
email = loader.render_to_string(email_template_name, c)
send_mail(subject, email, from_email, [user.email])
if html_email_template_name:
html_email = loader.render_to_string(html_email_template_name, c)
else:
html_email = None
send_mail(subject, email, from_email, [user.email], html_message=html_email)
class SetPasswordForm(forms.Form):
@ -309,7 +330,7 @@ class PasswordChangeForm(SetPasswordForm):
)
return old_password
PasswordChangeForm.base_fields = SortedDict([
PasswordChangeForm.base_fields = OrderedDict([
(k, PasswordChangeForm.base_fields[k])
for k in ['old_password', 'new_password1', 'new_password2']
])
@ -350,3 +371,11 @@ class AdminPasswordChangeForm(forms.Form):
if commit:
self.user.save()
return self.user
def _get_changed_data(self):
data = super(AdminPasswordChangeForm, self).changed_data
for name in self.fields.keys():
if name not in data:
return []
return ['password']
changed_data = property(_get_changed_data)

View File

@ -2,13 +2,13 @@ from __future__ import unicode_literals
import base64
import binascii
from collections import OrderedDict
import hashlib
import importlib
from django.dispatch import receiver
from django.conf import settings
from django.test.signals import setting_changed
from django.utils import importlib
from django.utils.datastructures import SortedDict
from django.utils.encoding import force_bytes, force_str, force_text
from django.core.exceptions import ImproperlyConfigured
from django.utils.crypto import (
@ -172,7 +172,7 @@ class BasePasswordHasher(object):
if isinstance(self.library, (tuple, list)):
name, mod_path = self.library
else:
name = mod_path = self.library
mod_path = self.library
try:
module = importlib.import_module(mod_path)
except ImportError as e:
@ -243,7 +243,7 @@ class PBKDF2PasswordHasher(BasePasswordHasher):
def safe_summary(self, encoded):
algorithm, iterations, salt, hash = encoded.split('$', 3)
assert algorithm == self.algorithm
return SortedDict([
return OrderedDict([
(_('algorithm'), algorithm),
(_('iterations'), iterations),
(_('salt'), mask_hash(salt)),
@ -320,7 +320,7 @@ class BCryptSHA256PasswordHasher(BasePasswordHasher):
algorithm, empty, algostr, work_factor, data = encoded.split('$', 4)
assert algorithm == self.algorithm
salt, checksum = data[:22], data[22:]
return SortedDict([
return OrderedDict([
(_('algorithm'), algorithm),
(_('work factor'), work_factor),
(_('salt'), mask_hash(salt)),
@ -368,7 +368,7 @@ class SHA1PasswordHasher(BasePasswordHasher):
def safe_summary(self, encoded):
algorithm, salt, hash = encoded.split('$', 2)
assert algorithm == self.algorithm
return SortedDict([
return OrderedDict([
(_('algorithm'), algorithm),
(_('salt'), mask_hash(salt, show=2)),
(_('hash'), mask_hash(hash)),
@ -396,7 +396,7 @@ class MD5PasswordHasher(BasePasswordHasher):
def safe_summary(self, encoded):
algorithm, salt, hash = encoded.split('$', 2)
assert algorithm == self.algorithm
return SortedDict([
return OrderedDict([
(_('algorithm'), algorithm),
(_('salt'), mask_hash(salt, show=2)),
(_('hash'), mask_hash(hash)),
@ -429,7 +429,7 @@ class UnsaltedSHA1PasswordHasher(BasePasswordHasher):
def safe_summary(self, encoded):
assert encoded.startswith('sha1$$')
hash = encoded[6:]
return SortedDict([
return OrderedDict([
(_('algorithm'), self.algorithm),
(_('hash'), mask_hash(hash)),
])
@ -462,7 +462,7 @@ class UnsaltedMD5PasswordHasher(BasePasswordHasher):
return constant_time_compare(encoded, encoded_2)
def safe_summary(self, encoded):
return SortedDict([
return OrderedDict([
(_('algorithm'), self.algorithm),
(_('hash'), mask_hash(encoded, show=3)),
])
@ -496,7 +496,7 @@ class CryptPasswordHasher(BasePasswordHasher):
def safe_summary(self, encoded):
algorithm, salt, data = encoded.split('$', 2)
assert algorithm == self.algorithm
return SortedDict([
return OrderedDict([
(_('algorithm'), algorithm),
(_('salt'), salt),
(_('hash'), mask_hash(data, show=3)),

View File

@ -0,0 +1 @@
<html><a href="{{ protocol }}://{{ domain }}/reset/{{ uid }}/{{ token }}/">Link</a></html>

View File

@ -12,6 +12,17 @@ from django.contrib.auth import authenticate, get_user
from django.http import HttpRequest
from django.test import TestCase
from django.test.utils import override_settings
from django.contrib.auth.hashers import MD5PasswordHasher
class CountingMD5PasswordHasher(MD5PasswordHasher):
"""Hasher that counts how many times it computes a hash."""
calls = 0
def encode(self, *args, **kwargs):
type(self).calls += 1
return super(CountingMD5PasswordHasher, self).encode(*args, **kwargs)
class BaseModelBackendTest(object):
@ -107,10 +118,26 @@ class BaseModelBackendTest(object):
self.assertEqual(user.get_all_permissions(), set(['auth.test']))
def test_get_all_superuser_permissions(self):
"A superuser has all permissions. Refs #14795"
"""A superuser has all permissions. Refs #14795."""
user = self.UserModel._default_manager.get(pk=self.superuser.pk)
self.assertEqual(len(user.get_all_permissions()), len(Permission.objects.all()))
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.tests.test_auth_backends.CountingMD5PasswordHasher',))
def test_authentication_timing(self):
"""Hasher is run once regardless of whether the user exists. Refs #20760."""
# Re-set the password, because this tests overrides PASSWORD_HASHERS
self.user.set_password('test')
self.user.save()
CountingMD5PasswordHasher.calls = 0
username = getattr(self.user, self.UserModel.USERNAME_FIELD)
authenticate(username=username, password='test')
self.assertEqual(CountingMD5PasswordHasher.calls, 1)
CountingMD5PasswordHasher.calls = 0
authenticate(username='no_such_user', password='test')
self.assertEqual(CountingMD5PasswordHasher.calls, 1)
@skipIfCustomUser
class ModelBackendTest(BaseModelBackendTest, TestCase):

View File

@ -161,7 +161,7 @@ class AuthContextProcessorTests(TestCase):
# Exception RuntimeError: 'maximum recursion depth exceeded while
# calling a Python object' in <type 'exceptions.AttributeError'>
# ignored"
query = Q(user=response.context['user']) & Q(someflag=True)
Q(user=response.context['user']) & Q(someflag=True)
# Tests for user equality. This is hard because User defines
# equality in a non-duck-typing way

View File

@ -1,7 +1,9 @@
from __future__ import unicode_literals
import os
import re
from django import forms
from django.contrib.auth import get_user_model
from django.contrib.auth.models import User
from django.contrib.auth.forms import (UserCreationForm, AuthenticationForm,
@ -131,6 +133,40 @@ class AuthenticationFormTest(TestCase):
self.assertEqual(form.non_field_errors(),
[force_text(form.error_messages['inactive'])])
def test_custom_login_allowed_policy(self):
# The user is inactive, but our custom form policy allows him to log in.
data = {
'username': 'inactive',
'password': 'password',
}
class AuthenticationFormWithInactiveUsersOkay(AuthenticationForm):
def confirm_login_allowed(self, user):
pass
form = AuthenticationFormWithInactiveUsersOkay(None, data)
self.assertTrue(form.is_valid())
# If we want to disallow some logins according to custom logic,
# we should raise a django.forms.ValidationError in the form.
class PickyAuthenticationForm(AuthenticationForm):
def confirm_login_allowed(self, user):
if user.username == "inactive":
raise forms.ValidationError(_("This user is disallowed."))
raise forms.ValidationError(_("Sorry, nobody's allowed in."))
form = PickyAuthenticationForm(None, data)
self.assertFalse(form.is_valid())
self.assertEqual(form.non_field_errors(), ['This user is disallowed.'])
data = {
'username': 'testclient',
'password': 'password',
}
form = PickyAuthenticationForm(None, data)
self.assertFalse(form.is_valid())
self.assertEqual(form.non_field_errors(), ["Sorry, nobody's allowed in."])
def test_success(self):
# The success case
data = {
@ -272,7 +308,7 @@ class UserChangeFormTest(TestCase):
fields = ('groups',)
# Just check we can create it
form = MyUserForm({})
MyUserForm({})
def test_unsuable_password(self):
user = User.objects.get(username='empty_password')
@ -417,6 +453,60 @@ class PasswordResetFormTest(TestCase):
form.save()
self.assertEqual(len(mail.outbox), 0)
@override_settings(
TEMPLATE_LOADERS=('django.template.loaders.filesystem.Loader',),
TEMPLATE_DIRS=(
os.path.join(os.path.dirname(upath(__file__)), 'templates'),
),
)
def test_save_plaintext_email(self):
"""
Test the PasswordResetForm.save() method with no html_email_template_name
parameter passed in.
Test to ensure original behavior is unchanged after the parameter was added.
"""
(user, username, email) = self.create_dummy_user()
form = PasswordResetForm({"email": email})
self.assertTrue(form.is_valid())
form.save()
self.assertEqual(len(mail.outbox), 1)
message = mail.outbox[0].message()
self.assertFalse(message.is_multipart())
self.assertEqual(message.get_content_type(), 'text/plain')
self.assertEqual(message.get('subject'), 'Custom password reset on example.com')
self.assertEqual(len(mail.outbox[0].alternatives), 0)
self.assertEqual(message.get_all('to'), [email])
self.assertTrue(re.match(r'^http://example.com/reset/[\w+/-]', message.get_payload()))
@override_settings(
TEMPLATE_LOADERS=('django.template.loaders.filesystem.Loader',),
TEMPLATE_DIRS=(
os.path.join(os.path.dirname(upath(__file__)), 'templates'),
),
)
def test_save_html_email_template_name(self):
"""
Test the PasswordResetFOrm.save() method with html_email_template_name
parameter specified.
Test to ensure that a multipart email is sent with both text/plain
and text/html parts.
"""
(user, username, email) = self.create_dummy_user()
form = PasswordResetForm({"email": email})
self.assertTrue(form.is_valid())
form.save(html_email_template_name='registration/html_password_reset_email.html')
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(len(mail.outbox[0].alternatives), 1)
message = mail.outbox[0].message()
self.assertEqual(message.get('subject'), 'Custom password reset on example.com')
self.assertEqual(len(message.get_payload()), 2)
self.assertTrue(message.is_multipart())
self.assertEqual(message.get_payload(0).get_content_type(), 'text/plain')
self.assertEqual(message.get_payload(1).get_content_type(), 'text/html')
self.assertEqual(message.get_all('to'), [email])
self.assertTrue(re.match(r'^http://example.com/reset/[\w/-]+', message.get_payload(0).get_payload()))
self.assertTrue(re.match(r'^<html><a href="http://example.com/reset/[\w/-]+/">Link</a></html>$', message.get_payload(1).get_payload()))
class ReadOnlyPasswordHashTest(TestCase):

View File

@ -0,0 +1,59 @@
from django.contrib.auth import authenticate
from django.contrib.auth.models import User
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.contrib.auth.views import (
password_reset, password_reset_done, password_reset_confirm,
password_reset_complete, password_change, password_change_done,
)
from django.test import RequestFactory, TestCase
from django.test.utils import override_settings
from django.utils.encoding import force_bytes, force_text
from django.utils.http import urlsafe_base64_encode
@skipIfCustomUser
@override_settings(
PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',),
)
class AuthTemplateTests(TestCase):
def test_titles(self):
rf = RequestFactory()
user = User.objects.create_user('jsmith', 'jsmith@example.com', 'pass')
user = authenticate(username=user.username, password='pass')
request = rf.get('/somepath/')
request.user = user
response = password_reset(request, post_reset_redirect='dummy/')
self.assertContains(response, '<title>Password reset</title>')
self.assertContains(response, '<h1>Password reset</h1>')
response = password_reset_done(request)
self.assertContains(response, '<title>Password reset successful</title>')
self.assertContains(response, '<h1>Password reset successful</h1>')
# password_reset_confirm invalid token
response = password_reset_confirm(request, uidb64='Bad', token='Bad', post_reset_redirect='dummy/')
self.assertContains(response, '<title>Password reset unsuccessful</title>')
self.assertContains(response, '<h1>Password reset unsuccessful</h1>')
# password_reset_confirm valid token
default_token_generator = PasswordResetTokenGenerator()
token = default_token_generator.make_token(user)
uidb64 = force_text(urlsafe_base64_encode(force_bytes(user.pk)))
response = password_reset_confirm(request, uidb64, token, post_reset_redirect='dummy/')
self.assertContains(response, '<title>Enter new password</title>')
self.assertContains(response, '<h1>Enter new password</h1>')
response = password_reset_complete(request)
self.assertContains(response, '<title>Password reset complete</title>')
self.assertContains(response, '<h1>Password reset complete</h1>')
response = password_change(request, post_change_redirect='dummy/')
self.assertContains(response, '<title>Password change</title>')
self.assertContains(response, '<h1>Password change</h1>')
response = password_change_done(request)
self.assertContains(response, '<title>Password change successful</title>')
self.assertContains(response, '<h1>Password change successful</h1>')

View File

@ -8,6 +8,7 @@ except ImportError: # Python 2
from django.conf import global_settings, settings
from django.contrib.sites.models import Site, RequestSite
from django.contrib.admin.models import LogEntry
from django.contrib.auth.models import User
from django.core import mail
from django.core.urlresolvers import reverse, NoReverseMatch
@ -54,6 +55,11 @@ class AuthViewsTestCase(TestCase):
self.assertTrue(SESSION_KEY in self.client.session)
return response
def logout(self):
response = self.client.get('/admin/logout/')
self.assertEqual(response.status_code, 200)
self.assertTrue(SESSION_KEY not in self.client.session)
def assertFormError(self, response, error):
"""Assert that error is found in response.context['form'] errors"""
form_errors = list(itertools.chain(*response.context['form'].errors.values()))
@ -122,6 +128,25 @@ class PasswordResetTest(AuthViewsTestCase):
self.assertEqual(len(mail.outbox), 1)
self.assertTrue("http://" in mail.outbox[0].body)
self.assertEqual(settings.DEFAULT_FROM_EMAIL, mail.outbox[0].from_email)
# optional multipart text/html email has been added. Make sure original,
# default functionality is 100% the same
self.assertFalse(mail.outbox[0].message().is_multipart())
def test_html_mail_template(self):
"""
A multipart email with text/plain and text/html is sent
if the html_email_template parameter is passed to the view
"""
response = self.client.post('/password_reset/html_email_template/', {'email': 'staffmember@example.com'})
self.assertEqual(response.status_code, 302)
self.assertEqual(len(mail.outbox), 1)
message = mail.outbox[0].message()
self.assertEqual(len(message.get_payload()), 2)
self.assertTrue(message.is_multipart())
self.assertEqual(message.get_payload(0).get_content_type(), 'text/plain')
self.assertEqual(message.get_payload(1).get_content_type(), 'text/html')
self.assertTrue('<html>' not in message.get_payload(0).get_payload())
self.assertTrue('<html>' in message.get_payload(1).get_payload())
def test_email_found_custom_from(self):
"Email is sent if a valid email address is provided for password reset when a custom from_email is provided."
@ -178,7 +203,7 @@ class PasswordResetTest(AuthViewsTestCase):
def _test_confirm_start(self):
# Start by creating the email
response = self.client.post('/password_reset/', {'email': 'staffmember@example.com'})
self.client.post('/password_reset/', {'email': 'staffmember@example.com'})
self.assertEqual(len(mail.outbox), 1)
return self._read_signup_email(mail.outbox[0])
@ -322,7 +347,7 @@ class ChangePasswordTest(AuthViewsTestCase):
})
def logout(self):
response = self.client.get('/logout/')
self.client.get('/logout/')
def test_password_change_fails_with_invalid_old_password(self):
self.login()
@ -344,7 +369,7 @@ class ChangePasswordTest(AuthViewsTestCase):
def test_password_change_succeeds(self):
self.login()
response = self.client.post('/password_change/', {
self.client.post('/password_change/', {
'old_password': 'password',
'new_password1': 'password1',
'new_password2': 'password1',
@ -459,7 +484,7 @@ class LoginTest(AuthViewsTestCase):
def test_login_form_contains_request(self):
# 15198
response = self.client.post('/custom_requestauth_login/', {
self.client.post('/custom_requestauth_login/', {
'username': 'testclient',
'password': 'password',
}, follow=True)
@ -670,18 +695,70 @@ class LogoutTest(AuthViewsTestCase):
self.confirm_logged_out()
@skipIfCustomUser
@override_settings(
PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',),
)
class ChangelistTests(AuthViewsTestCase):
urls = 'django.contrib.auth.tests.urls_admin'
def setUp(self):
# Make me a superuser before logging in.
User.objects.filter(username='testclient').update(is_staff=True, is_superuser=True)
self.login()
self.admin = User.objects.get(pk=1)
def get_user_data(self, user):
return {
'username': user.username,
'password': user.password,
'email': user.email,
'is_active': user.is_active,
'is_staff': user.is_staff,
'is_superuser': user.is_superuser,
'last_login_0': user.last_login.strftime('%Y-%m-%d'),
'last_login_1': user.last_login.strftime('%H:%M:%S'),
'initial-last_login_0': user.last_login.strftime('%Y-%m-%d'),
'initial-last_login_1': user.last_login.strftime('%H:%M:%S'),
'date_joined_0': user.date_joined.strftime('%Y-%m-%d'),
'date_joined_1': user.date_joined.strftime('%H:%M:%S'),
'initial-date_joined_0': user.date_joined.strftime('%Y-%m-%d'),
'initial-date_joined_1': user.date_joined.strftime('%H:%M:%S'),
'first_name': user.first_name,
'last_name': user.last_name,
}
# #20078 - users shouldn't be allowed to guess password hashes via
# repeated password__startswith queries.
def test_changelist_disallows_password_lookups(self):
# Make me a superuser before loging in.
User.objects.filter(username='testclient').update(is_staff=True, is_superuser=True)
self.login()
# A lookup that tries to filter on password isn't OK
with patch_logger('django.security.DisallowedModelAdminLookup', 'error') as logger_calls:
response = self.client.get('/admin/auth/user/?password__startswith=sha1$')
self.assertEqual(response.status_code, 400)
self.assertEqual(len(logger_calls), 1)
def test_user_change_email(self):
data = self.get_user_data(self.admin)
data['email'] = 'new_' + data['email']
response = self.client.post('/admin/auth/user/%s/' % self.admin.pk, data)
self.assertRedirects(response, '/admin/auth/user/')
row = LogEntry.objects.latest('id')
self.assertEqual(row.change_message, 'Changed email.')
def test_user_not_change(self):
response = self.client.post('/admin/auth/user/%s/' % self.admin.pk,
self.get_user_data(self.admin)
)
self.assertRedirects(response, '/admin/auth/user/')
row = LogEntry.objects.latest('id')
self.assertEqual(row.change_message, 'No fields changed.')
def test_user_change_password(self):
response = self.client.post('/admin/auth/user/%s/password/' % self.admin.pk, {
'password1': 'password1',
'password2': 'password1',
})
self.assertRedirects(response, '/admin/auth/user/%s/' % self.admin.pk)
row = LogEntry.objects.latest('id')
self.assertEqual(row.change_message, 'Changed password.')
self.logout()
self.login(password='password1')

View File

@ -67,6 +67,7 @@ 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'^password_reset/html_email_template/$', 'django.contrib.auth.views.password_reset', dict(html_email_template_name='registration/html_password_reset_email.html')),
(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/')),

View File

@ -7,10 +7,9 @@ from django.conf import settings
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect, QueryDict
from django.template.response import TemplateResponse
from django.utils.http import base36_to_int, is_safe_url, urlsafe_base64_decode, urlsafe_base64_encode
from django.utils.http import is_safe_url, urlsafe_base64_decode
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
@ -141,7 +140,8 @@ def password_reset(request, is_admin_site=False,
post_reset_redirect=None,
from_email=None,
current_app=None,
extra_context=None):
extra_context=None,
html_email_template_name=None):
if post_reset_redirect is None:
post_reset_redirect = reverse('password_reset_done')
else:
@ -156,6 +156,7 @@ def password_reset(request, is_admin_site=False,
'email_template_name': email_template_name,
'subject_template_name': subject_template_name,
'request': request,
'html_email_template_name': html_email_template_name,
}
if is_admin_site:
opts = dict(opts, domain_override=request.get_host())
@ -165,6 +166,7 @@ def password_reset(request, is_admin_site=False,
form = password_reset_form()
context = {
'form': form,
'title': _('Password reset'),
}
if extra_context is not None:
context.update(extra_context)
@ -175,7 +177,9 @@ def password_reset(request, is_admin_site=False,
def password_reset_done(request,
template_name='registration/password_reset_done.html',
current_app=None, extra_context=None):
context = {}
context = {
'title': _('Password reset successful'),
}
if extra_context is not None:
context.update(extra_context)
return TemplateResponse(request, template_name, context,
@ -209,6 +213,7 @@ def password_reset_confirm(request, uidb64=None, token=None,
if user is not None and token_generator.check_token(user, token):
validlink = True
title = _('Enter new password')
if request.method == 'POST':
form = set_password_form(user, request.POST)
if form.is_valid():
@ -219,8 +224,10 @@ def password_reset_confirm(request, uidb64=None, token=None,
else:
validlink = False
form = None
title = _('Password reset unsuccessful')
context = {
'form': form,
'title': title,
'validlink': validlink,
}
if extra_context is not None:
@ -232,7 +239,8 @@ def password_reset_complete(request,
template_name='registration/password_reset_complete.html',
current_app=None, extra_context=None):
context = {
'login_url': resolve_url(settings.LOGIN_URL)
'login_url': resolve_url(settings.LOGIN_URL),
'title': _('Password reset complete'),
}
if extra_context is not None:
context.update(extra_context)
@ -261,6 +269,7 @@ def password_change(request,
form = password_change_form(user=request.user)
context = {
'form': form,
'title': _('Password change'),
}
if extra_context is not None:
context.update(extra_context)
@ -272,7 +281,9 @@ def password_change(request,
def password_change_done(request,
template_name='registration/password_change_done.html',
current_app=None, extra_context=None):
context = {}
context = {
'title': _('Password change successful'),
}
if extra_context is not None:
context.update(extra_context)
return TemplateResponse(request, template_name, context,

View File

@ -1,10 +1,10 @@
from importlib import import_module
import warnings
from django.conf import settings
from django.core import urlresolvers
from django.core.exceptions import ImproperlyConfigured
from django.contrib.comments.models import Comment
from django.contrib.comments.forms import CommentForm
from django.utils.importlib import import_module
warnings.warn("django.contrib.comments is deprecated and will be removed before Django 1.8.", DeprecationWarning)

View File

@ -1,5 +1,3 @@
from __future__ import absolute_import
from django import http
from django.conf import settings
from django.contrib import comments

View File

@ -1,5 +1,3 @@
from __future__ import absolute_import
from django import template
from django.conf import settings
from django.contrib import comments

View File

@ -465,10 +465,10 @@ class GenericInlineModelAdmin(InlineModelAdmin):
formset = BaseGenericInlineFormSet
def get_formset(self, request, obj=None, **kwargs):
if self.declared_fieldsets:
fields = flatten_fieldsets(self.declared_fieldsets)
if 'fields' in kwargs:
fields = kwargs.pop('fields')
else:
fields = None
fields = flatten_fieldsets(self.get_fieldsets(request, obj))
if self.exclude is None:
exclude = []
else:

View File

@ -15,6 +15,7 @@ from django.test.utils import override_settings
'django.contrib.messages.middleware.MessageMiddleware',
'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware',
),
CSRF_FAILURE_VIEW='django.views.csrf.csrf_failure',
TEMPLATE_DIRS=(
os.path.join(os.path.dirname(__file__), 'templates'),
),

View File

@ -39,7 +39,7 @@ class FormPreview(object):
"""
while 1:
try:
f = self.form.base_fields[name]
self.form.base_fields[name]
except KeyError:
break # This field name isn't being used by the form.
name += '_'

View File

@ -2,8 +2,6 @@
This is a URLconf to be loaded by tests.py. Add any URLs needed for tests only.
"""
from __future__ import absolute_import
from django.conf.urls import patterns, url
from django.contrib.formtools.tests.tests import TestFormPreview

View File

@ -1,8 +1,8 @@
from datetime import datetime
from importlib import import_module
from django.http import HttpRequest
from django.conf import settings
from django.utils.importlib import import_module
from django.contrib.auth.models import User

View File

@ -1,11 +1,12 @@
from __future__ import unicode_literals
from importlib import import_module
from django import forms, http
from django.conf import settings
from django.db import models
from django.test import TestCase
from django.template.response import TemplateResponse
from django.utils.importlib import import_module
from django.contrib.auth.models import User

View File

@ -9,10 +9,9 @@ from django.forms.models import modelformset_factory
from django.http import HttpResponse
from django.template import Template, Context
from django.contrib.auth.models import User
from django.contrib.formtools.wizard.views import WizardView
temp_storage_location = tempfile.mkdtemp(dir=os.environ.get('DJANGO_TEST_TEMP_DIR'))
temp_storage = FileSystemStorage(location=temp_storage_location)

View File

@ -1,3 +1,4 @@
from collections import OrderedDict
import re
from django import forms
@ -5,7 +6,6 @@ from django.shortcuts import redirect
from django.core.urlresolvers import reverse
from django.forms import formsets, ValidationError
from django.views.generic import TemplateView
from django.utils.datastructures import SortedDict
from django.utils.decorators import classonlymethod
from django.utils.translation import ugettext as _
from django.utils import six
@ -17,7 +17,7 @@ from django.contrib.formtools.wizard.forms import ManagementForm
def normalize_name(name):
"""
Converts camel-case style names into underscore seperated words. Example::
Converts camel-case style names into underscore separated words. Example::
>>> normalize_name('oneTwoThree')
'one_two_three'
@ -158,7 +158,7 @@ class WizardView(TemplateView):
form_list = form_list or kwargs.pop('form_list',
getattr(cls, 'form_list', None)) or []
computed_form_list = SortedDict()
computed_form_list = OrderedDict()
assert len(form_list) > 0, 'at least one form is needed'
@ -206,7 +206,7 @@ class WizardView(TemplateView):
The form_list is always generated on the fly because condition methods
could use data from other (maybe previous forms).
"""
form_list = SortedDict()
form_list = OrderedDict()
for form_key, form_class in six.iteritems(self.form_list):
# try to fetch the value from condition list, by default, the form
# gets passed to the new list.
@ -498,9 +498,10 @@ class WizardView(TemplateView):
if step is None:
step = self.steps.current
form_list = self.get_form_list()
key = form_list.keyOrder.index(step) + 1
if len(form_list.keyOrder) > key:
return form_list.keyOrder[key]
keys = list(form_list.keys())
key = keys.index(step) + 1
if len(keys) > key:
return keys[key]
return None
def get_prev_step(self, step=None):
@ -512,9 +513,10 @@ class WizardView(TemplateView):
if step is None:
step = self.steps.current
form_list = self.get_form_list()
key = form_list.keyOrder.index(step) - 1
keys = list(form_list.keys())
key = keys.index(step) - 1
if key >= 0:
return form_list.keyOrder[key]
return keys[key]
return None
def get_step_index(self, step=None):
@ -524,7 +526,7 @@ class WizardView(TemplateView):
"""
if step is None:
step = self.steps.current
return self.get_form_list().keyOrder.index(step)
return list(self.get_form_list().keys()).index(step)
def get_context_data(self, form, **kwargs):
"""

View File

@ -55,7 +55,7 @@ class GeoModelAdmin(ModelAdmin):
3D editing).
"""
if isinstance(db_field, models.GeometryField) and db_field.dim < 3:
request = kwargs.pop('request', None)
kwargs.pop('request', None)
# Setting the widget with the newly defined widget.
kwargs['widget'] = self.get_map_widget(db_field)
return db_field.formfield(**kwargs)

View File

@ -1,10 +1,12 @@
import os
from django.conf import settings
from django.core.cache import get_cache
from django.core.cache.backends.db import BaseDatabaseCache
from django.core.exceptions import ImproperlyConfigured
from django.db.backends.sqlite3.creation import DatabaseCreation
class SpatiaLiteCreation(DatabaseCreation):
def create_test_db(self, verbosity=1, autoclobber=False):
@ -53,8 +55,6 @@ class SpatiaLiteCreation(DatabaseCreation):
interactive=False,
database=self.connection.alias)
from django.core.cache import get_cache
from django.core.cache.backends.db import BaseDatabaseCache
for cache_alias in settings.CACHES:
cache = get_cache(cache_alias)
if isinstance(cache, BaseDatabaseCache):
@ -62,7 +62,7 @@ class SpatiaLiteCreation(DatabaseCreation):
# Get a cursor (even though we don't need one yet). This has
# the side effect of initializing the test database.
cursor = self.connection.cursor()
self.connection.cursor()
return test_database_name

View File

@ -148,6 +148,7 @@ class GeometryField(Field):
value properly, and preserve any other lookup parameters before
returning to the caller.
"""
value = super(GeometryField, self).get_prep_value(value)
if isinstance(value, SQLEvaluator):
return value
elif isinstance(value, (tuple, list)):

View File

@ -101,7 +101,7 @@ class OGRGeometry(GDALBase):
else:
# Seeing if the input is a valid short-hand string
# (e.g., 'Point', 'POLYGON').
ogr_t = OGRGeomType(geom_input)
OGRGeomType(geom_input)
g = capi.create_geom(OGRGeomType(geom_input).num)
elif isinstance(geom_input, memoryview):
# WKB was passed in
@ -341,7 +341,7 @@ class OGRGeometry(GDALBase):
sz = self.wkb_size
# Creating the unsigned character buffer, and passing it in by reference.
buf = (c_ubyte * sz)()
wkb = capi.to_wkb(self.ptr, byteorder, byref(buf))
capi.to_wkb(self.ptr, byteorder, byref(buf))
# Returning a buffer of the string at the pointer.
return memoryview(string_at(buf, sz))

View File

@ -22,9 +22,9 @@ class EnvelopeTest(unittest.TestCase):
def test01_init(self):
"Testing Envelope initilization."
e1 = Envelope((0, 0, 5, 5))
e2 = Envelope(0, 0, 5, 5)
e3 = Envelope(0, '0', '5', 5) # Thanks to ww for this
e4 = Envelope(e1._envelope)
Envelope(0, 0, 5, 5)
Envelope(0, '0', '5', 5) # Thanks to ww for this
Envelope(e1._envelope)
self.assertRaises(OGRException, Envelope, (5, 5, 0, 0))
self.assertRaises(OGRException, Envelope, 5, 5, 0, 0)
self.assertRaises(OGRException, Envelope, (0, 0, 5, 5, 3))

View File

@ -25,15 +25,12 @@ class OGRGeomTest(unittest.TestCase, TestDataMixin):
"Testing OGRGeomType object."
# OGRGeomType should initialize on all these inputs.
try:
g = OGRGeomType(1)
g = OGRGeomType(7)
g = OGRGeomType('point')
g = OGRGeomType('GeometrycollectioN')
g = OGRGeomType('LINearrING')
g = OGRGeomType('Unknown')
except:
self.fail('Could not create an OGRGeomType object!')
OGRGeomType(1)
OGRGeomType(7)
OGRGeomType('point')
OGRGeomType('GeometrycollectioN')
OGRGeomType('LINearrING')
OGRGeomType('Unknown')
# Should throw TypeError on this input
self.assertRaises(OGRException, OGRGeomType, 23)
@ -127,7 +124,7 @@ class OGRGeomTest(unittest.TestCase, TestDataMixin):
def test02_points(self):
"Testing Point objects."
prev = OGRGeometry('POINT(0 0)')
OGRGeometry('POINT(0 0)')
for p in self.geometries.points:
if not hasattr(p, 'z'): # No 3D
pnt = OGRGeometry(p.wkt)
@ -243,7 +240,7 @@ class OGRGeomTest(unittest.TestCase, TestDataMixin):
poly = OGRGeometry('POLYGON((0 0, 5 0, 5 5, 0 5), (1 1, 2 1, 2 2, 2 1))')
self.assertEqual(8, poly.point_count)
with self.assertRaises(OGRException):
_ = poly.centroid
poly.centroid
poly.close_rings()
self.assertEqual(10, poly.point_count) # Two closing points should've been added
@ -251,7 +248,7 @@ class OGRGeomTest(unittest.TestCase, TestDataMixin):
def test08_multipolygons(self):
"Testing MultiPolygon objects."
prev = OGRGeometry('POINT(0 0)')
OGRGeometry('POINT(0 0)')
for mp in self.geometries.multipolygons:
mpoly = OGRGeometry(mp.wkt)
self.assertEqual(6, mpoly.geom_type)

View File

@ -58,7 +58,7 @@ class SpatialRefTest(unittest.TestCase):
def test01_wkt(self):
"Testing initialization on valid OGC WKT."
for s in srlist:
srs = SpatialReference(s.wkt)
SpatialReference(s.wkt)
def test02_bad_wkt(self):
"Testing initialization on invalid WKT."
@ -150,7 +150,7 @@ class SpatialRefTest(unittest.TestCase):
target = SpatialReference('WGS84')
for s in srlist:
if s.proj:
ct = CoordTransform(SpatialReference(s.wkt), target)
CoordTransform(SpatialReference(s.wkt), target)
def test13_attr_value(self):
"Testing the attr_value() method."

View File

@ -11,8 +11,6 @@
Grab GeoIP.dat.gz and GeoLiteCity.dat.gz, and unzip them in the directory
corresponding to settings.GEOIP_PATH.
"""
from __future__ import absolute_import
try:
from .base import GeoIP, GeoIPException
HAS_GEOIP = True

View File

@ -18,7 +18,8 @@ free_regex = re.compile(r'^GEO-\d{3}FREE')
lite_regex = re.compile(r'^GEO-\d{3}LITE')
#### GeoIP classes ####
class GeoIPException(Exception): pass
class GeoIPException(Exception):
pass
class GeoIP(object):
# The flags for GeoIP memory caching.

View File

@ -14,7 +14,7 @@ class GeoIPRecord(Structure):
('longitude', c_float),
# TODO: In 1.4.6 this changed from `int dma_code;` to
# `union {int metro_code; int dma_code;};`. Change
# to a `ctypes.Union` in to accomodate in future when
# to a `ctypes.Union` in to accommodate in future when
# pre-1.4.6 versions are no longer distributed.
('dma_code', c_int),
('area_code', c_int),

View File

@ -1,11 +1,12 @@
from importlib import import_module
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.utils.importlib import import_module
geom_backend = getattr(settings, 'GEOMETRY_BACKEND', 'geos')
try:
module = import_module('.%s' % geom_backend, 'django.contrib.gis.geometry.backend')
module = import_module('django.contrib.gis.geometry.backend.%s' % geom_backend)
except ImportError:
try:
module = import_module(geom_backend)

View File

@ -18,7 +18,6 @@ from django.contrib.gis.geos.base import GEOSBase, gdal
from django.contrib.gis.geos.coordseq import GEOSCoordSeq
from django.contrib.gis.geos.error import GEOSException, GEOSIndexError
from django.contrib.gis.geos.libgeos import GEOM_PTR, GEOS_PREPARE
from django.contrib.gis.geos.mutable_list import ListMixin
# All other functions in this module come from the ctypes
# prototypes module -- which handles all interaction with

View File

@ -124,24 +124,16 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
self.assertEqual(hexewkb_3d, pnt_3d.hexewkb)
self.assertEqual(True, GEOSGeometry(hexewkb_3d).hasz)
else:
try:
hexewkb = pnt_3d.hexewkb
except GEOSException:
pass
else:
self.fail('Should have raised GEOSException.')
with self.assertRaises(GEOSException):
pnt_3d.hexewkb
# Same for EWKB.
self.assertEqual(memoryview(a2b_hex(hexewkb_2d)), pnt_2d.ewkb)
if GEOS_PREPARE:
self.assertEqual(memoryview(a2b_hex(hexewkb_3d)), pnt_3d.ewkb)
else:
try:
ewkb = pnt_3d.ewkb
except GEOSException:
pass
else:
self.fail('Should have raised GEOSException')
with self.assertRaises(GEOSException):
pnt_3d.ewkb
# Redundant sanity check.
self.assertEqual(4326, GEOSGeometry(hexewkb_2d).srid)
@ -158,7 +150,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
# string-based
for err in self.geometries.errors:
with self.assertRaises((GEOSException, ValueError)):
_ = fromstr(err.wkt)
fromstr(err.wkt)
# Bad WKB
self.assertRaises(GEOSException, GEOSGeometry, memoryview(b'0'))

View File

@ -1,4 +1,4 @@
from __future__ import absolute_import
from __future__ import unicode_literals
from unittest import skipUnless

View File

@ -1,4 +1,4 @@
from __future__ import absolute_import, unicode_literals
from __future__ import unicode_literals
import os
import re

View File

@ -1,4 +1,4 @@
from __future__ import absolute_import
from __future__ import unicode_literals
from unittest import skipUnless

View File

@ -1,4 +1,4 @@
from __future__ import absolute_import
from __future__ import unicode_literals
from django.contrib.gis import feeds

View File

@ -1,5 +1,3 @@
from __future__ import absolute_import
from django.contrib.gis.sitemaps import GeoRSSSitemap, KMLSitemap, KMZSitemap
from .feeds import feed_dict

View File

@ -1,4 +1,4 @@
from __future__ import absolute_import
from __future__ import unicode_literals
from unittest import skipUnless
from xml.dom import minidom

View File

@ -1,5 +1,5 @@
# -*- encoding: utf-8 -*-
from __future__ import absolute_import, unicode_literals
from __future__ import unicode_literals
from datetime import datetime
from unittest import skipUnless

View File

@ -1,4 +1,4 @@
from __future__ import absolute_import
from __future__ import unicode_literals
from io import BytesIO
from unittest import skipUnless

View File

@ -1,4 +1,4 @@
from __future__ import absolute_import
from __future__ import unicode_literals
import re
import unittest

View File

@ -1,4 +1,4 @@
from __future__ import absolute_import
from __future__ import unicode_literals
from django.conf.urls import patterns

View File

@ -1,7 +1,7 @@
"""
Tests for geography support in PostGIS 1.5+
"""
from __future__ import absolute_import
from __future__ import unicode_literals
import os
from unittest import skipUnless

View File

@ -1,4 +1,4 @@
from __future__ import absolute_import
from __future__ import unicode_literals
import os
from unittest import skipUnless

View File

@ -1,5 +1,5 @@
# coding: utf-8
from __future__ import absolute_import, unicode_literals
from __future__ import unicode_literals
from copy import copy
from decimal import Decimal

View File

@ -1,4 +1,4 @@
from __future__ import absolute_import
from __future__ import unicode_literals
from unittest import skipUnless

View File

@ -24,7 +24,7 @@ test_srs = ({'srid' : 4326,
'srtext' : 'PROJCS["NAD83 / Texas South Central",GEOGCS["NAD83",DATUM["North_American_Datum_1983",SPHEROID["GRS 1980"',
'proj4_re' : r'\+proj=lcc \+lat_1=30.28333333333333 \+lat_2=28.38333333333333 \+lat_0=27.83333333333333 '
r'\+lon_0=-99 \+x_0=600000 \+y_0=4000000 (\+ellps=GRS80 )?'
r'(\+datum=NAD83 |\+towgs84=0,0,0,0,0,0,0)?\+units=m \+no_defs ',
r'(\+datum=NAD83 |\+towgs84=0,0,0,0,0,0,0 )?\+units=m \+no_defs ',
'spheroid' : 'GRS 1980', 'name' : 'NAD83 / Texas South Central',
'geographic' : False, 'projected' : True, 'spatialite' : False,
'ellipsoid' : (6378137.0, 6356752.31414, 298.257222101), # From proj's "cs2cs -le" and Wikipedia (semi-minor only)

View File

@ -1,4 +1,2 @@
from __future__ import absolute_import
from django.contrib.messages.api import *
from django.contrib.messages.constants import *

View File

@ -1,6 +1,7 @@
from importlib import import_module
from django.conf import settings
from django.core.management.base import NoArgsCommand
from django.utils.importlib import import_module
class Command(NoArgsCommand):

View File

@ -1,9 +1,9 @@
from importlib import import_module
import time
from django.conf import settings
from django.utils.cache import patch_vary_headers
from django.utils.http import cookie_date
from django.utils.importlib import import_module
class SessionMiddleware(object):
def __init__(self):

View File

@ -86,17 +86,27 @@ class Sitemap(object):
domain = site.domain
urls = []
latest_lastmod = None
all_items_lastmod = True # track if all items have a lastmod
for item in self.paginator.page(page).object_list:
loc = "%s://%s%s" % (protocol, domain, self.__get('location', item))
priority = self.__get('priority', item, None)
lastmod = self.__get('lastmod', item, None)
if all_items_lastmod:
all_items_lastmod = lastmod is not None
if (all_items_lastmod and
(latest_lastmod is None or lastmod > latest_lastmod)):
latest_lastmod = lastmod
url_info = {
'item': item,
'location': loc,
'lastmod': self.__get('lastmod', item, None),
'lastmod': lastmod,
'changefreq': self.__get('changefreq', item, None),
'priority': str(priority if priority is not None else ''),
}
urls.append(url_info)
if all_items_lastmod:
self.latest_lastmod = latest_lastmod
return urls
class FlatPageSitemap(Sitemap):

View File

@ -77,6 +77,21 @@ class HTTPSitemapTests(SitemapTestsBase):
""" % (self.base_url, date.today())
self.assertXMLEqual(response.content.decode('utf-8'), expected_content)
def test_sitemap_last_modified(self):
"Tests that Last-Modified header is set correctly"
response = self.client.get('/lastmod/sitemap.xml')
self.assertEqual(response['Last-Modified'], 'Wed, 13 Mar 2013 10:00:00 GMT')
def test_sitemap_last_modified_missing(self):
"Tests that Last-Modified header is missing when sitemap has no lastmod"
response = self.client.get('/generic/sitemap.xml')
self.assertFalse(response.has_header('Last-Modified'))
def test_sitemap_last_modified_mixed(self):
"Tests that Last-Modified header is omitted when lastmod not on all items"
response = self.client.get('/lastmod-mixed/sitemap.xml')
self.assertFalse(response.has_header('Last-Modified'))
@skipUnless(settings.USE_I18N, "Internationalization is not enabled")
@override_settings(USE_L10N=True)
def test_localized_priority(self):

View File

@ -15,10 +15,36 @@ class SimpleSitemap(Sitemap):
def items(self):
return [object()]
class FixedLastmodSitemap(SimpleSitemap):
lastmod = datetime(2013, 3, 13, 10, 0, 0)
class FixedLastmodMixedSitemap(Sitemap):
changefreq = "never"
priority = 0.5
location = '/location/'
loop = 0
def items(self):
o1 = TestModel()
o1.lastmod = datetime(2013, 3, 13, 10, 0, 0)
o2 = TestModel()
return [o1, o2]
simple_sitemaps = {
'simple': SimpleSitemap,
}
fixed_lastmod_sitemaps = {
'fixed-lastmod': FixedLastmodSitemap,
}
fixed_lastmod__mixed_sitemaps = {
'fixed-lastmod-mixed': FixedLastmodMixedSitemap,
}
generic_sitemaps = {
'generic': GenericSitemap({'queryset': TestModel.objects.all()}),
}
@ -36,6 +62,8 @@ urlpatterns = patterns('django.contrib.sitemaps.views',
(r'^simple/sitemap\.xml$', 'sitemap', {'sitemaps': simple_sitemaps}),
(r'^simple/custom-sitemap\.xml$', 'sitemap',
{'sitemaps': simple_sitemaps, 'template_name': 'custom_sitemap.xml'}),
(r'^lastmod/sitemap\.xml$', 'sitemap', {'sitemaps': fixed_lastmod_sitemaps}),
(r'^lastmod-mixed/sitemap\.xml$', 'sitemap', {'sitemaps': fixed_lastmod__mixed_sitemaps}),
(r'^generic/sitemap\.xml$', 'sitemap', {'sitemaps': generic_sitemaps}),
(r'^flatpages/sitemap\.xml$', 'sitemap', {'sitemaps': flatpage_sitemaps}),
url(r'^cached/index\.xml$', cache_page(1)(views.index),

View File

@ -1,3 +1,4 @@
from calendar import timegm
from functools import wraps
from django.contrib.sites.models import get_current_site
@ -6,6 +7,7 @@ from django.core.paginator import EmptyPage, PageNotAnInteger
from django.http import Http404
from django.template.response import TemplateResponse
from django.utils import six
from django.utils.http import http_date
def x_robots_tag(func):
@wraps(func)
@ -64,5 +66,11 @@ def sitemap(request, sitemaps, section=None,
raise Http404("Page %s empty" % page)
except PageNotAnInteger:
raise Http404("No page '%s'" % page)
return TemplateResponse(request, template_name, {'urlset': urls},
content_type=content_type)
response = TemplateResponse(request, template_name, {'urlset': urls},
content_type=content_type)
if hasattr(site, 'latest_lastmod'):
# if latest_lastmod is defined for site, set header so as
# ConditionalGetMiddleware is able to send 304 NOT MODIFIED
response['Last-Modified'] = http_date(
timegm(site.latest_lastmod.utctimetuple()))
return response

View File

@ -1,8 +1,9 @@
from collections import OrderedDict
import os
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.core.files.storage import default_storage, Storage, FileSystemStorage
from django.utils.datastructures import SortedDict
from django.utils.functional import empty, memoize, LazyObject
from django.utils.module_loading import import_by_path
from django.utils._os import safe_join
@ -11,7 +12,7 @@ from django.utils import six
from django.contrib.staticfiles import utils
from django.contrib.staticfiles.storage import AppStaticStorage
_finders = SortedDict()
_finders = OrderedDict()
class BaseFinder(object):
@ -47,7 +48,7 @@ class FileSystemFinder(BaseFinder):
# List of locations with static files
self.locations = []
# Maps dir paths to an appropriate storage instance
self.storages = SortedDict()
self.storages = OrderedDict()
if not isinstance(settings.STATICFILES_DIRS, (list, tuple)):
raise ImproperlyConfigured(
"Your STATICFILES_DIRS setting is not a tuple or list; "
@ -118,7 +119,7 @@ class AppDirectoriesFinder(BaseFinder):
# The list of apps that are handled
self.apps = []
# Mapping of app module paths to storage instances
self.storages = SortedDict()
self.storages = OrderedDict()
if apps is None:
apps = settings.INSTALLED_APPS
for app in apps:

View File

@ -2,12 +2,12 @@ from __future__ import unicode_literals
import os
import sys
from collections import OrderedDict
from optparse import make_option
from django.core.files.storage import FileSystemStorage
from django.core.management.base import CommandError, NoArgsCommand
from django.utils.encoding import smart_text
from django.utils.datastructures import SortedDict
from django.utils.six.moves import input
from django.contrib.staticfiles import finders, storage
@ -97,7 +97,7 @@ class Command(NoArgsCommand):
else:
handler = self.copy_file
found_files = SortedDict()
found_files = OrderedDict()
for finder in finders.get_finders():
for path, storage in finder.list(self.ignore_patterns):
# Prefix the relative path if the source storage contains it

View File

@ -1,5 +1,7 @@
from __future__ import unicode_literals
from collections import OrderedDict
import hashlib
from importlib import import_module
import os
import posixpath
import re
@ -15,10 +17,8 @@ from django.core.cache import (get_cache, InvalidCacheBackendError,
from django.core.exceptions import ImproperlyConfigured
from django.core.files.base import ContentFile
from django.core.files.storage import FileSystemStorage, get_storage_class
from django.utils.datastructures import SortedDict
from django.utils.encoding import force_bytes, force_text
from django.utils.functional import LazyObject
from django.utils.importlib import import_module
from django.utils._os import upath
from django.contrib.staticfiles.utils import check_settings, matches_patterns
@ -64,7 +64,7 @@ class CachedFilesMixin(object):
except InvalidCacheBackendError:
# Use the default backend
self.cache = default_cache
self._patterns = SortedDict()
self._patterns = OrderedDict()
for extension, patterns in self.patterns:
for pattern in patterns:
if isinstance(pattern, (tuple, list)):
@ -202,7 +202,7 @@ class CachedFilesMixin(object):
def post_process(self, paths, dry_run=False, **options):
"""
Post process the given list of files (called from collectstatic).
Post process the given OrderedDict of files (called from collectstatic).
Processing is actually two separate operations:

View File

@ -11,7 +11,6 @@ except ImportError: # Python 2
from urllib import unquote
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.http import Http404
from django.views import static
@ -31,9 +30,7 @@ def serve(request, path, insecure=False, **kwargs):
It uses the django.views.static view to serve the found files.
"""
if not settings.DEBUG and not insecure:
raise ImproperlyConfigured("The staticfiles view can only be used in "
"debug mode or if the --insecure "
"option of 'runserver' is used")
raise Http404
normalized_path = posixpath.normpath(unquote(path)).lstrip('/')
absolute_path = finders.find(normalized_path)
if not absolute_path:

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