mirror of
https://github.com/django/django.git
synced 2025-07-04 17:59:13 +00:00
newforms-admin: Made it easier to specify a custom template to be used in the admin section. You can now specify index_template and login_template properties on an AdminSite subclass, and change_form_template, change_list_template, object_history_template and delete_confirmation_template properties on a ModelAdmin subclass.
git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@7630 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
f33bdf7e9c
commit
725293d51a
@ -59,7 +59,10 @@ def flatten_fieldsets(fieldsets):
|
|||||||
class AdminForm(object):
|
class AdminForm(object):
|
||||||
def __init__(self, form, fieldsets, prepopulated_fields):
|
def __init__(self, form, fieldsets, prepopulated_fields):
|
||||||
self.form, self.fieldsets = form, fieldsets
|
self.form, self.fieldsets = form, fieldsets
|
||||||
self.prepopulated_fields = [{'field': form[field_name], 'dependencies': [form[f] for f in dependencies]} for field_name, dependencies in prepopulated_fields.items()]
|
self.prepopulated_fields = [{
|
||||||
|
'field': form[field_name],
|
||||||
|
'dependencies': [form[f] for f in dependencies]
|
||||||
|
} for field_name, dependencies in prepopulated_fields.items()]
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
for name, options in self.fieldsets:
|
for name, options in self.fieldsets:
|
||||||
@ -233,6 +236,12 @@ class ModelAdmin(BaseModelAdmin):
|
|||||||
save_on_top = False
|
save_on_top = False
|
||||||
ordering = None
|
ordering = None
|
||||||
inlines = []
|
inlines = []
|
||||||
|
|
||||||
|
# Custom templates (designed to be over-ridden in subclasses)
|
||||||
|
change_form_template = None
|
||||||
|
change_list_template = None
|
||||||
|
delete_confirmation_template = None
|
||||||
|
object_history_template = None
|
||||||
|
|
||||||
def __init__(self, model, admin_site):
|
def __init__(self, model, admin_site):
|
||||||
self.model = model
|
self.model = model
|
||||||
@ -372,6 +381,7 @@ class ModelAdmin(BaseModelAdmin):
|
|||||||
if request.POST.has_key("_popup"):
|
if request.POST.has_key("_popup"):
|
||||||
post_url_continue += "?_popup=1"
|
post_url_continue += "?_popup=1"
|
||||||
return HttpResponseRedirect(post_url_continue % pk_value)
|
return HttpResponseRedirect(post_url_continue % pk_value)
|
||||||
|
|
||||||
if request.POST.has_key("_popup"):
|
if request.POST.has_key("_popup"):
|
||||||
return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script>' % \
|
return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script>' % \
|
||||||
# escape() calls force_unicode.
|
# escape() calls force_unicode.
|
||||||
@ -470,7 +480,7 @@ class ModelAdmin(BaseModelAdmin):
|
|||||||
'save_as': self.save_as,
|
'save_as': self.save_as,
|
||||||
'save_on_top': self.save_on_top,
|
'save_on_top': self.save_on_top,
|
||||||
})
|
})
|
||||||
return render_to_response([
|
return render_to_response(self.change_form_template or [
|
||||||
"admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()),
|
"admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()),
|
||||||
"admin/%s/change_form.html" % app_label,
|
"admin/%s/change_form.html" % app_label,
|
||||||
"admin/change_form.html"
|
"admin/change_form.html"
|
||||||
@ -626,6 +636,7 @@ class ModelAdmin(BaseModelAdmin):
|
|||||||
if ERROR_FLAG in request.GET.keys():
|
if ERROR_FLAG in request.GET.keys():
|
||||||
return render_to_response('admin/invalid_setup.html', {'title': _('Database error')})
|
return render_to_response('admin/invalid_setup.html', {'title': _('Database error')})
|
||||||
return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1')
|
return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1')
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'title': cl.title,
|
'title': cl.title,
|
||||||
'is_popup': cl.is_popup,
|
'is_popup': cl.is_popup,
|
||||||
@ -633,7 +644,7 @@ class ModelAdmin(BaseModelAdmin):
|
|||||||
}
|
}
|
||||||
context.update({'has_add_permission': self.has_add_permission(request)}),
|
context.update({'has_add_permission': self.has_add_permission(request)}),
|
||||||
context.update(extra_context or {})
|
context.update(extra_context or {})
|
||||||
return render_to_response([
|
return render_to_response(self.change_list_template or [
|
||||||
'admin/%s/%s/change_list.html' % (app_label, opts.object_name.lower()),
|
'admin/%s/%s/change_list.html' % (app_label, opts.object_name.lower()),
|
||||||
'admin/%s/change_list.html' % app_label,
|
'admin/%s/change_list.html' % app_label,
|
||||||
'admin/change_list.html'
|
'admin/change_list.html'
|
||||||
@ -676,6 +687,7 @@ class ModelAdmin(BaseModelAdmin):
|
|||||||
if not self.has_change_permission(request, None):
|
if not self.has_change_permission(request, None):
|
||||||
return HttpResponseRedirect("../../../../")
|
return HttpResponseRedirect("../../../../")
|
||||||
return HttpResponseRedirect("../../")
|
return HttpResponseRedirect("../../")
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
"title": _("Are you sure?"),
|
"title": _("Are you sure?"),
|
||||||
"object_name": opts.verbose_name,
|
"object_name": opts.verbose_name,
|
||||||
@ -685,7 +697,7 @@ class ModelAdmin(BaseModelAdmin):
|
|||||||
"opts": opts,
|
"opts": opts,
|
||||||
}
|
}
|
||||||
context.update(extra_context or {})
|
context.update(extra_context or {})
|
||||||
return render_to_response([
|
return render_to_response(self.delete_confirmation_template or [
|
||||||
"admin/%s/%s/delete_confirmation.html" % (app_label, opts.object_name.lower()),
|
"admin/%s/%s/delete_confirmation.html" % (app_label, opts.object_name.lower()),
|
||||||
"admin/%s/delete_confirmation.html" % app_label,
|
"admin/%s/delete_confirmation.html" % app_label,
|
||||||
"admin/delete_confirmation.html"
|
"admin/delete_confirmation.html"
|
||||||
@ -697,8 +709,10 @@ class ModelAdmin(BaseModelAdmin):
|
|||||||
from django.contrib.admin.models import LogEntry
|
from django.contrib.admin.models import LogEntry
|
||||||
model = self.model
|
model = self.model
|
||||||
opts = model._meta
|
opts = model._meta
|
||||||
action_list = LogEntry.objects.filter(object_id=object_id,
|
action_list = LogEntry.objects.filter(
|
||||||
content_type__id__exact=ContentType.objects.get_for_model(model).id).select_related().order_by('action_time')
|
object_id = object_id,
|
||||||
|
content_type__id__exact = ContentType.objects.get_for_model(model).id
|
||||||
|
).select_related().order_by('action_time')
|
||||||
# If no history was found, see whether this object even exists.
|
# If no history was found, see whether this object even exists.
|
||||||
obj = get_object_or_404(model, pk=object_id)
|
obj = get_object_or_404(model, pk=object_id)
|
||||||
context = {
|
context = {
|
||||||
@ -708,7 +722,7 @@ class ModelAdmin(BaseModelAdmin):
|
|||||||
'object': obj,
|
'object': obj,
|
||||||
}
|
}
|
||||||
context.update(extra_context or {})
|
context.update(extra_context or {})
|
||||||
return render_to_response([
|
return render_to_response(self.object_history_template or [
|
||||||
"admin/%s/%s/object_history.html" % (opts.app_label, opts.object_name.lower()),
|
"admin/%s/%s/object_history.html" % (opts.app_label, opts.object_name.lower()),
|
||||||
"admin/%s/object_history.html" % opts.app_label,
|
"admin/%s/object_history.html" % opts.app_label,
|
||||||
"admin/object_history.html"
|
"admin/object_history.html"
|
||||||
|
@ -23,23 +23,6 @@ class AlreadyRegistered(Exception):
|
|||||||
class NotRegistered(Exception):
|
class NotRegistered(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _display_login_form(request, error_message=''):
|
|
||||||
request.session.set_test_cookie()
|
|
||||||
if request.POST and request.POST.has_key('post_data'):
|
|
||||||
# User has failed login BUT has previously saved post data.
|
|
||||||
post_data = request.POST['post_data']
|
|
||||||
elif request.POST:
|
|
||||||
# User's session must have expired; save their post data.
|
|
||||||
post_data = _encode_post_data(request.POST)
|
|
||||||
else:
|
|
||||||
post_data = _encode_post_data({})
|
|
||||||
return render_to_response('admin/login.html', {
|
|
||||||
'title': _('Log in'),
|
|
||||||
'app_path': request.path,
|
|
||||||
'post_data': post_data,
|
|
||||||
'error_message': error_message
|
|
||||||
}, context_instance=template.RequestContext(request))
|
|
||||||
|
|
||||||
def _encode_post_data(post_data):
|
def _encode_post_data(post_data):
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
pickled = pickle.dumps(post_data)
|
pickled = pickle.dumps(post_data)
|
||||||
@ -56,6 +39,16 @@ def _decode_post_data(encoded_data):
|
|||||||
return pickle.loads(pickled)
|
return pickle.loads(pickled)
|
||||||
|
|
||||||
class AdminSite(object):
|
class AdminSite(object):
|
||||||
|
"""
|
||||||
|
An AdminSite object encapsulates an instance of the Django admin application, ready
|
||||||
|
to be hooked in to your URLConf. Models are registered with the AdminSite using the
|
||||||
|
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):
|
def __init__(self):
|
||||||
self._registry = {} # model_class class -> admin_class instance
|
self._registry = {} # model_class class -> admin_class instance
|
||||||
|
|
||||||
@ -120,7 +113,6 @@ class AdminSite(object):
|
|||||||
# expired sessions and continue through (#5999)
|
# expired sessions and continue through (#5999)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
if url == '':
|
if url == '':
|
||||||
return self.index(request)
|
return self.index(request)
|
||||||
elif url == 'password_change':
|
elif url == 'password_change':
|
||||||
@ -214,12 +206,12 @@ class AdminSite(object):
|
|||||||
message = _("Please log in again, because your session has expired. Don't worry: Your submission has been saved.")
|
message = _("Please log in again, because your session has expired. Don't worry: Your submission has been saved.")
|
||||||
else:
|
else:
|
||||||
message = ""
|
message = ""
|
||||||
return _display_login_form(request, message)
|
return self.display_login_form(request, message)
|
||||||
|
|
||||||
# Check that the user accepts cookies.
|
# Check that the user accepts cookies.
|
||||||
if not request.session.test_cookie_worked():
|
if not request.session.test_cookie_worked():
|
||||||
message = _("Looks like your browser isn't configured to accept cookies. Please enable cookies, reload this page, and try again.")
|
message = _("Looks like your browser isn't configured to accept cookies. Please enable cookies, reload this page, and try again.")
|
||||||
return _display_login_form(request, message)
|
return self.display_login_form(request, message)
|
||||||
|
|
||||||
# Check the password.
|
# Check the password.
|
||||||
username = request.POST.get('username', None)
|
username = request.POST.get('username', None)
|
||||||
@ -235,7 +227,7 @@ class AdminSite(object):
|
|||||||
message = _("Usernames cannot contain the '@' character.")
|
message = _("Usernames cannot contain the '@' character.")
|
||||||
else:
|
else:
|
||||||
message = _("Your e-mail address is not your username. Try '%s' instead.") % user.username
|
message = _("Your e-mail address is not your username. Try '%s' instead.") % user.username
|
||||||
return _display_login_form(request, message)
|
return self.display_login_form(request, message)
|
||||||
|
|
||||||
# The user data is correct; log in the user in and continue.
|
# The user data is correct; log in the user in and continue.
|
||||||
else:
|
else:
|
||||||
@ -255,7 +247,7 @@ class AdminSite(object):
|
|||||||
request.session.delete_test_cookie()
|
request.session.delete_test_cookie()
|
||||||
return http.HttpResponseRedirect(request.path)
|
return http.HttpResponseRedirect(request.path)
|
||||||
else:
|
else:
|
||||||
return _display_login_form(request, ERROR_MESSAGE)
|
return self.display_login_form(request, ERROR_MESSAGE)
|
||||||
|
|
||||||
def index(self, request):
|
def index(self, request):
|
||||||
"""
|
"""
|
||||||
@ -300,11 +292,29 @@ class AdminSite(object):
|
|||||||
for app in app_list:
|
for app in app_list:
|
||||||
app['models'].sort(lambda x, y: cmp(x['name'], y['name']))
|
app['models'].sort(lambda x, y: cmp(x['name'], y['name']))
|
||||||
|
|
||||||
return render_to_response('admin/index.html', {
|
return render_to_response(self.index_template or 'admin/index.html', {
|
||||||
'title': _('Site administration'),
|
'title': _('Site administration'),
|
||||||
'app_list': app_list,
|
'app_list': app_list,
|
||||||
}, context_instance=template.RequestContext(request))
|
}, context_instance=template.RequestContext(request))
|
||||||
|
|
||||||
|
def display_login_form(self, request, error_message=''):
|
||||||
|
request.session.set_test_cookie()
|
||||||
|
if request.POST and request.POST.has_key('post_data'):
|
||||||
|
# User has failed login BUT has previously saved post data.
|
||||||
|
post_data = request.POST['post_data']
|
||||||
|
elif request.POST:
|
||||||
|
# User's session must have expired; save their post data.
|
||||||
|
post_data = _encode_post_data(request.POST)
|
||||||
|
else:
|
||||||
|
post_data = _encode_post_data({})
|
||||||
|
return render_to_response(self.login_template or 'admin/login.html', {
|
||||||
|
'title': _('Log in'),
|
||||||
|
'app_path': request.path,
|
||||||
|
'post_data': post_data,
|
||||||
|
'error_message': error_message
|
||||||
|
}, context_instance=template.RequestContext(request))
|
||||||
|
|
||||||
|
|
||||||
# This global object represents the default admin site, for the common case.
|
# This global object represents the default admin site, for the common case.
|
||||||
# You can instantiate AdminSite in your own code to create a custom admin site.
|
# You can instantiate AdminSite in your own code to create a custom admin site.
|
||||||
site = AdminSite()
|
site = AdminSite()
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
|
|
||||||
class Article(models.Model):
|
class Article(models.Model):
|
||||||
"""
|
"""
|
||||||
A simple article to test admin views. Test backwards compabilty.
|
A simple article to test admin views. Test backwards compabilty.
|
||||||
@ -24,6 +23,14 @@ class CustomArticle(models.Model):
|
|||||||
date = models.DateTimeField()
|
date = models.DateTimeField()
|
||||||
|
|
||||||
class CustomArticleAdmin(admin.ModelAdmin):
|
class CustomArticleAdmin(admin.ModelAdmin):
|
||||||
|
"""
|
||||||
|
Tests various hooks for using custom templates and contexts.
|
||||||
|
"""
|
||||||
|
change_list_template = 'custom_admin/change_list.html'
|
||||||
|
change_form_template = 'custom_admin/change_form.html'
|
||||||
|
object_history_template = 'custom_admin/object_history.html'
|
||||||
|
delete_confirmation_template = 'custom_admin/delete_confirmation.html'
|
||||||
|
|
||||||
def changelist_view(self, request):
|
def changelist_view(self, request):
|
||||||
"Test that extra_context works"
|
"Test that extra_context works"
|
||||||
return super(CustomArticleAdmin, self).changelist_view(request, extra_context={
|
return super(CustomArticleAdmin, self).changelist_view(request, extra_context={
|
||||||
|
@ -5,7 +5,7 @@ from django.contrib.contenttypes.models import ContentType
|
|||||||
from django.contrib.admin.sites import LOGIN_FORM_KEY, _encode_post_data
|
from django.contrib.admin.sites import LOGIN_FORM_KEY, _encode_post_data
|
||||||
|
|
||||||
# local test models
|
# local test models
|
||||||
from models import Article
|
from models import Article, CustomArticle
|
||||||
|
|
||||||
def get_perm(Model, perm):
|
def get_perm(Model, perm):
|
||||||
"""Return the permission object, for the Model"""
|
"""Return the permission object, for the Model"""
|
||||||
@ -183,13 +183,64 @@ class AdminViewPermissionsTest(TestCase):
|
|||||||
self.failUnlessEqual(Article.objects.get(pk=1).content, '<p>edited article</p>')
|
self.failUnlessEqual(Article.objects.get(pk=1).content, '<p>edited article</p>')
|
||||||
self.client.get('/test_admin/admin/logout/')
|
self.client.get('/test_admin/admin/logout/')
|
||||||
|
|
||||||
def testCustomChangelistView(self):
|
def testCustomModelAdminTemplates(self):
|
||||||
self.client.get('/test_admin/admin/')
|
self.client.get('/test_admin/admin/')
|
||||||
self.client.post('/test_admin/admin/', self.super_login)
|
self.client.post('/test_admin/admin/', self.super_login)
|
||||||
|
|
||||||
|
# Test custom change list template with custom extra context
|
||||||
request = self.client.get('/test_admin/admin/admin_views/customarticle/')
|
request = self.client.get('/test_admin/admin/admin_views/customarticle/')
|
||||||
self.failUnlessEqual(request.status_code, 200)
|
self.failUnlessEqual(request.status_code, 200)
|
||||||
self.assert_("var hello = 'Hello!';" in request.content)
|
self.assert_("var hello = 'Hello!';" in request.content)
|
||||||
|
self.assertTemplateUsed(request, 'custom_admin/change_list.html')
|
||||||
|
|
||||||
|
# Test custom change form template
|
||||||
|
request = self.client.get('/test_admin/admin/admin_views/customarticle/add/')
|
||||||
|
self.assertTemplateUsed(request, 'custom_admin/change_form.html')
|
||||||
|
|
||||||
|
# Add an article so we can test delete and history views
|
||||||
|
post = self.client.post('/test_admin/admin/admin_views/customarticle/add/', {
|
||||||
|
'content': '<p>great article</p>',
|
||||||
|
'date_0': '2008-03-18',
|
||||||
|
'date_1': '10:54:39'
|
||||||
|
})
|
||||||
|
self.assertRedirects(post, '/test_admin/admin/admin_views/customarticle/')
|
||||||
|
self.failUnlessEqual(CustomArticle.objects.all().count(), 1)
|
||||||
|
|
||||||
|
# Test custom delete and object history templates
|
||||||
|
request = self.client.get('/test_admin/admin/admin_views/customarticle/1/delete/')
|
||||||
|
self.assertTemplateUsed(request, 'custom_admin/delete_confirmation.html')
|
||||||
|
request = self.client.get('/test_admin/admin/admin_views/customarticle/1/history/')
|
||||||
|
self.assertTemplateUsed(request, 'custom_admin/object_history.html')
|
||||||
|
|
||||||
|
self.client.get('/test_admin/admin/logout/')
|
||||||
|
|
||||||
|
def testCustomAdminSiteTemplates(self):
|
||||||
|
from django.contrib import admin
|
||||||
|
self.assertEqual(admin.site.index_template, None)
|
||||||
|
self.assertEqual(admin.site.login_template, None)
|
||||||
|
|
||||||
|
self.client.get('/test_admin/admin/logout/')
|
||||||
|
request = self.client.get('/test_admin/admin/')
|
||||||
|
self.assertTemplateUsed(request, 'admin/login.html')
|
||||||
|
self.client.post('/test_admin/admin/', self.changeuser_login)
|
||||||
|
request = self.client.get('/test_admin/admin/')
|
||||||
|
self.assertTemplateUsed(request, 'admin/index.html')
|
||||||
|
|
||||||
|
self.client.get('/test_admin/admin/logout/')
|
||||||
|
admin.site.login_template = 'custom_admin/login.html'
|
||||||
|
admin.site.index_template = 'custom_admin/index.html'
|
||||||
|
request = self.client.get('/test_admin/admin/')
|
||||||
|
self.assertTemplateUsed(request, 'custom_admin/login.html')
|
||||||
|
self.assert_('Hello from a custom login template' in request.content)
|
||||||
|
self.client.post('/test_admin/admin/', self.changeuser_login)
|
||||||
|
request = self.client.get('/test_admin/admin/')
|
||||||
|
self.assertTemplateUsed(request, 'custom_admin/index.html')
|
||||||
|
self.assert_('Hello from a custom index template' in request.content)
|
||||||
|
|
||||||
|
self.client.get('/test_admin/admin/logout/')
|
||||||
|
admin.site.login_template = None
|
||||||
|
admin.site.index_template = None
|
||||||
|
|
||||||
def testDeleteView(self):
|
def testDeleteView(self):
|
||||||
"""Delete view should restrict access and actually delete items."""
|
"""Delete view should restrict access and actually delete items."""
|
||||||
|
|
||||||
|
1
tests/templates/custom_admin/change_form.html
Normal file
1
tests/templates/custom_admin/change_form.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
{% extends "admin/change_form.html" %}
|
1
tests/templates/custom_admin/delete_confirmation.html
Normal file
1
tests/templates/custom_admin/delete_confirmation.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
{% extends "admin/delete_confirmation.html" %}
|
6
tests/templates/custom_admin/index.html
Normal file
6
tests/templates/custom_admin/index.html
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{% extends "admin/index.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
Hello from a custom index template
|
||||||
|
{{ block.super }}
|
||||||
|
{% endblock %}
|
6
tests/templates/custom_admin/login.html
Normal file
6
tests/templates/custom_admin/login.html
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{% extends "admin/login.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
Hello from a custom login template
|
||||||
|
{{ block.super }}
|
||||||
|
{% endblock %}
|
1
tests/templates/custom_admin/object_history.html
Normal file
1
tests/templates/custom_admin/object_history.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
{% extends "admin/object_history.html" %}
|
Loading…
x
Reference in New Issue
Block a user