summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Willison <simon@simonwillison.net>2008-06-13 15:42:43 +0000
committerSimon Willison <simon@simonwillison.net>2008-06-13 15:42:43 +0000
commit725293d51ad6f5dbefb59fa9adfd76d09e1526c6 (patch)
treef945d43b5db9f30d5d1cf6e517cfcf4df88ce1da
parentf33bdf7e9cbe48d51966abb72cbda0e8b865bdb9 (diff)
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
-rw-r--r--django/contrib/admin/options.py28
-rw-r--r--django/contrib/admin/sites.py56
-rw-r--r--tests/regressiontests/admin_views/models.py9
-rw-r--r--tests/regressiontests/admin_views/tests.py57
-rw-r--r--tests/templates/custom_admin/change_form.html1
-rw-r--r--tests/templates/custom_admin/change_list.html (renamed from tests/templates/admin/admin_views/customarticle/change_list.html)0
-rw-r--r--tests/templates/custom_admin/delete_confirmation.html1
-rw-r--r--tests/templates/custom_admin/index.html6
-rw-r--r--tests/templates/custom_admin/login.html6
-rw-r--r--tests/templates/custom_admin/object_history.html1
10 files changed, 131 insertions, 34 deletions
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index a7a44d3146..b52a82e61a 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -59,7 +59,10 @@ def flatten_fieldsets(fieldsets):
class AdminForm(object):
def __init__(self, form, fieldsets, prepopulated_fields):
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):
for name, options in self.fieldsets:
@@ -233,6 +236,12 @@ 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
+ delete_confirmation_template = None
+ object_history_template = None
def __init__(self, model, admin_site):
self.model = model
@@ -372,6 +381,7 @@ class ModelAdmin(BaseModelAdmin):
if request.POST.has_key("_popup"):
post_url_continue += "?_popup=1"
return HttpResponseRedirect(post_url_continue % pk_value)
+
if request.POST.has_key("_popup"):
return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script>' % \
# escape() calls force_unicode.
@@ -470,7 +480,7 @@ class ModelAdmin(BaseModelAdmin):
'save_as': self.save_as,
'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/change_form.html" % app_label,
"admin/change_form.html"
@@ -626,6 +636,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,
@@ -633,7 +644,7 @@ class ModelAdmin(BaseModelAdmin):
}
context.update({'has_add_permission': self.has_add_permission(request)}),
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/change_list.html' % app_label,
'admin/change_list.html'
@@ -676,6 +687,7 @@ 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,
@@ -685,7 +697,7 @@ class ModelAdmin(BaseModelAdmin):
"opts": opts,
}
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/delete_confirmation.html" % app_label,
"admin/delete_confirmation.html"
@@ -697,8 +709,10 @@ class ModelAdmin(BaseModelAdmin):
from django.contrib.admin.models import LogEntry
model = self.model
opts = model._meta
- action_list = LogEntry.objects.filter(object_id=object_id,
- content_type__id__exact=ContentType.objects.get_for_model(model).id).select_related().order_by('action_time')
+ action_list = LogEntry.objects.filter(
+ 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.
obj = get_object_or_404(model, pk=object_id)
context = {
@@ -708,7 +722,7 @@ class ModelAdmin(BaseModelAdmin):
'object': obj,
}
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/object_history.html" % opts.app_label,
"admin/object_history.html"
diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py
index b4f160dbbe..ffc7e28bdd 100644
--- a/django/contrib/admin/sites.py
+++ b/django/contrib/admin/sites.py
@@ -23,23 +23,6 @@ class AlreadyRegistered(Exception):
class NotRegistered(Exception):
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):
from django.conf import settings
pickled = pickle.dumps(post_data)
@@ -56,6 +39,16 @@ def _decode_post_data(encoded_data):
return pickle.loads(pickled)
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):
self._registry = {} # model_class class -> admin_class instance
@@ -120,7 +113,6 @@ class AdminSite(object):
# expired sessions and continue through (#5999)
return response
-
if url == '':
return self.index(request)
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.")
else:
message = ""
- return _display_login_form(request, message)
+ return self.display_login_form(request, message)
# Check that the user accepts cookies.
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.")
- return _display_login_form(request, message)
+ return self.display_login_form(request, message)
# Check the password.
username = request.POST.get('username', None)
@@ -235,7 +227,7 @@ class AdminSite(object):
message = _("Usernames cannot contain the '@' character.")
else:
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.
else:
@@ -255,7 +247,7 @@ class AdminSite(object):
request.session.delete_test_cookie()
return http.HttpResponseRedirect(request.path)
else:
- return _display_login_form(request, ERROR_MESSAGE)
+ return self.display_login_form(request, ERROR_MESSAGE)
def index(self, request):
"""
@@ -300,11 +292,29 @@ class AdminSite(object):
for app in app_list:
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'),
'app_list': app_list,
}, 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.
# You can instantiate AdminSite in your own code to create a custom admin site.
site = AdminSite()
diff --git a/tests/regressiontests/admin_views/models.py b/tests/regressiontests/admin_views/models.py
index b6d247a65c..2c0ba1d909 100644
--- a/tests/regressiontests/admin_views/models.py
+++ b/tests/regressiontests/admin_views/models.py
@@ -1,7 +1,6 @@
from django.db import models
from django.contrib import admin
-
class Article(models.Model):
"""
A simple article to test admin views. Test backwards compabilty.
@@ -24,6 +23,14 @@ class CustomArticle(models.Model):
date = models.DateTimeField()
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):
"Test that extra_context works"
return super(CustomArticleAdmin, self).changelist_view(request, extra_context={
diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py
index 30161299cc..1e3b334221 100644
--- a/tests/regressiontests/admin_views/tests.py
+++ b/tests/regressiontests/admin_views/tests.py
@@ -5,7 +5,7 @@ from django.contrib.contenttypes.models import ContentType
from django.contrib.admin.sites import LOGIN_FORM_KEY, _encode_post_data
# local test models
-from models import Article
+from models import Article, CustomArticle
def get_perm(Model, perm):
"""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.client.get('/test_admin/admin/logout/')
- def testCustomChangelistView(self):
+ def testCustomModelAdminTemplates(self):
self.client.get('/test_admin/admin/')
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/')
self.failUnlessEqual(request.status_code, 200)
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):
"""Delete view should restrict access and actually delete items."""
diff --git a/tests/templates/custom_admin/change_form.html b/tests/templates/custom_admin/change_form.html
new file mode 100644
index 0000000000..f42ba4b649
--- /dev/null
+++ b/tests/templates/custom_admin/change_form.html
@@ -0,0 +1 @@
+{% extends "admin/change_form.html" %}
diff --git a/tests/templates/admin/admin_views/customarticle/change_list.html b/tests/templates/custom_admin/change_list.html
index eebc9c7e30..eebc9c7e30 100644
--- a/tests/templates/admin/admin_views/customarticle/change_list.html
+++ b/tests/templates/custom_admin/change_list.html
diff --git a/tests/templates/custom_admin/delete_confirmation.html b/tests/templates/custom_admin/delete_confirmation.html
new file mode 100644
index 0000000000..9353c5bfc8
--- /dev/null
+++ b/tests/templates/custom_admin/delete_confirmation.html
@@ -0,0 +1 @@
+{% extends "admin/delete_confirmation.html" %}
diff --git a/tests/templates/custom_admin/index.html b/tests/templates/custom_admin/index.html
new file mode 100644
index 0000000000..d52033ee2d
--- /dev/null
+++ b/tests/templates/custom_admin/index.html
@@ -0,0 +1,6 @@
+{% extends "admin/index.html" %}
+
+{% block content %}
+Hello from a custom index template
+{{ block.super }}
+{% endblock %}
diff --git a/tests/templates/custom_admin/login.html b/tests/templates/custom_admin/login.html
new file mode 100644
index 0000000000..e10a26952f
--- /dev/null
+++ b/tests/templates/custom_admin/login.html
@@ -0,0 +1,6 @@
+{% extends "admin/login.html" %}
+
+{% block content %}
+Hello from a custom login template
+{{ block.super }}
+{% endblock %}
diff --git a/tests/templates/custom_admin/object_history.html b/tests/templates/custom_admin/object_history.html
new file mode 100644
index 0000000000..aee3b5bcba
--- /dev/null
+++ b/tests/templates/custom_admin/object_history.html
@@ -0,0 +1 @@
+{% extends "admin/object_history.html" %}