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
3
AUTHORS
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Before Width: | Height: | Size: 199 B |
Before Width: | Height: | Size: 212 B |
Before Width: | Height: | Size: 197 B |
Before Width: | Height: | Size: 203 B |
Before Width: | Height: | Size: 198 B |
Before Width: | Height: | Size: 200 B |
Before Width: | Height: | Size: 932 B |
After Width: | Height: | Size: 967 B |
Before Width: | Height: | Size: 336 B |
Before Width: | Height: | Size: 351 B |
After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 354 B |
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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(' ')
|
||||
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]
|
||||
|
|
|
@ -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']:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import unittest
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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('..')
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)),
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<html><a href="{{ protocol }}://{{ domain }}/reset/{{ uid }}/{{ token }}/">Link</a></html>
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
||||
|
|
|
@ -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>')
|
|
@ -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')
|
||||
|
|
|
@ -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/')),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from django import http
|
||||
from django.conf import settings
|
||||
from django.contrib import comments
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from django import template
|
||||
from django.conf import settings
|
||||
from django.contrib import comments
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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'),
|
||||
),
|
||||
|
|
|
@ -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 += '_'
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)):
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'))
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from unittest import skipUnless
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import re
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from unittest import skipUnless
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.contrib.gis import feeds
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from django.contrib.gis.sitemaps import GeoRSSSitemap, KMLSitemap, KMZSitemap
|
||||
|
||||
from .feeds import feed_dict
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from unittest import skipUnless
|
||||
from xml.dom import minidom
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from io import BytesIO
|
||||
from unittest import skipUnless
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
import unittest
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf.urls import patterns
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
from unittest import skipUnless
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from unittest import skipUnless
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,4 +1,2 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from django.contrib.messages.api import *
|
||||
from django.contrib.messages.constants import *
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|