summaryrefslogtreecommitdiff
path: root/django/contrib/admin
diff options
context:
space:
mode:
authorJustin Bronn <jbronn@gmail.com>2008-08-05 17:15:33 +0000
committerJustin Bronn <jbronn@gmail.com>2008-08-05 17:15:33 +0000
commitaa239e3e5405933af6a29dac3cf587b59a099927 (patch)
treeea2cbd139c9a8cf84c09e0b2008bff70e05927ef /django/contrib/admin
parent45b73c9a4685809236f84046cc7ffd32a50db958 (diff)
gis: Merged revisions 7981-8001,8003-8011,8013-8033,8035-8036,8038-8039,8041-8063,8065-8076,8078-8139,8141-8154,8156-8214 via svnmerge from trunk.archive/attic/gis
git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@8215 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django/contrib/admin')
-rw-r--r--django/contrib/admin/__init__.py7
-rw-r--r--django/contrib/admin/media/css/rtl.css3
-rw-r--r--django/contrib/admin/options.py99
-rw-r--r--django/contrib/admin/sites.py68
-rw-r--r--django/contrib/admin/templates/admin/includes/fieldset.html4
-rw-r--r--django/contrib/admin/templates/admin_doc/model_detail.html2
-rw-r--r--django/contrib/admin/templates/registration/password_reset_complete.html16
-rw-r--r--django/contrib/admin/templates/registration/password_reset_confirm.html32
-rw-r--r--django/contrib/admin/templates/registration/password_reset_done.html2
-rw-r--r--django/contrib/admin/templates/registration/password_reset_email.html14
-rw-r--r--django/contrib/admin/templates/registration/password_reset_form.html2
-rw-r--r--django/contrib/admin/validation.py11
-rw-r--r--django/contrib/admin/views/decorators.py8
-rw-r--r--django/contrib/admin/views/main.py1
-rw-r--r--django/contrib/admin/widgets.py3
15 files changed, 180 insertions, 92 deletions
diff --git a/django/contrib/admin/__init__.py b/django/contrib/admin/__init__.py
index 56b64faacb..704dc58ee4 100644
--- a/django/contrib/admin/__init__.py
+++ b/django/contrib/admin/__init__.py
@@ -8,9 +8,12 @@ def autodiscover():
not present. This forces an import on them to register any admin bits they
may want.
"""
+ import imp
from django.conf import settings
for app in settings.INSTALLED_APPS:
try:
- __import__("%s.admin" % app)
+ imp.find_module("admin", __import__(app, {}, {}, [app.split(".")[-1]]).__path__)
except ImportError:
- pass
+ # there is no app admin.py, skip it
+ continue
+ __import__("%s.admin" % app)
diff --git a/django/contrib/admin/media/css/rtl.css b/django/contrib/admin/media/css/rtl.css
index 1974e7c2ec..54175a7f9a 100644
--- a/django/contrib/admin/media/css/rtl.css
+++ b/django/contrib/admin/media/css/rtl.css
@@ -44,3 +44,6 @@ div.breadcrumbs { text-align:right; }
.selector { float: right;}
.selector .selector-filter { text-align: right;}
+
+/* x unsorted */
+.inline-related h2 { text-align:right }
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index 501014a0d5..d161c58e64 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -1,5 +1,4 @@
-from django import oldforms, template
-from django import forms
+from django import forms, template
from django.forms.formsets import all_valid
from django.forms.models import modelform_factory, inlineformset_factory
from django.forms.models import BaseInlineFormset
@@ -15,7 +14,10 @@ from django.utils.safestring import mark_safe
from django.utils.text import capfirst, get_text_list
from django.utils.translation import ugettext as _
from django.utils.encoding import force_unicode
-import sets
+try:
+ set
+except NameError:
+ from sets import Set as set # Python 2.3 fallback
HORIZONTAL, VERTICAL = 1, 2
# returns the <ul> class for a given radio_admin field
@@ -90,7 +92,7 @@ class Fieldline(object):
yield AdminField(self.form, field, is_first=(i == 0))
def errors(self):
- return mark_safe(u'\n'.join([self.form[f].errors.as_ul() for f in self.fields]))
+ return mark_safe(u'\n'.join([self.form[f].errors.as_ul() for f in self.fields]).strip('\n'))
class AdminField(object):
def __init__(self, form, field, is_first):
@@ -130,6 +132,23 @@ class BaseModelAdmin(object):
If kwargs are given, they're passed to the form Field's constructor.
"""
+
+ # If the field specifies choices, we don't need to look for special
+ # admin widgets - we just need to use a select widget of some kind.
+ if db_field.choices:
+ if db_field.name in self.radio_fields:
+ # If the field is named as a radio_field, use a RadioSelect
+ kwargs['widget'] = widgets.AdminRadioSelect(
+ choices=db_field.get_choices(include_blank=db_field.blank,
+ blank_choice=[('', _('None'))]),
+ attrs={
+ 'class': get_ul_class(self.radio_fields[db_field.name]),
+ }
+ )
+ else:
+ # Otherwise, use the default select widget.
+ return db_field.formfield(**kwargs)
+
# For DateTimeFields, use a special field and widget.
if isinstance(db_field, models.DateTimeField):
kwargs['form_class'] = forms.SplitDateTimeField
@@ -162,10 +181,13 @@ class BaseModelAdmin(object):
kwargs['empty_label'] = db_field.blank and _('None') or None
else:
if isinstance(db_field, models.ManyToManyField):
- if db_field.name in self.raw_id_fields:
+ # If it uses an intermediary model, don't show field in admin.
+ if db_field.rel.through is not None:
+ return None
+ elif db_field.name in self.raw_id_fields:
kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel)
kwargs['help_text'] = ''
- elif db_field.name in (self.filter_vertical + self.filter_horizontal):
+ elif db_field.name in (list(self.filter_vertical) + list(self.filter_horizontal)):
kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, (db_field.name in self.filter_vertical))
# Wrap the widget's render() method with a method that adds
# extra HTML to the end of the rendered output.
@@ -174,15 +196,6 @@ class BaseModelAdmin(object):
if not db_field.name in self.raw_id_fields:
formfield.widget = widgets.RelatedFieldWidgetWrapper(formfield.widget, db_field.rel, self.admin_site)
return formfield
-
- if db_field.choices and db_field.name in self.radio_fields:
- kwargs['widget'] = widgets.AdminRadioSelect(
- choices=db_field.get_choices(include_blank=db_field.blank,
- blank_choice=[('', _('None'))]),
- attrs={
- 'class': get_ul_class(self.radio_fields[db_field.name]),
- }
- )
# For any other type of field, just call its formfield() method.
return db_field.formfield(**kwargs)
@@ -210,7 +223,7 @@ class ModelAdmin(BaseModelAdmin):
save_on_top = False
ordering = None
inlines = []
-
+
# Custom templates (designed to be over-ridden in subclasses)
change_form_template = None
change_list_template = None
@@ -261,7 +274,7 @@ class ModelAdmin(BaseModelAdmin):
js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.js'])
if self.filter_vertical or self.filter_horizontal:
js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js'])
-
+
return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js])
media = property(_media)
@@ -345,7 +358,7 @@ class ModelAdmin(BaseModelAdmin):
pk_value = new_object._get_pk_val()
LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(self.model).id, pk_value, force_unicode(new_object), ADDITION)
- msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': opts.verbose_name, 'obj': new_object}
+ msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': new_object}
# Here, we distinguish between different save types by checking for
# the presence of keys in request.POST.
if request.POST.has_key("_continue"):
@@ -359,7 +372,7 @@ class ModelAdmin(BaseModelAdmin):
# escape() calls force_unicode.
(escape(pk_value), escape(new_object)))
elif request.POST.has_key("_addanother"):
- request.user.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % opts.verbose_name))
+ request.user.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name)))
return HttpResponseRedirect(request.path)
else:
request.user.message_set.create(message=msg)
@@ -378,7 +391,7 @@ class ModelAdmin(BaseModelAdmin):
Saves the object in the "change" stage and returns an HttpResponseRedirect.
`form` is a bound Form instance that's verified to be valid.
-
+
`formsets` is a sequence of InlineFormSet instances that are verified to be valid.
"""
from django.contrib.admin.models import LogEntry, CHANGE
@@ -394,20 +407,20 @@ class ModelAdmin(BaseModelAdmin):
change_message = []
if form.changed_data:
change_message.append(_('Changed %s.') % get_text_list(form.changed_data, _('and')))
-
+
if formsets:
for formset in formsets:
for added_object in formset.new_objects:
- change_message.append(_('Added %(name)s "%(object)s".')
+ change_message.append(_('Added %(name)s "%(object)s".')
% {'name': added_object._meta.verbose_name,
'object': added_object})
for changed_object, changed_fields in formset.changed_objects:
- change_message.append(_('Changed %(list)s for %(name)s "%(object)s".')
- % {'list': get_text_list(changed_fields, _('and')),
- 'name': changed_object._meta.verbose_name,
+ change_message.append(_('Changed %(list)s for %(name)s "%(object)s".')
+ % {'list': get_text_list(changed_fields, _('and')),
+ 'name': changed_object._meta.verbose_name,
'object': changed_object})
for deleted_object in formset.deleted_objects:
- change_message.append(_('Deleted %(name)s "%(object)s".')
+ change_message.append(_('Deleted %(name)s "%(object)s".')
% {'name': deleted_object._meta.verbose_name,
'object': deleted_object})
change_message = ' '.join(change_message)
@@ -415,7 +428,7 @@ class ModelAdmin(BaseModelAdmin):
change_message = _('No fields changed.')
LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(self.model).id, pk_value, force_unicode(new_object), CHANGE, change_message)
- msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': opts.verbose_name, 'obj': new_object}
+ msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': new_object}
if request.POST.has_key("_continue"):
request.user.message_set.create(message=msg + ' ' + _("You may edit it again below."))
if request.REQUEST.has_key('_popup'):
@@ -423,10 +436,10 @@ class ModelAdmin(BaseModelAdmin):
else:
return HttpResponseRedirect(request.path)
elif request.POST.has_key("_saveasnew"):
- request.user.message_set.create(message=_('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': opts.verbose_name, 'obj': new_object})
+ request.user.message_set.create(message=_('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': force_unicode(opts.verbose_name), 'obj': new_object})
return HttpResponseRedirect("../%s/" % pk_value)
elif request.POST.has_key("_addanother"):
- request.user.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % opts.verbose_name))
+ request.user.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name)))
return HttpResponseRedirect("../add/")
else:
request.user.message_set.create(message=msg)
@@ -504,7 +517,7 @@ class ModelAdmin(BaseModelAdmin):
inline_admin_formsets.append(inline_admin_formset)
context = {
- 'title': _('Add %s') % opts.verbose_name,
+ 'title': _('Add %s') % force_unicode(opts.verbose_name),
'adminform': adminForm,
'is_popup': request.REQUEST.has_key('_popup'),
'show_delete': False,
@@ -534,7 +547,7 @@ class ModelAdmin(BaseModelAdmin):
raise PermissionDenied
if obj is None:
- raise Http404('%s object with primary key %r does not exist.' % (opts.verbose_name, escape(object_id)))
+ raise Http404('%s object with primary key %r does not exist.' % (force_unicode(opts.verbose_name), escape(object_id)))
if request.POST and request.POST.has_key("_saveasnew"):
return self.add_view(request, form_url='../../add/')
@@ -557,17 +570,16 @@ class ModelAdmin(BaseModelAdmin):
adminForm = AdminForm(form, self.get_fieldsets(request, obj), self.prepopulated_fields)
media = self.media + adminForm.media
- for fs in inline_formsets:
- media = media + fs.media
inline_admin_formsets = []
for inline, formset in zip(self.inline_instances, inline_formsets):
fieldsets = list(inline.get_fieldsets(request, obj))
inline_admin_formset = InlineAdminFormSet(inline, formset, fieldsets)
inline_admin_formsets.append(inline_admin_formset)
+ media = media + inline_admin_formset.media
context = {
- 'title': _('Change %s') % opts.verbose_name,
+ 'title': _('Change %s') % force_unicode(opts.verbose_name),
'adminform': adminForm,
'object_id': object_id,
'original': obj,
@@ -599,7 +611,7 @@ class ModelAdmin(BaseModelAdmin):
if ERROR_FLAG in request.GET.keys():
return render_to_response('admin/invalid_setup.html', {'title': _('Database error')})
return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1')
-
+
context = {
'title': cl.title,
'is_popup': cl.is_popup,
@@ -632,12 +644,12 @@ class ModelAdmin(BaseModelAdmin):
raise PermissionDenied
if obj is None:
- raise Http404('%s object with primary key %r does not exist.' % (opts.verbose_name, escape(object_id)))
+ raise Http404('%s object with primary key %r does not exist.' % (force_unicode(opts.verbose_name), escape(object_id)))
# Populate deleted_objects, a data structure of all related objects that
# will also be deleted.
deleted_objects = [mark_safe(u'%s: <a href="../../%s/">%s</a>' % (escape(force_unicode(capfirst(opts.verbose_name))), quote(object_id), escape(obj))), []]
- perms_needed = sets.Set()
+ perms_needed = set()
get_deleted_objects(deleted_objects, perms_needed, request.user, obj, opts, 1, self.admin_site)
if request.POST: # The user has already confirmed the deletion.
@@ -650,10 +662,10 @@ class ModelAdmin(BaseModelAdmin):
if not self.has_change_permission(request, None):
return HttpResponseRedirect("../../../../")
return HttpResponseRedirect("../../")
-
+
context = {
"title": _("Are you sure?"),
- "object_name": opts.verbose_name,
+ "object_name": force_unicode(opts.verbose_name),
"object": obj,
"deleted_objects": deleted_objects,
"perms_lacking": perms_needed,
@@ -681,7 +693,7 @@ class ModelAdmin(BaseModelAdmin):
context = {
'title': _('Change history: %s') % force_unicode(obj),
'action_list': action_list,
- 'module_name': capfirst(opts.verbose_name_plural),
+ 'module_name': capfirst(force_unicode(opts.verbose_name_plural)),
'object': obj,
'root_path': self.admin_site.root_path,
}
@@ -761,6 +773,13 @@ class InlineAdminFormSet(object):
for field_name in flatten_fieldsets(self.fieldsets):
yield self.formset.form.base_fields[field_name]
+ def _media(self):
+ media = self.formset.media
+ for fs in self:
+ media = media + fs.media
+ return media
+ media = property(_media)
+
class InlineAdminForm(AdminForm):
"""
A wrapper around an inline form for use in the admin system.
diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py
index bb4dc58ece..26e935e7fb 100644
--- a/django/contrib/admin/sites.py
+++ b/django/contrib/admin/sites.py
@@ -1,3 +1,7 @@
+import base64
+import cPickle as pickle
+import re
+
from django import http, template
from django.contrib.admin import ModelAdmin
from django.contrib.auth import authenticate, login
@@ -8,11 +12,7 @@ from django.utils.text import capfirst
from django.utils.translation import ugettext_lazy, ugettext as _
from django.views.decorators.cache import never_cache
from django.conf import settings
-import base64
-import cPickle as pickle
-import datetime
-import md5
-import re
+from django.utils.hashcompat import md5_constructor
ERROR_MESSAGE = ugettext_lazy("Please enter a correct username and password. Note that both fields are case-sensitive.")
LOGIN_FORM_KEY = 'this_is_the_login_form'
@@ -26,16 +26,14 @@ class NotRegistered(Exception):
pass
def _encode_post_data(post_data):
- from django.conf import settings
pickled = pickle.dumps(post_data)
- pickled_md5 = md5.new(pickled + settings.SECRET_KEY).hexdigest()
+ pickled_md5 = md5_constructor(pickled + settings.SECRET_KEY).hexdigest()
return base64.encodestring(pickled + pickled_md5)
def _decode_post_data(encoded_data):
- from django.conf import settings
encoded_data = base64.decodestring(encoded_data)
pickled, tamper_check = encoded_data[:-32], encoded_data[-32:]
- if md5.new(pickled + settings.SECRET_KEY).hexdigest() != tamper_check:
+ if md5_constructor(pickled + settings.SECRET_KEY).hexdigest() != tamper_check:
from django.core.exceptions import SuspiciousOperation
raise SuspiciousOperation, "User may have tampered with session cookie."
return pickle.loads(pickled)
@@ -47,10 +45,10 @@ class AdminSite(object):
register() method, and the root() method can then be used as a Django view function
that presents a full admin interface for the collection of registered models.
"""
-
+
index_template = None
login_template = None
-
+
def __init__(self):
self._registry = {} # model_class class -> admin_class instance
@@ -66,19 +64,33 @@ class AdminSite(object):
If a model is already registered, this will raise AlreadyRegistered.
"""
- do_validate = admin_class and settings.DEBUG
- if do_validate:
- # don't import the humongous validation code unless required
+ # Don't import the humongous validation code unless required
+ if admin_class and settings.DEBUG:
from django.contrib.admin.validation import validate
- admin_class = admin_class or ModelAdmin
- # TODO: Handle options
+ else:
+ validate = lambda model, adminclass: None
+
+ if not admin_class:
+ admin_class = ModelAdmin
if isinstance(model_or_iterable, ModelBase):
model_or_iterable = [model_or_iterable]
for model in model_or_iterable:
if model in self._registry:
raise AlreadyRegistered('The model %s is already registered' % model.__name__)
- if do_validate:
- validate(admin_class, model)
+
+ # If we got **options then dynamically construct a subclass of
+ # admin_class with those **options.
+ if options:
+ # For reasons I don't quite understand, without a __module__
+ # the created class appears to "live" in the wrong place,
+ # which causes issues later on.
+ options['__module__'] = __name__
+ admin_class = type("%sAdmin" % model.__name__, (admin_class,), options)
+
+ # Validate (which might be a no-op)
+ validate(admin_class, model)
+
+ # Instantiate the admin class to save in the registry
self._registry[model] = admin_class(model, self)
def unregister(self, model_or_iterable):
@@ -102,23 +114,23 @@ class AdminSite(object):
return request.user.is_authenticated() and request.user.is_staff
def root(self, request, url):
- """
+ """
Handles main URL routing for the admin app.
`url` is the remainder of the URL -- e.g. 'comments/comment/'.
"""
if request.method == 'GET' and not request.path.endswith('/'):
return http.HttpResponseRedirect(request.path + '/')
-
+
# Figure out the admin base URL path and stash it for later use
self.root_path = re.sub(re.escape(url) + '$', '', request.path)
-
+
url = url.rstrip('/') # Trim trailing slash, if it exists.
# The 'logout' view doesn't require that the person is logged in.
if url == 'logout':
return self.logout(request)
-
+
# Check permission to continue or display login form.
if not self.has_permission(request):
return self.login(request)
@@ -139,7 +151,7 @@ class AdminSite(object):
match = USER_CHANGE_PASSWORD_URL_RE.match(url)
if match:
return self.user_change_password(request, match.group(1))
-
+
if '/' in url:
return self.model_page(request, *url.split('/', 2))
@@ -189,7 +201,6 @@ class AdminSite(object):
This takes into account the USE_I18N setting. If it's set to False, the
generated JavaScript will be leaner and faster.
"""
- from django.conf import settings
if settings.USE_I18N:
from django.views.i18n import javascript_catalog
else:
@@ -249,9 +260,6 @@ class AdminSite(object):
else:
if user.is_active and user.is_staff:
login(request, user)
- # TODO: set last_login with an event.
- user.last_login = datetime.datetime.now()
- user.save()
if request.POST.has_key('post_data'):
post_data = _decode_post_data(request.POST['post_data'])
if post_data and not post_data.has_key(LOGIN_FORM_KEY):
@@ -308,14 +316,14 @@ class AdminSite(object):
# Sort the models alphabetically within each app.
for app in app_list:
app['models'].sort(lambda x, y: cmp(x['name'], y['name']))
-
+
context = {
'title': _('Site administration'),
'app_list': app_list,
'root_path': self.root_path,
}
context.update(extra_context or {})
- return render_to_response(self.index_template or 'admin/index.html', context,
+ return render_to_response(self.index_template or 'admin/index.html', context,
context_instance=template.RequestContext(request)
)
index = never_cache(index)
@@ -330,7 +338,7 @@ class AdminSite(object):
post_data = _encode_post_data(request.POST)
else:
post_data = _encode_post_data({})
-
+
context = {
'title': _('Log in'),
'app_path': request.path,
diff --git a/django/contrib/admin/templates/admin/includes/fieldset.html b/django/contrib/admin/templates/admin/includes/fieldset.html
index a61795cfe4..27e54c75d3 100644
--- a/django/contrib/admin/templates/admin/includes/fieldset.html
+++ b/django/contrib/admin/templates/admin/includes/fieldset.html
@@ -1,6 +1,6 @@
<fieldset class="module aligned {{ fieldset.classes }}">
{% if fieldset.name %}<h2>{{ fieldset.name }}</h2>{% endif %}
- {% if fieldset.description %}<div class="description">{{ fieldset.description }}</div>{% endif %}
+ {% if fieldset.description %}<div class="description">{{ fieldset.description|safe }}</div>{% endif %}
{% for line in fieldset %}
<div class="form-row{% if line.errors %} errors{% endif %} {% for field in line %}{{ field.field.name }} {% endfor %} ">
{{ line.errors }}
@@ -14,4 +14,4 @@
{% endfor %}
</div>
{% endfor %}
-</fieldset> \ No newline at end of file
+</fieldset>
diff --git a/django/contrib/admin/templates/admin_doc/model_detail.html b/django/contrib/admin/templates/admin_doc/model_detail.html
index 81bf87db15..414397f23a 100644
--- a/django/contrib/admin/templates/admin_doc/model_detail.html
+++ b/django/contrib/admin/templates/admin_doc/model_detail.html
@@ -34,7 +34,7 @@
<tr>
<td>{{ field.name }}</td>
<td>{{ field.data_type }}</td>
- <td>{% if field.verbose %}{{ field.verbose }}{% endif %}{% if field.help_text %} - {{ field.help_text }}{% endif %}</td>
+ <td>{% if field.verbose %}{{ field.verbose }}{% endif %}{% if field.help_text %} - {{ field.help_text|safe }}{% endif %}</td>
</tr>
{% endfor %}
</tbody>
diff --git a/django/contrib/admin/templates/registration/password_reset_complete.html b/django/contrib/admin/templates/registration/password_reset_complete.html
new file mode 100644
index 0000000000..fceb167a88
--- /dev/null
+++ b/django/contrib/admin/templates/registration/password_reset_complete.html
@@ -0,0 +1,16 @@
+{% extends "admin/base_site.html" %}
+{% load i18n %}
+
+{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password reset' %}</div>{% endblock %}
+
+{% block title %}{% trans 'Password reset complete' %}{% 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>
+
+{% endblock %}
diff --git a/django/contrib/admin/templates/registration/password_reset_confirm.html b/django/contrib/admin/templates/registration/password_reset_confirm.html
new file mode 100644
index 0000000000..9ba0e5af27
--- /dev/null
+++ b/django/contrib/admin/templates/registration/password_reset_confirm.html
@@ -0,0 +1,32 @@
+{% extends "admin/base_site.html" %}
+{% load i18n %}
+
+{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password reset confirmation' %}</div>{% endblock %}
+
+{% block title %}{% trans 'Password reset' %}{% 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">
+{% if form.new_password1.errors %}{{ form.new_password1.errors }}{% endif %}
+<p class="aligned wide"><label for="id_new_password1">{% trans 'New password:' %}</label>{{ form.new_password1 }}</p>
+{% if form.new_password2.errors %}{{ form.new_password2.errors }}{% endif %}
+<p class="aligned wide"><label for="id_new_password2">{% trans 'Confirm password:' %}</label>{{ form.new_password2 }}</p>
+<p><input type="submit" value="{% trans 'Change my password' %}" /></p>
+</form>
+
+{% 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." %}
+
+{% endif %}
+
+{% endblock %}
diff --git a/django/contrib/admin/templates/registration/password_reset_done.html b/django/contrib/admin/templates/registration/password_reset_done.html
index f97b5688c2..e223bdb9de 100644
--- a/django/contrib/admin/templates/registration/password_reset_done.html
+++ b/django/contrib/admin/templates/registration/password_reset_done.html
@@ -9,6 +9,6 @@
<h1>{% trans 'Password reset successful' %}</h1>
-<p>{% trans "We've e-mailed a new password to the e-mail address you submitted. You should be receiving it shortly." %}</p>
+<p>{% trans "We've e-mailed you instructions for setting your password to the e-mail address you submitted. You should be receiving it shortly." %}</p>
{% endblock %}
diff --git a/django/contrib/admin/templates/registration/password_reset_email.html b/django/contrib/admin/templates/registration/password_reset_email.html
index f765dd0670..3b2d5b0f34 100644
--- a/django/contrib/admin/templates/registration/password_reset_email.html
+++ b/django/contrib/admin/templates/registration/password_reset_email.html
@@ -1,15 +1,15 @@
-{% load i18n %}
+{% load i18n %}{% autoescape off %}
{% trans "You're receiving this e-mail because you requested a password reset" %}
{% blocktrans %}for your user account at {{ site_name }}{% endblocktrans %}.
-{% blocktrans %}Your new password is: {{ new_password }}{% endblocktrans %}
-
-{% trans "Feel free to change this password by going to this page:" %}
-
-http://{{ domain }}/password_change/
-
+{% trans "Please go to the following page and choose a new password:" %}
+{% block reset_link %}
+{{ protocol }}://{{ domain }}/reset/{{ uid }}-{{ token }}/
+{% endblock %}
{% trans "Your username, in case you've forgotten:" %} {{ user.username }}
{% trans "Thanks for using our site!" %}
{% blocktrans %}The {{ site_name }} team{% endblocktrans %}
+
+{% endautoescape %}
diff --git a/django/contrib/admin/templates/registration/password_reset_form.html b/django/contrib/admin/templates/registration/password_reset_form.html
index d8c7d03f93..4ecebc77a1 100644
--- a/django/contrib/admin/templates/registration/password_reset_form.html
+++ b/django/contrib/admin/templates/registration/password_reset_form.html
@@ -9,7 +9,7 @@
<h1>{% trans "Password reset" %}</h1>
-<p>{% trans "Forgotten your password? Enter your e-mail address below, and we'll reset your password and e-mail the new one to you." %}</p>
+<p>{% trans "Forgotten your password? Enter your e-mail address below, and we'll e-mail instructions for setting a new one." %}</p>
<form action="" method="post">
{% if form.email.errors %}{{ form.email.errors }}{% endif %}
diff --git a/django/contrib/admin/validation.py b/django/contrib/admin/validation.py
index 2c9cb8554d..a42f2eb985 100644
--- a/django/contrib/admin/validation.py
+++ b/django/contrib/admin/validation.py
@@ -1,3 +1,7 @@
+try:
+ set
+except NameError:
+ from sets import Set as set # Python 2.3 fallback
from django.core.exceptions import ImproperlyConfigured
from django.db import models
@@ -165,6 +169,8 @@ def _validate_base(cls, model):
_check_form_field_existsw('fields', field)
if cls.fieldsets:
raise ImproperlyConfigured('Both fieldsets and fields are specified in %s.' % cls.__name__)
+ if len(cls.fields) > len(set(cls.fields)):
+ raise ImproperlyConfigured('There are duplicate field(s) in %s.fields' % cls.__name__)
# fieldsets
if cls.fieldsets: # default value is None
@@ -179,7 +185,10 @@ def _validate_base(cls, model):
raise ImproperlyConfigured("`fields` key is required in "
"%s.fieldsets[%d][1] field options dict."
% (cls.__name__, idx))
- for field in flatten_fieldsets(cls.fieldsets):
+ flattened_fieldsets = flatten_fieldsets(cls.fieldsets)
+ if len(flattened_fieldsets) > len(set(flattened_fieldsets)):
+ raise ImproperlyConfigured('There are duplicate field(s) in %s.fieldsets' % cls.__name__)
+ for field in flattened_fieldsets:
_check_form_field_existsw("fieldsets[%d][1]['fields']" % idx, field)
# form
diff --git a/django/contrib/admin/views/decorators.py b/django/contrib/admin/views/decorators.py
index 57517cc821..cf0cd704c2 100644
--- a/django/contrib/admin/views/decorators.py
+++ b/django/contrib/admin/views/decorators.py
@@ -1,5 +1,4 @@
import base64
-import md5
import cPickle as pickle
try:
from functools import wraps
@@ -12,6 +11,7 @@ from django.contrib.auth.models import User
from django.contrib.auth import authenticate, login
from django.shortcuts import render_to_response
from django.utils.translation import ugettext_lazy, ugettext as _
+from django.utils.hashcompat import md5_constructor
ERROR_MESSAGE = ugettext_lazy("Please enter a correct username and password. Note that both fields are case-sensitive.")
LOGIN_FORM_KEY = 'this_is_the_login_form'
@@ -35,13 +35,13 @@ def _display_login_form(request, error_message=''):
def _encode_post_data(post_data):
pickled = pickle.dumps(post_data)
- pickled_md5 = md5.new(pickled + settings.SECRET_KEY).hexdigest()
+ pickled_md5 = md5_constructor(pickled + settings.SECRET_KEY).hexdigest()
return base64.encodestring(pickled + pickled_md5)
def _decode_post_data(encoded_data):
encoded_data = base64.decodestring(encoded_data)
pickled, tamper_check = encoded_data[:-32], encoded_data[-32:]
- if md5.new(pickled + settings.SECRET_KEY).hexdigest() != tamper_check:
+ if md5_constructor(pickled + settings.SECRET_KEY).hexdigest() != tamper_check:
from django.core.exceptions import SuspiciousOperation
raise SuspiciousOperation, "User may have tampered with session cookie."
return pickle.loads(pickled)
@@ -87,7 +87,7 @@ def staff_member_required(view_func):
if len(users) == 1:
message = _("Your e-mail address is not your username. Try '%s' instead.") % users[0].username
else:
- # Either we cannot find the user, or if more than 1
+ # Either we cannot find the user, or if more than 1
# we cannot guess which user is the correct one.
message = _("Usernames cannot contain the '@' character.")
return _display_login_form(request, message)
diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py
index 926270cc68..116d9d49f1 100644
--- a/django/contrib/admin/views/main.py
+++ b/django/contrib/admin/views/main.py
@@ -6,7 +6,6 @@ from django.db import models
from django.db.models.query import QuerySet
from django.utils.encoding import force_unicode, smart_str
from django.utils.translation import ugettext
-from django.utils.safestring import mark_safe
from django.utils.http import urlencode
import operator
diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py
index 1139f8af60..1737920312 100644
--- a/django/contrib/admin/widgets.py
+++ b/django/contrib/admin/widgets.py
@@ -7,8 +7,7 @@ import copy
from django import forms
from django.forms.widgets import RadioFieldRenderer
from django.forms.util import flatatt
-from django.utils.datastructures import MultiValueDict
-from django.utils.text import capfirst, truncate_words
+from django.utils.text import truncate_words
from django.utils.translation import ugettext as _
from django.utils.safestring import mark_safe
from django.utils.encoding import force_unicode