From a19ed8aea395e8e07164ff7d85bd7dff2f24edca Mon Sep 17 00:00:00 2001 From: Brian Rosner Date: Fri, 18 Jul 2008 23:54:34 +0000 Subject: [PATCH] Merged the newforms-admin branch into trunk. This is a backward incompatible change. The admin contrib app has been refactored. The newforms module has several improvements including FormSets and Media definitions. git-svn-id: http://code.djangoproject.com/svn/django/trunk@7967 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 10 + django/__init__.py | 2 +- django/conf/project_template/urls.py | 8 +- django/contrib/admin/__init__.py | 16 + django/contrib/admin/filterspecs.py | 28 +- django/contrib/admin/media/css/forms.css | 21 + django/contrib/admin/media/js/SelectFilter.js | 81 -- .../media/js/admin/CollapsedFieldsets.js | 2 +- .../media/js/admin/RelatedObjectLookups.js | 4 +- django/contrib/admin/models.py | 3 +- django/contrib/admin/options.py | 795 ++++++++++++++++ django/contrib/admin/sites.py | 349 +++++++ .../templates/admin/auth/user/add_form.html | 13 +- .../admin/auth/user/change_password.html | 13 +- .../contrib/admin/templates/admin/base.html | 9 +- .../admin/templates/admin/change_form.html | 69 +- .../admin/templates/admin/change_list.html | 18 +- .../templates/admin/delete_confirmation.html | 2 + .../templates/admin/edit_inline/stacked.html | 26 + .../templates/admin/edit_inline/tabular.html | 64 ++ .../templates/admin/edit_inline_stacked.html | 16 - .../templates/admin/edit_inline_tabular.html | 44 - .../admin/templates/admin/field_line.html | 10 - .../admin/templates/admin/filters.html | 7 - .../templates/admin/includes/fieldset.html | 17 + .../contrib/admin/templates/admin/index.html | 7 +- .../admin/templates/admin/invalid_setup.html | 2 - .../contrib/admin/templates/admin/login.html | 4 +- .../admin/templates/admin/object_history.html | 8 +- .../admin/templates/admin/search_form.html | 2 +- .../admin/templates/admin_doc/index.html | 1 + .../admin/templates/admin_doc/view_index.html | 1 + .../registration/password_change_done.html | 1 + .../registration/password_change_form.html | 1 + .../registration/password_reset_form.html | 2 +- .../admin/templates/widget/date_time.html | 5 - .../admin/templates/widget/default.html | 1 - .../contrib/admin/templates/widget/file.html | 4 - .../admin/templates/widget/foreign.html | 20 - .../admin/templates/widget/many_to_many.html | 1 - .../admin/templates/widget/one_to_one.html | 2 - .../contrib/admin/templatetags/admin_list.py | 18 +- .../admin/templatetags/admin_modify.py | 240 +---- .../admin/templatetags/adminapplist.py | 81 -- django/contrib/admin/urls.py | 43 - django/contrib/admin/util.py | 139 +++ django/contrib/admin/validation.py | 280 ++++++ django/contrib/admin/views/auth.py | 78 -- django/contrib/admin/views/decorators.py | 1 - django/contrib/admin/views/main.py | 622 +------------ django/contrib/admin/widgets.py | 215 +++++ .../contrib/admindocs}/__init__.py | 0 django/contrib/admindocs/urls.py | 15 + django/contrib/{admin => admindocs}/utils.py | 0 .../views/doc.py => admindocs/views.py} | 52 +- django/contrib/auth/admin.py | 66 ++ django/contrib/auth/forms.py | 233 +++-- django/contrib/auth/models.py | 25 +- django/contrib/auth/tests/__init__.py | 8 + .../contrib/auth/{tests.py => tests/basic.py} | 8 +- django/contrib/auth/tests/forms.py | 135 +++ django/contrib/auth/views.py | 91 +- django/contrib/comments/admin.py | 30 + django/contrib/comments/models.py | 26 +- django/contrib/comments/views/comments.py | 52 +- django/contrib/flatpages/admin.py | 15 + django/contrib/flatpages/models.py | 11 +- django/contrib/redirects/models.py | 26 +- django/contrib/sites/admin.py | 9 + django/contrib/sites/models.py | 7 +- django/core/management/validation.py | 62 -- django/db/models/__init__.py | 2 +- django/db/models/base.py | 5 +- django/db/models/fields/__init__.py | 33 +- django/db/models/fields/related.py | 77 +- django/db/models/manipulators.py | 7 +- django/db/models/options.py | 75 -- django/newforms/__init__.py | 1 + django/newforms/forms.py | 51 +- django/newforms/formsets.py | 292 ++++++ django/newforms/models.py | 222 ++++- django/newforms/widgets.py | 170 +++- docs/admin.txt | 678 ++++++++++++++ docs/authentication.txt | 44 +- docs/custom_model_fields.txt | 1 - docs/localflavor.txt | 4 +- docs/model-api.txt | 478 ---------- docs/modelforms.txt | 122 +++ docs/newforms.txt | 640 +++++++++++++ docs/tutorial02.txt | 163 ++-- tests/modeltests/invalid_models/models.py | 4 +- tests/modeltests/model_forms/models.py | 18 + tests/modeltests/model_formsets/__init__.py | 0 tests/modeltests/model_formsets/models.py | 324 +++++++ .../admin_ordering/__init__.py | 0 .../regressiontests/admin_ordering/models.py | 46 + tests/regressiontests/admin_views/__init__.py | 0 .../fixtures/admin-views-users.xml | 81 ++ .../fixtures/string-primary-key.xml | 6 + tests/regressiontests/admin_views/models.py | 61 ++ tests/regressiontests/admin_views/tests.py | 362 ++++++++ tests/regressiontests/admin_views/urls.py | 7 + .../regressiontests/admin_widgets/__init__.py | 0 tests/regressiontests/admin_widgets/models.py | 85 ++ tests/regressiontests/forms/forms.py | 72 ++ tests/regressiontests/forms/formsets.py | 575 ++++++++++++ tests/regressiontests/forms/media.py | 359 +++++++ tests/regressiontests/forms/tests.py | 4 + tests/regressiontests/forms/widgets.py | 91 ++ .../inline_formsets/__init__.py | 0 .../regressiontests/inline_formsets/models.py | 55 ++ .../invalid_admin_options/models.py | 337 ------- tests/regressiontests/modeladmin/__init__.py | 0 tests/regressiontests/modeladmin/models.py | 876 ++++++++++++++++++ tests/templates/custom_admin/change_form.html | 1 + tests/templates/custom_admin/change_list.html | 7 + .../custom_admin/delete_confirmation.html | 1 + tests/templates/custom_admin/index.html | 6 + tests/templates/custom_admin/login.html | 6 + .../custom_admin/object_history.html | 1 + tests/urls.py | 3 + 121 files changed, 8050 insertions(+), 2680 deletions(-) delete mode 100644 django/contrib/admin/media/js/SelectFilter.js create mode 100644 django/contrib/admin/options.py create mode 100644 django/contrib/admin/sites.py create mode 100644 django/contrib/admin/templates/admin/edit_inline/stacked.html create mode 100644 django/contrib/admin/templates/admin/edit_inline/tabular.html delete mode 100644 django/contrib/admin/templates/admin/edit_inline_stacked.html delete mode 100644 django/contrib/admin/templates/admin/edit_inline_tabular.html delete mode 100644 django/contrib/admin/templates/admin/field_line.html delete mode 100644 django/contrib/admin/templates/admin/filters.html create mode 100644 django/contrib/admin/templates/admin/includes/fieldset.html delete mode 100644 django/contrib/admin/templates/widget/date_time.html delete mode 100644 django/contrib/admin/templates/widget/default.html delete mode 100644 django/contrib/admin/templates/widget/file.html delete mode 100644 django/contrib/admin/templates/widget/foreign.html delete mode 100644 django/contrib/admin/templates/widget/many_to_many.html delete mode 100644 django/contrib/admin/templates/widget/one_to_one.html delete mode 100644 django/contrib/admin/templatetags/adminapplist.py delete mode 100644 django/contrib/admin/urls.py create mode 100644 django/contrib/admin/util.py create mode 100644 django/contrib/admin/validation.py delete mode 100644 django/contrib/admin/views/auth.py create mode 100644 django/contrib/admin/widgets.py rename {tests/regressiontests/invalid_admin_options => django/contrib/admindocs}/__init__.py (100%) create mode 100644 django/contrib/admindocs/urls.py rename django/contrib/{admin => admindocs}/utils.py (100%) rename django/contrib/{admin/views/doc.py => admindocs/views.py} (87%) create mode 100644 django/contrib/auth/admin.py create mode 100644 django/contrib/auth/tests/__init__.py rename django/contrib/auth/{tests.py => tests/basic.py} (97%) create mode 100644 django/contrib/auth/tests/forms.py create mode 100644 django/contrib/comments/admin.py create mode 100644 django/contrib/flatpages/admin.py create mode 100644 django/contrib/sites/admin.py create mode 100644 django/newforms/formsets.py create mode 100644 docs/admin.txt create mode 100644 tests/modeltests/model_formsets/__init__.py create mode 100644 tests/modeltests/model_formsets/models.py create mode 100644 tests/regressiontests/admin_ordering/__init__.py create mode 100644 tests/regressiontests/admin_ordering/models.py create mode 100644 tests/regressiontests/admin_views/__init__.py create mode 100644 tests/regressiontests/admin_views/fixtures/admin-views-users.xml create mode 100644 tests/regressiontests/admin_views/fixtures/string-primary-key.xml create mode 100644 tests/regressiontests/admin_views/models.py create mode 100644 tests/regressiontests/admin_views/tests.py create mode 100644 tests/regressiontests/admin_views/urls.py create mode 100644 tests/regressiontests/admin_widgets/__init__.py create mode 100644 tests/regressiontests/admin_widgets/models.py create mode 100644 tests/regressiontests/forms/formsets.py create mode 100644 tests/regressiontests/forms/media.py create mode 100644 tests/regressiontests/inline_formsets/__init__.py create mode 100644 tests/regressiontests/inline_formsets/models.py delete mode 100644 tests/regressiontests/invalid_admin_options/models.py create mode 100644 tests/regressiontests/modeladmin/__init__.py create mode 100644 tests/regressiontests/modeladmin/models.py create mode 100644 tests/templates/custom_admin/change_form.html create mode 100644 tests/templates/custom_admin/change_list.html create mode 100644 tests/templates/custom_admin/delete_confirmation.html create mode 100644 tests/templates/custom_admin/index.html create mode 100644 tests/templates/custom_admin/login.html create mode 100644 tests/templates/custom_admin/object_history.html diff --git a/AUTHORS b/AUTHORS index 4212bb9a2f..24e3e5bcac 100644 --- a/AUTHORS +++ b/AUTHORS @@ -74,6 +74,7 @@ answer newbie questions, and generally made Django that much better: Arvis Bickovskis Paul Bissex Simon Blanchard + David Blewett Matt Boersma boobsd@gmail.com Andrew Brehaut @@ -172,6 +173,7 @@ answer newbie questions, and generally made Django that much better: Espen Grindhaug Thomas Güttler dAniel hAhler + hambaloney Brian Harring Brant Harris Hawkeye @@ -194,6 +196,7 @@ answer newbie questions, and generally made Django that much better: Baurzhan Ismagulov james_027@yahoo.com jcrasta@gmail.com + jdetaeye Zak Johnson Nis Jørgensen Michael Josephson @@ -241,11 +244,13 @@ answer newbie questions, and generally made Django that much better: Waylan Limberg limodou Philip Lindborg + Simon Litchfield Daniel Lindsley Trey Long msaelices Matt McClanahan Martin Maney + Petr Marhoun masonsimon+django@gmail.com Manuzhai Petr Marhoun @@ -258,6 +263,7 @@ answer newbie questions, and generally made Django that much better: mattycakes@gmail.com Jason McBrayer mccutchen@gmail.com + Christian Metts michael.mcewan@gmail.com michal@plovarna.cz Slawek Mikula @@ -270,6 +276,7 @@ answer newbie questions, and generally made Django that much better: Eric Moritz mrmachine Robin Munn + msundstr Robert Myers Nebojša Dorđević Doug Napoleone @@ -290,6 +297,7 @@ answer newbie questions, and generally made Django that much better: peter@mymart.com pgross@thoughtworks.com phaedo + Julien Phalip phil@produxion.net phil.h.smith@gmail.com Gustavo Picon @@ -298,6 +306,7 @@ answer newbie questions, and generally made Django that much better: Mihai Preda Daniel Poelzleithner polpak@yahoo.com + Matthias Pronk Jyrki Pulliainen Johann Queuniet Jan Rademaker @@ -314,6 +323,7 @@ answer newbie questions, and generally made Django that much better: Matt Riggott Henrique Romano Armin Ronacher + Daniel Roseman Brian Rosner Oliver Rutherfurd ryankanno diff --git a/django/__init__.py b/django/__init__.py index de473fa4e9..9c5fda133d 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,4 +1,4 @@ -VERSION = (0, 97, 'pre') +VERSION = (0, 97, 'newforms-admin') def get_version(): "Returns the version as a human-format string." diff --git a/django/conf/project_template/urls.py b/django/conf/project_template/urls.py index 402dd6536b..98335a151c 100644 --- a/django/conf/project_template/urls.py +++ b/django/conf/project_template/urls.py @@ -1,9 +1,15 @@ from django.conf.urls.defaults import * +# Uncomment this for admin: +#from django.contrib import admin + urlpatterns = patterns('', # Example: # (r'^{{ project_name }}/', include('{{ project_name }}.foo.urls')), + # Uncomment this for admin docs: + #(r'^admin/doc/', include('django.contrib.admindocs.urls')), + # Uncomment this for admin: -# (r'^admin/', include('django.contrib.admin.urls')), + #('^admin/(.*)', admin.site.root), ) diff --git a/django/contrib/admin/__init__.py b/django/contrib/admin/__init__.py index e69de29bb2..56b64faacb 100644 --- a/django/contrib/admin/__init__.py +++ b/django/contrib/admin/__init__.py @@ -0,0 +1,16 @@ +from django.contrib.admin.options import ModelAdmin, HORIZONTAL, VERTICAL +from django.contrib.admin.options import StackedInline, TabularInline +from django.contrib.admin.sites import AdminSite, site + +def autodiscover(): + """ + Auto-discover INSTALLED_APPS admin.py modules and fail silently when + not present. This forces an import on them to register any admin bits they + may want. + """ + from django.conf import settings + for app in settings.INSTALLED_APPS: + try: + __import__("%s.admin" % app) + except ImportError: + pass diff --git a/django/contrib/admin/filterspecs.py b/django/contrib/admin/filterspecs.py index a4f92c986a..d6a4a0bc48 100644 --- a/django/contrib/admin/filterspecs.py +++ b/django/contrib/admin/filterspecs.py @@ -15,7 +15,7 @@ import datetime class FilterSpec(object): filter_specs = [] - def __init__(self, f, request, params, model): + def __init__(self, f, request, params, model, model_admin): self.field = f self.params = params @@ -23,10 +23,10 @@ class FilterSpec(object): cls.filter_specs.append((test, factory)) register = classmethod(register) - def create(cls, f, request, params, model): + def create(cls, f, request, params, model, model_admin): for test, factory in cls.filter_specs: if test(f): - return factory(f, request, params, model) + return factory(f, request, params, model, model_admin) create = classmethod(create) def has_output(self): @@ -52,8 +52,8 @@ class FilterSpec(object): return mark_safe("".join(t)) class RelatedFilterSpec(FilterSpec): - def __init__(self, f, request, params, model): - super(RelatedFilterSpec, self).__init__(f, request, params, model) + def __init__(self, f, request, params, model, model_admin): + super(RelatedFilterSpec, self).__init__(f, request, params, model, model_admin) if isinstance(f, models.ManyToManyField): self.lookup_title = f.rel.to._meta.verbose_name else: @@ -81,8 +81,8 @@ class RelatedFilterSpec(FilterSpec): FilterSpec.register(lambda f: bool(f.rel), RelatedFilterSpec) class ChoicesFilterSpec(FilterSpec): - def __init__(self, f, request, params, model): - super(ChoicesFilterSpec, self).__init__(f, request, params, model) + def __init__(self, f, request, params, model, model_admin): + super(ChoicesFilterSpec, self).__init__(f, request, params, model, model_admin) self.lookup_kwarg = '%s__exact' % f.name self.lookup_val = request.GET.get(self.lookup_kwarg, None) @@ -98,8 +98,8 @@ class ChoicesFilterSpec(FilterSpec): FilterSpec.register(lambda f: bool(f.choices), ChoicesFilterSpec) class DateFieldFilterSpec(FilterSpec): - def __init__(self, f, request, params, model): - super(DateFieldFilterSpec, self).__init__(f, request, params, model) + def __init__(self, f, request, params, model, model_admin): + super(DateFieldFilterSpec, self).__init__(f, request, params, model, model_admin) self.field_generic = '%s__' % self.field.name @@ -133,8 +133,8 @@ class DateFieldFilterSpec(FilterSpec): FilterSpec.register(lambda f: isinstance(f, models.DateField), DateFieldFilterSpec) class BooleanFieldFilterSpec(FilterSpec): - def __init__(self, f, request, params, model): - super(BooleanFieldFilterSpec, self).__init__(f, request, params, model) + def __init__(self, f, request, params, model, model_admin): + super(BooleanFieldFilterSpec, self).__init__(f, request, params, model, model_admin) self.lookup_kwarg = '%s__exact' % f.name self.lookup_kwarg2 = '%s__isnull' % f.name self.lookup_val = request.GET.get(self.lookup_kwarg, None) @@ -159,10 +159,10 @@ FilterSpec.register(lambda f: isinstance(f, models.BooleanField) or isinstance(f # if a field is eligible to use the BooleanFieldFilterSpec, that'd be much # more appropriate, and the AllValuesFilterSpec won't get used for it. class AllValuesFilterSpec(FilterSpec): - def __init__(self, f, request, params, model): - super(AllValuesFilterSpec, self).__init__(f, request, params, model) + def __init__(self, f, request, params, model, model_admin): + super(AllValuesFilterSpec, self).__init__(f, request, params, model, model_admin) self.lookup_val = request.GET.get(f.name, None) - self.lookup_choices = model._meta.admin.manager.distinct().order_by(f.name).values(f.name) + self.lookup_choices = model_admin.queryset(request).distinct().order_by(f.name).values(f.name) def title(self): return self.field.verbose_name diff --git a/django/contrib/admin/media/css/forms.css b/django/contrib/admin/media/css/forms.css index 72e57501e9..2a1a0995a0 100644 --- a/django/contrib/admin/media/css/forms.css +++ b/django/contrib/admin/media/css/forms.css @@ -58,3 +58,24 @@ fieldset.monospace textarea { font-family:"Bitstream Vera Sans Mono",Monaco,"Cou .vLargeTextField, .vXMLLargeTextField { width:48em; } .flatpages-flatpage #id_content { height:40.2em; } .module table .vPositiveSmallIntegerField { width:2.2em; } + +/* x unsorted */ +.inline-group {padding:10px; padding-bottom:5px; background:#eee; margin:10px 0;} +.inline-group h3.header {margin:-5px -10px 5px -10px; background:#bbb; color:#fff; padding:2px 5px 3px 5px; font-size:11px} +.inline-related {margin-bottom:15px; position:relative;} +.last-related {margin-bottom:0px;} +.inline-related h2 { margin:0; padding:2px 5px 3px 5px; font-size:11px; text-align:left; font-weight:bold; color:#888; } +.inline-related h2 b {font-weight:normal; color:#aaa;} +.inline-related h2 span.delete {padding-left:20px; position:absolute; top:0px; right:5px;} +.inline-related h2 span.delete label {margin-left:2px; padding-top:1px;} +.inline-related fieldset {background:#fbfbfb;} +.inline-related fieldset.module h2 { margin:0; padding:2px 5px 3px 5px; font-size:11px; text-align:left; font-weight:bold; background:#bcd; color:#fff; } +.inline-related.tabular fieldset.module table {width:100%;} + +.inline-group .tabular tr.has_original td {padding-top:2em;} +.inline-group .tabular tr td.original { padding:2px 0 0 0; width:0; _position:relative; } +.inline-group .tabular th.original {width:0px; padding:0;} +.inline-group .tabular td.original p {position:absolute; left:0; height:1.1em; padding:2px 7px; overflow:hidden; font-size:9px; font-weight:bold; color:#666; _width:700px; } +.inline-group ul.tools {padding:0; margin: 0; list-style:none;} +.inline-group ul.tools li {display:inline; padding:0 5px;} +.inline-group ul.tools a.add {background:url(../img/admin/icon_addlink.gif) 0 50% no-repeat; padding-left:14px;} \ No newline at end of file diff --git a/django/contrib/admin/media/js/SelectFilter.js b/django/contrib/admin/media/js/SelectFilter.js deleted file mode 100644 index 0501920608..0000000000 --- a/django/contrib/admin/media/js/SelectFilter.js +++ /dev/null @@ -1,81 +0,0 @@ -/* -SelectFilter - Turns a multiple-select box into a filter interface. - -Requires SelectBox.js and addevent.js. -*/ - -function findForm(node) { - // returns the node of the form containing the given node - if (node.tagName.toLowerCase() != 'form') { - return findForm(node.parentNode); - } - return node; -} - -var SelectFilter = { - init: function(field_id) { - var from_box = document.getElementById(field_id); - from_box.id += '_from'; // change its ID - // Create the INPUT input box - var input_box = document.createElement('input'); - input_box.id = field_id + '_input'; - input_box.setAttribute('type', 'text'); - from_box.parentNode.insertBefore(input_box, from_box); - from_box.parentNode.insertBefore(document.createElement('br'), input_box.nextSibling); - // Create the TO box - var to_box = document.createElement('select'); - to_box.id = field_id + '_to'; - to_box.setAttribute('multiple', 'multiple'); - to_box.setAttribute('size', from_box.size); - from_box.parentNode.insertBefore(to_box, from_box.nextSibling); - to_box.setAttribute('name', from_box.getAttribute('name')); - from_box.setAttribute('name', from_box.getAttribute('name') + '_old'); - // Give the filters a CSS hook - from_box.setAttribute('class', 'filtered'); - to_box.setAttribute('class', 'filtered'); - // Set up the JavaScript event handlers for the select box filter interface - addEvent(input_box, 'keyup', function(e) { SelectFilter.filter_key_up(e, field_id); }); - addEvent(input_box, 'keydown', function(e) { SelectFilter.filter_key_down(e, field_id); }); - addEvent(from_box, 'dblclick', function() { SelectBox.move(field_id + '_from', field_id + '_to'); }); - addEvent(from_box, 'focus', function() { input_box.focus(); }); - addEvent(to_box, 'dblclick', function() { SelectBox.move(field_id + '_to', field_id + '_from'); }); - addEvent(findForm(from_box), 'submit', function() { SelectBox.select_all(field_id + '_to'); }); - SelectBox.init(field_id + '_from'); - SelectBox.init(field_id + '_to'); - // Move selected from_box options to to_box - SelectBox.move(field_id + '_from', field_id + '_to'); - }, - filter_key_up: function(event, field_id) { - from = document.getElementById(field_id + '_from'); - // don't submit form if user pressed Enter - if ((event.which && event.which == 13) || (event.keyCode && event.keyCode == 13)) { - from.selectedIndex = 0; - SelectBox.move(field_id + '_from', field_id + '_to'); - from.selectedIndex = 0; - return false; - } - var temp = from.selectedIndex; - SelectBox.filter(field_id + '_from', document.getElementById(field_id + '_input').value); - from.selectedIndex = temp; - return true; - }, - filter_key_down: function(event, field_id) { - from = document.getElementById(field_id + '_from'); - // right arrow -- move across - if ((event.which && event.which == 39) || (event.keyCode && event.keyCode == 39)) { - var old_index = from.selectedIndex; - SelectBox.move(field_id + '_from', field_id + '_to'); - from.selectedIndex = (old_index == from.length) ? from.length - 1 : old_index; - return false; - } - // down arrow -- wrap around - if ((event.which && event.which == 40) || (event.keyCode && event.keyCode == 40)) { - from.selectedIndex = (from.length == from.selectedIndex + 1) ? 0 : from.selectedIndex + 1; - } - // up arrow -- wrap around - if ((event.which && event.which == 38) || (event.keyCode && event.keyCode == 38)) { - from.selectedIndex = (from.selectedIndex == 0) ? from.length - 1 : from.selectedIndex - 1; - } - return true; - } -} diff --git a/django/contrib/admin/media/js/admin/CollapsedFieldsets.js b/django/contrib/admin/media/js/admin/CollapsedFieldsets.js index c8426db228..d66bec0d97 100644 --- a/django/contrib/admin/media/js/admin/CollapsedFieldsets.js +++ b/django/contrib/admin/media/js/admin/CollapsedFieldsets.js @@ -47,7 +47,7 @@ var CollapsedFieldsets = { // Returns true if any fields in the fieldset have validation errors. var divs = fs.getElementsByTagName('div'); for (var i=0; i class for a given radio_admin field +get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '') + +class IncorrectLookupParameters(Exception): + pass + +def flatten_fieldsets(fieldsets): + """Returns a list of field names from an admin fieldsets structure.""" + field_names = [] + for name, opts in fieldsets: + for field in opts['fields']: + # type checking feels dirty, but it seems like the best way here + if type(field) == tuple: + field_names.extend(field) + else: + field_names.append(field) + return field_names + +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()] + + def __iter__(self): + for name, options in self.fieldsets: + yield Fieldset(self.form, name, **options) + + def first_field(self): + for bf in self.form: + return bf + + def _media(self): + media = self.form.media + for fs in self: + media = media + fs.media + return media + media = property(_media) + +class Fieldset(object): + def __init__(self, form, name=None, fields=(), classes=(), description=None): + self.form = form + self.name, self.fields = name, fields + self.classes = u' '.join(classes) + self.description = description + + def _media(self): + from django.conf import settings + if 'collapse' in self.classes: + return forms.Media(js=['%sjs/admin/CollapsedFieldsets.js' % settings.ADMIN_MEDIA_PREFIX]) + return forms.Media() + media = property(_media) + + def __iter__(self): + for field in self.fields: + yield Fieldline(self.form, field) + +class Fieldline(object): + def __init__(self, form, field): + self.form = form # A django.forms.Form instance + if isinstance(field, basestring): + self.fields = [field] + else: + self.fields = field + + def __iter__(self): + for i, field in enumerate(self.fields): + 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])) + +class AdminField(object): + def __init__(self, form, field, is_first): + self.field = form[field] # A django.forms.BoundField instance + self.is_first = is_first # Whether this field is first on the line + self.is_checkbox = isinstance(self.field.field.widget, forms.CheckboxInput) + + def label_tag(self): + classes = [] + if self.is_checkbox: + classes.append(u'vCheckboxLabel') + contents = escape(self.field.label) + else: + contents = force_unicode(escape(self.field.label)) + u':' + if self.field.field.required: + classes.append(u'required') + if not self.is_first: + classes.append(u'inline') + attrs = classes and {'class': u' '.join(classes)} or {} + return self.field.label_tag(contents=contents, attrs=attrs) + +class BaseModelAdmin(object): + """Functionality common to both ModelAdmin and InlineAdmin.""" + raw_id_fields = () + fields = None + fieldsets = None + form = forms.ModelForm + filter_vertical = () + filter_horizontal = () + radio_fields = {} + prepopulated_fields = {} + + def formfield_for_dbfield(self, db_field, **kwargs): + """ + Hook for specifying the form Field instance for a given database Field + instance. + + If kwargs are given, they're passed to the form Field's constructor. + """ + # For DateTimeFields, use a special field and widget. + if isinstance(db_field, models.DateTimeField): + kwargs['form_class'] = forms.SplitDateTimeField + kwargs['widget'] = widgets.AdminSplitDateTime() + return db_field.formfield(**kwargs) + + # For DateFields, add a custom CSS class. + if isinstance(db_field, models.DateField): + kwargs['widget'] = widgets.AdminDateWidget + return db_field.formfield(**kwargs) + + # For TimeFields, add a custom CSS class. + if isinstance(db_field, models.TimeField): + kwargs['widget'] = widgets.AdminTimeWidget + return db_field.formfield(**kwargs) + + # For FileFields and ImageFields add a link to the current file. + if isinstance(db_field, models.ImageField) or isinstance(db_field, models.FileField): + kwargs['widget'] = widgets.AdminFileWidget + return db_field.formfield(**kwargs) + + # For ForeignKey or ManyToManyFields, use a special widget. + if isinstance(db_field, (models.ForeignKey, models.ManyToManyField)): + if isinstance(db_field, models.ForeignKey) and db_field.name in self.raw_id_fields: + kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel) + elif isinstance(db_field, models.ForeignKey) and db_field.name in self.radio_fields: + kwargs['widget'] = widgets.AdminRadioSelect(attrs={ + 'class': get_ul_class(self.radio_fields[db_field.name]), + }) + 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: + kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel) + kwargs['help_text'] = '' + elif db_field.name in (self.filter_vertical + 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. + formfield = db_field.formfield(**kwargs) + # Don't wrap raw_id fields. Their add function is in the popup window. + 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) + + def _declared_fieldsets(self): + if self.fieldsets: + return self.fieldsets + elif self.fields: + return [(None, {'fields': self.fields})] + return None + declared_fieldsets = property(_declared_fieldsets) + +class ModelAdmin(BaseModelAdmin): + "Encapsulates all admin options and functionality for a given model." + __metaclass__ = forms.MediaDefiningClass + + list_display = ('__str__',) + list_display_links = () + list_filter = () + list_select_related = False + list_per_page = 100 + search_fields = () + date_hierarchy = None + save_as = False + 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 + self.opts = model._meta + self.admin_site = admin_site + self.inline_instances = [] + for inline_class in self.inlines: + inline_instance = inline_class(self.model, self.admin_site) + self.inline_instances.append(inline_instance) + super(ModelAdmin, self).__init__() + + def __call__(self, request, url): + # Check that LogEntry, ContentType and the auth context processor are installed. + from django.conf import settings + if settings.DEBUG: + from django.contrib.admin.models import LogEntry + if not LogEntry._meta.installed: + raise ImproperlyConfigured("Put 'django.contrib.admin' in your INSTALLED_APPS setting in order to use the admin application.") + if not ContentType._meta.installed: + raise ImproperlyConfigured("Put 'django.contrib.contenttypes' in your INSTALLED_APPS setting in order to use the admin application.") + if 'django.core.context_processors.auth' not in settings.TEMPLATE_CONTEXT_PROCESSORS: + raise ImproperlyConfigured("Put 'django.core.context_processors.auth' in your TEMPLATE_CONTEXT_PROCESSORS setting in order to use the admin application.") + + # Delegate to the appropriate method, based on the URL. + if url is None: + return self.changelist_view(request) + elif url.endswith('add'): + return self.add_view(request) + elif url.endswith('history'): + return self.history_view(request, unquote(url[:-8])) + elif url.endswith('delete'): + return self.delete_view(request, unquote(url[:-7])) + else: + return self.change_view(request, unquote(url)) + + def _media(self): + from django.conf import settings + + js = ['js/core.js', 'js/admin/RelatedObjectLookups.js'] + if self.prepopulated_fields: + js.append('js/urlify.js') + if self.opts.get_ordered_objects(): + 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) + + def has_add_permission(self, request): + "Returns True if the given request has permission to add an object." + opts = self.opts + return request.user.has_perm(opts.app_label + '.' + opts.get_add_permission()) + + def has_change_permission(self, request, obj=None): + """ + Returns True if the given request has permission to change the given + Django model instance. + + If `obj` is None, this should return True if the given request has + permission to change *any* object of the given type. + """ + opts = self.opts + return request.user.has_perm(opts.app_label + '.' + opts.get_change_permission()) + + def has_delete_permission(self, request, obj=None): + """ + Returns True if the given request has permission to change the given + Django model instance. + + If `obj` is None, this should return True if the given request has + permission to delete *any* object of the given type. + """ + opts = self.opts + return request.user.has_perm(opts.app_label + '.' + opts.get_delete_permission()) + + def queryset(self, request): + """ + Returns a QuerySet of all model instances that can be edited by the + admin site. This is used by changelist_view. + """ + qs = self.model._default_manager.get_query_set() + # TODO: this should be handled by some parameter to the ChangeList. + ordering = self.ordering or () # otherwise we might try to *None, which is bad ;) + if ordering: + qs = qs.order_by(*ordering) + return qs + + def get_fieldsets(self, request, obj=None): + "Hook for specifying fieldsets for the add form." + if self.declared_fieldsets: + return self.declared_fieldsets + form = self.get_form(request) + return [(None, {'fields': form.base_fields.keys()})] + + def get_form(self, request, obj=None): + """ + Returns a Form class for use in the admin add view. This is used by + add_view and change_view. + """ + if self.declared_fieldsets: + fields = flatten_fieldsets(self.declared_fieldsets) + else: + fields = None + return modelform_factory(self.model, form=self.form, fields=fields, formfield_callback=self.formfield_for_dbfield) + + def get_formsets(self, request, obj=None): + for inline in self.inline_instances: + yield inline.get_formset(request, obj) + + def save_add(self, request, form, formsets, post_url_continue): + """ + Saves the object in the "add" stage and returns an HttpResponseRedirect. + + `form` is a bound Form instance that's verified to be valid. + """ + from django.contrib.admin.models import LogEntry, ADDITION + opts = self.model._meta + new_object = form.save(commit=True) + + if formsets: + for formset in formsets: + # HACK: it seems like the parent obejct should be passed into + # a method of something, not just set as an attribute + formset.instance = new_object + formset.save() + + 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} + # Here, we distinguish between different save types by checking for + # the presence of keys in request.POST. + if request.POST.has_key("_continue"): + request.user.message_set.create(message=msg + ' ' + _("You may edit it again below.")) + 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('' % \ + # 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)) + return HttpResponseRedirect(request.path) + else: + request.user.message_set.create(message=msg) + # Figure out where to redirect. If the user has change permission, + # redirect to the change-list page for this object. Otherwise, + # redirect to the admin index. + if self.has_change_permission(request, None): + post_url = '../' + else: + post_url = '../../../' + return HttpResponseRedirect(post_url) + save_add = transaction.commit_on_success(save_add) + + def save_change(self, request, form, formsets=None): + """ + 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 + opts = self.model._meta + new_object = form.save(commit=True) + pk_value = new_object._get_pk_val() + + if formsets: + for formset in formsets: + formset.save() + + # Construct the change message. + 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".') + % {'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, + 'object': changed_object}) + for deleted_object in formset.deleted_objects: + change_message.append(_('Deleted %(name)s "%(object)s".') + % {'name': deleted_object._meta.verbose_name, + 'object': deleted_object}) + change_message = ' '.join(change_message) + if not change_message: + 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} + 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'): + return HttpResponseRedirect(request.path + "?_popup=1") + 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}) + 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)) + return HttpResponseRedirect("../add/") + else: + request.user.message_set.create(message=msg) + return HttpResponseRedirect("../") + save_change = transaction.commit_on_success(save_change) + + def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None): + opts = self.model._meta + app_label = opts.app_label + ordered_objects = opts.get_ordered_objects() + context.update({ + 'add': add, + 'change': change, + 'has_add_permission': self.has_add_permission(request), + 'has_change_permission': self.has_change_permission(request, obj), + 'has_delete_permission': self.has_delete_permission(request, obj), + 'has_file_field': True, # FIXME - this should check if form or formsets have a FileField, + 'has_absolute_url': hasattr(self.model, 'get_absolute_url'), + 'ordered_objects': ordered_objects, + 'form_url': mark_safe(form_url), + 'opts': opts, + 'content_type_id': ContentType.objects.get_for_model(self.model).id, + 'save_as': self.save_as, + 'save_on_top': self.save_on_top, + 'root_path': self.admin_site.root_path, + }) + 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" + ], context, context_instance=template.RequestContext(request)) + + def add_view(self, request, form_url='', extra_context=None): + "The 'add' admin view for this model." + model = self.model + opts = model._meta + app_label = opts.app_label + + if not self.has_add_permission(request): + raise PermissionDenied + + if self.has_change_permission(request, None): + # redirect to list view + post_url = '../' + else: + # Object list will give 'Permission Denied', so go back to admin home + post_url = '../../../' + + ModelForm = self.get_form(request) + inline_formsets = [] + obj = self.model() + if request.method == 'POST': + form = ModelForm(request.POST, request.FILES) + for FormSet in self.get_formsets(request): + inline_formset = FormSet(data=request.POST, files=request.FILES, + instance=obj, save_as_new=request.POST.has_key("_saveasnew")) + inline_formsets.append(inline_formset) + if all_valid(inline_formsets) and form.is_valid(): + return self.save_add(request, form, inline_formsets, '../%s/') + else: + form = ModelForm(initial=dict(request.GET.items())) + for FormSet in self.get_formsets(request): + inline_formset = FormSet(instance=obj) + inline_formsets.append(inline_formset) + + adminForm = AdminForm(form, list(self.get_fieldsets(request)), 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)) + inline_admin_formset = InlineAdminFormSet(inline, formset, fieldsets) + inline_admin_formsets.append(inline_admin_formset) + + context = { + 'title': _('Add %s') % opts.verbose_name, + 'adminform': adminForm, + 'is_popup': request.REQUEST.has_key('_popup'), + 'show_delete': False, + 'media': mark_safe(media), + 'inline_admin_formsets': inline_admin_formsets, + 'errors': AdminErrorList(form, inline_formsets), + 'root_path': self.admin_site.root_path, + } + context.update(extra_context or {}) + return self.render_change_form(request, context, add=True) + + def change_view(self, request, object_id, extra_context=None): + "The 'change' admin view for this model." + model = self.model + opts = model._meta + app_label = opts.app_label + + try: + obj = model._default_manager.get(pk=object_id) + except model.DoesNotExist: + # Don't raise Http404 just yet, because we haven't checked + # permissions yet. We don't want an unauthenticated user to be able + # to determine whether a given object exists. + obj = None + + if not self.has_change_permission(request, obj): + raise PermissionDenied + + if obj is None: + raise Http404('%s object with primary key %r does not exist.' % (opts.verbose_name, escape(object_id))) + + if request.POST and request.POST.has_key("_saveasnew"): + return self.add_view(request, form_url='../../add/') + + ModelForm = self.get_form(request, obj) + inline_formsets = [] + if request.method == 'POST': + form = ModelForm(request.POST, request.FILES, instance=obj) + for FormSet in self.get_formsets(request, obj): + inline_formset = FormSet(request.POST, request.FILES, instance=obj) + inline_formsets.append(inline_formset) + + if all_valid(inline_formsets) and form.is_valid(): + return self.save_change(request, form, inline_formsets) + else: + form = ModelForm(instance=obj) + for FormSet in self.get_formsets(request, obj): + inline_formset = FormSet(instance=obj) + inline_formsets.append(inline_formset) + + 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) + + context = { + 'title': _('Change %s') % opts.verbose_name, + 'adminform': adminForm, + 'object_id': object_id, + 'original': obj, + 'is_popup': request.REQUEST.has_key('_popup'), + 'media': mark_safe(media), + 'inline_admin_formsets': inline_admin_formsets, + 'errors': AdminErrorList(form, inline_formsets), + 'root_path': self.admin_site.root_path, + } + context.update(extra_context or {}) + return self.render_change_form(request, context, change=True, obj=obj) + + def changelist_view(self, request, extra_context=None): + "The 'change list' admin view for this model." + from django.contrib.admin.views.main import ChangeList, ERROR_FLAG + opts = self.model._meta + app_label = opts.app_label + if not self.has_change_permission(request, None): + raise PermissionDenied + try: + cl = ChangeList(request, self.model, self.list_display, self.list_display_links, self.list_filter, + self.date_hierarchy, self.search_fields, self.list_select_related, self.list_per_page, self) + except IncorrectLookupParameters: + # Wacky lookup parameters were given, so redirect to the main + # changelist page, without parameters, and pass an 'invalid=1' + # parameter via the query string. If wacky parameters were given and + # the 'invalid=1' parameter was already in the query string, something + # is screwed up with the database, so display an error page. + 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, + 'cl': cl, + 'has_add_permission': self.has_add_permission(request), + 'root_path': self.admin_site.root_path, + } + context.update(extra_context or {}) + 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' + ], context, context_instance=template.RequestContext(request)) + + def delete_view(self, request, object_id, extra_context=None): + "The 'delete' admin view for this model." + from django.contrib.admin.models import LogEntry, DELETION + opts = self.model._meta + app_label = opts.app_label + + try: + obj = self.model._default_manager.get(pk=object_id) + except self.model.DoesNotExist: + # Don't raise Http404 just yet, because we haven't checked + # permissions yet. We don't want an unauthenticated user to be able + # to determine whether a given object exists. + obj = None + + if not self.has_delete_permission(request, obj): + raise PermissionDenied + + if obj is None: + raise Http404('%s object with primary key %r does not exist.' % (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: %s' % (escape(force_unicode(capfirst(opts.verbose_name))), quote(object_id), escape(obj))), []] + perms_needed = sets.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. + if perms_needed: + raise PermissionDenied + obj_display = str(obj) + obj.delete() + LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(self.model).id, object_id, obj_display, DELETION) + request.user.message_set.create(message=_('The %(name)s "%(obj)s" was deleted successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj_display)}) + if not self.has_change_permission(request, None): + return HttpResponseRedirect("../../../../") + return HttpResponseRedirect("../../") + + context = { + "title": _("Are you sure?"), + "object_name": opts.verbose_name, + "object": obj, + "deleted_objects": deleted_objects, + "perms_lacking": perms_needed, + "opts": opts, + "root_path": self.admin_site.root_path, + } + context.update(extra_context or {}) + 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" + ], context, context_instance=template.RequestContext(request)) + + def history_view(self, request, object_id, extra_context=None): + "The 'history' admin view for this model." + 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') + # If no history was found, see whether this object even exists. + obj = get_object_or_404(model, pk=object_id) + context = { + 'title': _('Change history: %s') % force_unicode(obj), + 'action_list': action_list, + 'module_name': capfirst(opts.verbose_name_plural), + 'object': obj, + 'root_path': self.admin_site.root_path, + } + context.update(extra_context or {}) + 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" + ], context, context_instance=template.RequestContext(request)) + +class InlineModelAdmin(BaseModelAdmin): + """ + Options for inline editing of ``model`` instances. + + Provide ``name`` to specify the attribute name of the ``ForeignKey`` from + ``model`` to its parent. This is required if ``model`` has more than one + ``ForeignKey`` to its parent. + """ + model = None + fk_name = None + formset = BaseInlineFormset + extra = 3 + max_num = 0 + template = None + verbose_name = None + verbose_name_plural = None + + def __init__(self, parent_model, admin_site): + self.admin_site = admin_site + self.parent_model = parent_model + self.opts = self.model._meta + super(InlineModelAdmin, self).__init__() + if self.verbose_name is None: + self.verbose_name = self.model._meta.verbose_name + if self.verbose_name_plural is None: + self.verbose_name_plural = self.model._meta.verbose_name_plural + + def get_formset(self, request, obj=None): + """Returns a BaseInlineFormSet class for use in admin add/change views.""" + if self.declared_fieldsets: + fields = flatten_fieldsets(self.declared_fieldsets) + else: + fields = None + return inlineformset_factory(self.parent_model, self.model, + form=self.form, formset=self.formset, fk_name=self.fk_name, + fields=fields, formfield_callback=self.formfield_for_dbfield, + extra=self.extra, max_num=self.max_num) + + def get_fieldsets(self, request, obj=None): + if self.declared_fieldsets: + return self.declared_fieldsets + form = self.get_formset(request).form + return [(None, {'fields': form.base_fields.keys()})] + +class StackedInline(InlineModelAdmin): + template = 'admin/edit_inline/stacked.html' + +class TabularInline(InlineModelAdmin): + template = 'admin/edit_inline/tabular.html' + +class InlineAdminFormSet(object): + """ + A wrapper around an inline formset for use in the admin system. + """ + def __init__(self, inline, formset, fieldsets): + self.opts = inline + self.formset = formset + self.fieldsets = fieldsets + + def __iter__(self): + for form, original in zip(self.formset.initial_forms, self.formset.get_queryset()): + yield InlineAdminForm(self.formset, form, self.fieldsets, self.opts.prepopulated_fields, original) + for form in self.formset.extra_forms: + yield InlineAdminForm(self.formset, form, self.fieldsets, self.opts.prepopulated_fields, None) + + def fields(self): + for field_name in flatten_fieldsets(self.fieldsets): + yield self.formset.form.base_fields[field_name] + +class InlineAdminForm(AdminForm): + """ + A wrapper around an inline form for use in the admin system. + """ + def __init__(self, formset, form, fieldsets, prepopulated_fields, original): + self.formset = formset + self.original = original + self.show_url = original and hasattr(original, 'get_absolute_url') + super(InlineAdminForm, self).__init__(form, fieldsets, prepopulated_fields) + + def pk_field(self): + return AdminField(self.form, self.formset._pk_field_name, False) + + def deletion_field(self): + from django.newforms.formsets import DELETION_FIELD_NAME + return AdminField(self.form, DELETION_FIELD_NAME, False) + + def ordering_field(self): + from django.newforms.formsets import ORDERING_FIELD_NAME + return AdminField(self.form, ORDERING_FIELD_NAME, False) + +class AdminErrorList(forms.util.ErrorList): + """ + Stores all errors for the form/formsets in an add/change stage view. + """ + def __init__(self, form, inline_formsets): + if form.is_bound: + self.extend(form.errors.values()) + for inline_formset in inline_formsets: + self.extend(inline_formset.non_form_errors()) + for errors_in_inline_form in inline_formset.errors: + self.extend(errors_in_inline_form.values()) diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py new file mode 100644 index 0000000000..bb4dc58ece --- /dev/null +++ b/django/contrib/admin/sites.py @@ -0,0 +1,349 @@ +from django import http, template +from django.contrib.admin import ModelAdmin +from django.contrib.auth import authenticate, login +from django.db.models.base import ModelBase +from django.shortcuts import render_to_response +from django.utils.safestring import mark_safe +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 + +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' + +USER_CHANGE_PASSWORD_URL_RE = re.compile('auth/user/(\d+)/password') + +class AlreadyRegistered(Exception): + pass + +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() + 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: + from django.core.exceptions import SuspiciousOperation + raise SuspiciousOperation, "User may have tampered with session cookie." + 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 + + def register(self, model_or_iterable, admin_class=None, **options): + """ + Registers the given model(s) with the given admin class. + + The model(s) should be Model classes, not instances. + + If an admin class isn't given, it will use ModelAdmin (the default + admin options). If keyword arguments are given -- e.g., list_display -- + they'll be applied as options to the admin class. + + 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 + from django.contrib.admin.validation import validate + admin_class = admin_class or ModelAdmin + # TODO: Handle options + 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) + self._registry[model] = admin_class(model, self) + + def unregister(self, model_or_iterable): + """ + Unregisters the given model(s). + + If a model isn't already registered, this will raise NotRegistered. + """ + if isinstance(model_or_iterable, ModelBase): + model_or_iterable = [model_or_iterable] + for model in model_or_iterable: + if model not in self._registry: + raise NotRegistered('The model %s is not registered' % model.__name__) + del self._registry[model] + + def has_permission(self, request): + """ + Returns True if the given HttpRequest has permission to view + *at least one* page in the admin site. + """ + 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) + + if url == '': + return self.index(request) + elif url == 'password_change': + return self.password_change(request) + elif url == 'password_change/done': + return self.password_change_done(request) + elif url == 'jsi18n': + return self.i18n_javascript(request) + # urls starting with 'r/' are for the "show in web" links + elif url.startswith('r/'): + from django.views.defaults import shortcut + return shortcut(request, *url.split('/')[1:]) + else: + 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)) + + raise http.Http404('The requested admin page does not exist.') + + def model_page(self, request, app_label, model_name, rest_of_url=None): + """ + Handles the model-specific functionality of the admin site, delegating + to the appropriate ModelAdmin class. + """ + from django.db import models + model = models.get_model(app_label, model_name) + if model is None: + raise http.Http404("App %r, model %r, not found." % (app_label, model_name)) + try: + admin_obj = self._registry[model] + except KeyError: + raise http.Http404("This model exists but has not been registered with the admin site.") + return admin_obj(request, rest_of_url) + model_page = never_cache(model_page) + + def password_change(self, request): + """ + Handles the "change password" task -- both form display and validation. + """ + from django.contrib.auth.views import password_change + return password_change(request) + + def password_change_done(self, request): + """ + Displays the "success" page after a password change. + """ + from django.contrib.auth.views import password_change_done + return password_change_done(request) + + def user_change_password(self, request, id): + """ + Handles the "user change password" task + """ + from django.contrib.auth.views import user_change_password + return user_change_password(request, id) + + def i18n_javascript(self, request): + """ + Displays the i18n JavaScript that the Django admin requires. + + 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: + from django.views.i18n import null_javascript_catalog as javascript_catalog + return javascript_catalog(request, packages='django.conf') + + def logout(self, request): + """ + Logs out the user for the given HttpRequest. + + This should *not* assume the user is already logged in. + """ + from django.contrib.auth.views import logout + return logout(request) + logout = never_cache(logout) + + def login(self, request): + """ + Displays the login form for the given HttpRequest. + """ + from django.contrib.auth.models import User + + # If this isn't already the login page, display it. + if not request.POST.has_key(LOGIN_FORM_KEY): + if request.POST: + message = _("Please log in again, because your session has expired. Don't worry: Your submission has been saved.") + else: + 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 self.display_login_form(request, message) + + # Check the password. + username = request.POST.get('username', None) + password = request.POST.get('password', None) + user = authenticate(username=username, password=password) + if user is None: + message = ERROR_MESSAGE + if u'@' in username: + # Mistakenly entered e-mail address instead of username? Look it up. + try: + user = User.objects.get(email=username) + except (User.DoesNotExist, User.MultipleObjectsReturned): + message = _("Usernames cannot contain the '@' character.") + else: + if user.check_password(password): + message = _("Your e-mail address is not your username." + " Try '%s' instead.") % user.username + else: + message = _("Usernames cannot contain the '@' character.") + return self.display_login_form(request, message) + + # The user data is correct; log in the user in and continue. + 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): + # overwrite request.POST with the saved post_data, and continue + request.POST = post_data + request.user = user + return self.root(request, request.path.split(self.root_path)[-1]) + else: + request.session.delete_test_cookie() + return http.HttpResponseRedirect(request.path) + else: + return self.display_login_form(request, ERROR_MESSAGE) + login = never_cache(login) + + def index(self, request, extra_context=None): + """ + Displays the main admin index page, which lists all of the installed + apps that have been registered in this site. + """ + app_dict = {} + user = request.user + for model, model_admin in self._registry.items(): + app_label = model._meta.app_label + has_module_perms = user.has_module_perms(app_label) + + if has_module_perms: + perms = { + 'add': model_admin.has_add_permission(request), + 'change': model_admin.has_change_permission(request), + 'delete': model_admin.has_delete_permission(request), + } + + # Check whether user has any perm for this module. + # If so, add the module to the model_list. + if True in perms.values(): + model_dict = { + 'name': capfirst(model._meta.verbose_name_plural), + 'admin_url': mark_safe('%s/%s/' % (app_label, model.__name__.lower())), + 'perms': perms, + } + if app_label in app_dict: + app_dict[app_label]['models'].append(model_dict) + else: + app_dict[app_label] = { + 'name': app_label.title(), + 'has_module_perms': has_module_perms, + 'models': [model_dict], + } + + # Sort the apps alphabetically. + app_list = app_dict.values() + app_list.sort(lambda x, y: cmp(x['name'], y['name'])) + + # 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, + context_instance=template.RequestContext(request) + ) + index = never_cache(index) + + def display_login_form(self, request, error_message='', extra_context=None): + 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({}) + + context = { + 'title': _('Log in'), + 'app_path': request.path, + 'post_data': post_data, + 'error_message': error_message, + 'root_path': self.root_path, + } + context.update(extra_context or {}) + return render_to_response(self.login_template or 'admin/login.html', context, + 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/django/contrib/admin/templates/admin/auth/user/add_form.html b/django/contrib/admin/templates/admin/auth/user/add_form.html index 139fa6a75e..65824a6b7d 100644 --- a/django/contrib/admin/templates/admin/auth/user/add_form.html +++ b/django/contrib/admin/templates/admin/auth/user/add_form.html @@ -8,21 +8,26 @@
- {{ form.username.html_error_list }} + {{ form.username.errors }} + {# TODO: get required class on label_tag #} {{ form.username }} -

{{ username_help_text }}

+

{{ form.username.help_text }}

- {{ form.password1.html_error_list }} + {{ form.password1.errors }} + {# TODO: get required class on label_tag #} {{ form.password1 }}
- {{ form.password2.html_error_list }} + {{ form.password2.errors }} + {# TODO: get required class on label_tag #} {{ form.password2 }}

{% trans 'Enter the same password as above, for verification.' %}

+ +
{% endblock %} diff --git a/django/contrib/admin/templates/admin/auth/user/change_password.html b/django/contrib/admin/templates/admin/auth/user/change_password.html index 28e342da86..f1c4a8d34a 100644 --- a/django/contrib/admin/templates/admin/auth/user/change_password.html +++ b/django/contrib/admin/templates/admin/auth/user/change_password.html @@ -2,7 +2,6 @@ {% load i18n admin_modify adminmedia %} {% block extrahead %}{{ block.super }} -{% for js in javascript_imports %}{% include_admin_script js %}{% endfor %} {% endblock %} {% block stylesheet %}{% admin_media_prefix %}css/forms.css{% endblock %} {% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }} change-form{% endblock %} @@ -18,9 +17,9 @@
{% block form_top %}{% endblock %}
{% if is_popup %}{% endif %} -{% if form.error_dict %} +{% if form.errors %}

- {% blocktrans count form.error_dict.items|length as counter %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %} + {% blocktrans count form.errors.items|length as counter %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %}

{% endif %} @@ -29,12 +28,14 @@
- {{ form.password1.html_error_list }} + {{ form.password1.errors }} + {# TODO: get required class on label_tag #} {{ form.password1 }}
- {{ form.password2.html_error_list }} + {{ form.password2.errors }} + {# TODO: get required class on label_tag #} {{ form.password2 }}

{% trans 'Enter the same password as above, for verification.' %}

@@ -45,7 +46,7 @@
- +
{% endblock %} diff --git a/django/contrib/admin/templates/admin/base.html b/django/contrib/admin/templates/admin/base.html index cdd3561ab9..479e18b2ee 100644 --- a/django/contrib/admin/templates/admin/base.html +++ b/django/contrib/admin/templates/admin/base.html @@ -22,14 +22,7 @@ {% block branding %}{% endblock %} {% if user.is_authenticated and user.is_staff %} -
- {% trans 'Welcome,' %} {% if user.first_name %}{{ user.first_name|escape }}{% else %}{{ user.username }}{% endif %}. - {% block userlinks %} - {% trans 'Documentation' %} - / {% trans 'Change password' %} - / {% trans 'Log out' %} - {% endblock %} -
+
{% trans 'Welcome,' %} {% if user.first_name %}{{ user.first_name|escape }}{% else %}{{ user.username }}{% endif %}. {% block userlinks %}{% trans 'Documentation' %} / {% trans 'Change password' %} / {% trans 'Log out' %}{% endblock %}
{% endif %} {% block nav-global %}{% endblock %} diff --git a/django/contrib/admin/templates/admin/change_form.html b/django/contrib/admin/templates/admin/change_form.html index d540cf6e79..e8df6b99f5 100644 --- a/django/contrib/admin/templates/admin/change_form.html +++ b/django/contrib/admin/templates/admin/change_form.html @@ -1,12 +1,17 @@ {% extends "admin/base_site.html" %} {% load i18n admin_modify adminmedia %} + {% block extrahead %}{{ block.super }} -{% for js in javascript_imports %}{% include_admin_script js %}{% endfor %} +{{ media }} {% endblock %} + {% block stylesheet %}{% admin_media_prefix %}css/forms.css{% endblock %} + {% block coltype %}{% if ordered_objects %}colMS{% else %}colM{% endif %}{% endblock %} + {% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }} change-form{% endblock %} + {% block breadcrumbs %}{% if not is_popup %} {% endif %}{% endblock %} + {% block content %}
{% block object-tools %} {% if change %}{% if not is_popup %} @@ -25,45 +31,48 @@
{% block form_top %}{% endblock %}
{% if is_popup %}{% endif %} -{% if opts.admin.save_on_top %}{% submit_row %}{% endif %} -{% if form.error_dict %} +{% if save_on_top %}{% submit_row %}{% endif %} +{% if errors %}

- {% blocktrans count form.error_dict.items|length as counter %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %} + {% blocktrans count errors.items|length as counter %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %}

+
    {% for error in adminform.form.non_field_errors %}
  • {{ error }}
  • {% endfor %}
{% endif %} -{% for bound_field_set in bound_field_sets %} -
- {% if bound_field_set.name %}

{{ bound_field_set.name }}

{% endif %} - {% if bound_field_set.description %}
{{ bound_field_set.description|safe }}
{% endif %} - {% for bound_field_line in bound_field_set %} - {% admin_field_line bound_field_line %} - {% for bound_field in bound_field_line %} - {% filter_interface_script_maybe bound_field %} - {% endfor %} - {% endfor %} -
+ +{% for fieldset in adminform %} + {% include "admin/includes/fieldset.html" %} {% endfor %} + {% block after_field_sets %}{% endblock %} -{% if change %} - {% if ordered_objects %} -

{% trans "Ordering" %}

-
- {% if form.order_.errors %}{{ form.order_.html_error_list }}{% endif %} -

{{ form.order_ }}

-
- {% endif %} -{% endif %} -{% for related_object in inline_related_objects %}{% edit_inline related_object %}{% endfor %} + +{% for inline_admin_formset in inline_admin_formsets %} + {% include inline_admin_formset.opts.template %} +{% endfor %} + {% block after_related_objects %}{% endblock %} + {% submit_row %} + {% if add %} - + {% endif %} -{% if auto_populated_fields %} - + +{# JavaScript for prepopulated fields #} + +{% if add %} + {% endif %} +
{% endblock %} diff --git a/django/contrib/admin/templates/admin/change_list.html b/django/contrib/admin/templates/admin/change_list.html index 611c5ff8fc..24286a51a7 100644 --- a/django/contrib/admin/templates/admin/change_list.html +++ b/django/contrib/admin/templates/admin/change_list.html @@ -1,9 +1,14 @@ {% extends "admin/base_site.html" %} {% load adminmedia admin_list i18n %} + {% block stylesheet %}{% admin_media_prefix %}css/changelists.css{% endblock %} + {% block bodyclass %}change-list{% endblock %} + {% if not is_popup %}{% block breadcrumbs %}{% endblock %}{% endif %} + {% block coltype %}flex{% endblock %} + {% block content %}
{% block object-tools %} @@ -14,7 +19,18 @@
{% block search %}{% search_form cl %}{% endblock %} {% block date_hierarchy %}{% date_hierarchy cl %}{% endblock %} -{% block filters %}{% filters cl %}{% endblock %} + +{% block filters %} +{% if cl.has_filters %} +
+

{% trans 'Filter' %}

+{% for spec in cl.filter_specs %} + {% admin_list_filter cl spec %} +{% endfor %} +
+{% endif %} +{% endblock %} + {% block result_list %}{% result_list cl %}{% endblock %} {% block pagination %}{% pagination cl %}{% endblock %}
diff --git a/django/contrib/admin/templates/admin/delete_confirmation.html b/django/contrib/admin/templates/admin/delete_confirmation.html index f2126882fa..386e134b96 100644 --- a/django/contrib/admin/templates/admin/delete_confirmation.html +++ b/django/contrib/admin/templates/admin/delete_confirmation.html @@ -1,5 +1,6 @@ {% extends "admin/base_site.html" %} {% load i18n %} + {% block breadcrumbs %} {% endblock %} + {% block content %} {% if perms_lacking %}

{% blocktrans with object|escape as escaped_object %}Deleting the {{ object_name }} '{{ escaped_object }}' would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:{% endblocktrans %}

diff --git a/django/contrib/admin/templates/admin/edit_inline/stacked.html b/django/contrib/admin/templates/admin/edit_inline/stacked.html new file mode 100644 index 0000000000..c726b0fcda --- /dev/null +++ b/django/contrib/admin/templates/admin/edit_inline/stacked.html @@ -0,0 +1,26 @@ +{% load i18n %} +
+{{ inline_admin_formset.formset.management_form }} +{#

{{ inline_admin_formset.opts.verbose_name_plural|title }}

#} +{{ inline_admin_formset.formset.non_form_errors }} + +{% for inline_admin_form in inline_admin_formset %} + +{% endfor %} + +{# #} +
diff --git a/django/contrib/admin/templates/admin/edit_inline/tabular.html b/django/contrib/admin/templates/admin/edit_inline/tabular.html new file mode 100644 index 0000000000..f6332fafbc --- /dev/null +++ b/django/contrib/admin/templates/admin/edit_inline/tabular.html @@ -0,0 +1,64 @@ +{% load i18n %} +
+ + + {# #} + +
diff --git a/django/contrib/admin/templates/admin/edit_inline_stacked.html b/django/contrib/admin/templates/admin/edit_inline_stacked.html deleted file mode 100644 index 45aa0a4f58..0000000000 --- a/django/contrib/admin/templates/admin/edit_inline_stacked.html +++ /dev/null @@ -1,16 +0,0 @@ -{% load admin_modify %} -
- {% for fcw in bound_related_object.form_field_collection_wrappers %} -

{{ bound_related_object.relation.opts.verbose_name|capfirst }} #{{ forloop.counter }}

- {% if bound_related_object.show_url %}{% if fcw.obj.original %} -

View on site

- {% endif %}{% endif %} - {% for bound_field in fcw.bound_fields %} - {% if bound_field.hidden %} - {% field_widget bound_field %} - {% else %} - {% admin_field_line bound_field %} - {% endif %} - {% endfor %} - {% endfor %} -
diff --git a/django/contrib/admin/templates/admin/edit_inline_tabular.html b/django/contrib/admin/templates/admin/edit_inline_tabular.html deleted file mode 100644 index e2dbcbaed2..0000000000 --- a/django/contrib/admin/templates/admin/edit_inline_tabular.html +++ /dev/null @@ -1,44 +0,0 @@ -{% load admin_modify %} -
-

{{ bound_related_object.relation.opts.verbose_name_plural|capfirst }}

- - {% for fw in bound_related_object.field_wrapper_list %} - {% if fw.needs_header %} - {{ fw.field.verbose_name|capfirst }} - {% endif %} - {% endfor %} - - {% for fcw in bound_related_object.form_field_collection_wrappers %} - {% if change %}{% if original_row_needed %} - {% if fcw.obj.original %} - - {% endif %} - {% endif %}{% endif %} - {% if fcw.obj.errors %} - - {% endif %} - - {% for bound_field in fcw.bound_fields %} - {% if not bound_field.hidden %} - - {% endif %} - {% endfor %} - {% if bound_related_object.show_url %}{% endif %} - - - {% endfor %}
{{ fcw.obj.original }}
- {{ fcw.obj.html_combined_error_list }} -
- {% field_widget bound_field %} - - {% if fcw.obj.original %}View on site{% endif %} -
- - {% for fcw in bound_related_object.form_field_collection_wrappers %} - {% for bound_field in fcw.bound_fields %} - {% if bound_field.hidden %} - {% field_widget bound_field %} - {% endif %} - {% endfor %} - {% endfor %} -
diff --git a/django/contrib/admin/templates/admin/field_line.html b/django/contrib/admin/templates/admin/field_line.html deleted file mode 100644 index f4b53fff67..0000000000 --- a/django/contrib/admin/templates/admin/field_line.html +++ /dev/null @@ -1,10 +0,0 @@ -{% load admin_modify %} -
-{% for bound_field in bound_fields %}{{ bound_field.html_error_list }}{% endfor %} -{% for bound_field in bound_fields %} - {% if bound_field.has_label_first %}{% field_label bound_field %}{% endif %} - {% field_widget bound_field %} - {% if not bound_field.has_label_first %}{% field_label bound_field %}{% endif %} - {% if bound_field.field.help_text %}

{{ bound_field.field.help_text|safe }}

{% endif %} -{% endfor %} -
diff --git a/django/contrib/admin/templates/admin/filters.html b/django/contrib/admin/templates/admin/filters.html deleted file mode 100644 index 3ca763cce3..0000000000 --- a/django/contrib/admin/templates/admin/filters.html +++ /dev/null @@ -1,7 +0,0 @@ -{% load admin_list %} -{% load i18n %} -{% if cl.has_filters %}
-

{% trans 'Filter' %}

-{% for spec in cl.filter_specs %} - {% filter cl spec %} -{% endfor %}
{% endif %} diff --git a/django/contrib/admin/templates/admin/includes/fieldset.html b/django/contrib/admin/templates/admin/includes/fieldset.html new file mode 100644 index 0000000000..a61795cfe4 --- /dev/null +++ b/django/contrib/admin/templates/admin/includes/fieldset.html @@ -0,0 +1,17 @@ +
+ {% if fieldset.name %}

{{ fieldset.name }}

{% endif %} + {% if fieldset.description %}
{{ fieldset.description }}
{% endif %} + {% for line in fieldset %} +
+ {{ line.errors }} + {% for field in line %} + {% if field.is_checkbox %} + {{ field.field }}{{ field.label_tag }} + {% else %} + {{ field.label_tag }}{{ field.field }} + {% endif %} + {% if field.field.field.help_text %}

{{ field.field.field.help_text|safe }}

{% endif %} + {% endfor %} +
+ {% endfor %} +
\ No newline at end of file diff --git a/django/contrib/admin/templates/admin/index.html b/django/contrib/admin/templates/admin/index.html index 2f406a1754..074be03208 100644 --- a/django/contrib/admin/templates/admin/index.html +++ b/django/contrib/admin/templates/admin/index.html @@ -2,15 +2,16 @@ {% load i18n %} {% block stylesheet %}{% load adminmedia %}{% admin_media_prefix %}css/dashboard.css{% endblock %} + {% block coltype %}colMS{% endblock %} + {% block bodyclass %}dashboard{% endblock %} + {% block breadcrumbs %}{% endblock %} + {% block content %}
-{% load adminapplist %} - -{% get_admin_app_list as app_list %} {% if app_list %} {% for app in app_list %}
diff --git a/django/contrib/admin/templates/admin/invalid_setup.html b/django/contrib/admin/templates/admin/invalid_setup.html index 1fa0d32358..f09b316b06 100644 --- a/django/contrib/admin/templates/admin/invalid_setup.html +++ b/django/contrib/admin/templates/admin/invalid_setup.html @@ -4,7 +4,5 @@ {% block breadcrumbs %}{% endblock %} {% block content %} -

{% trans "Something's wrong with your database installation. Make sure the appropriate database tables have been created, and make sure the database is readable by the appropriate user." %}

- {% endblock %} diff --git a/django/contrib/admin/templates/admin/login.html b/django/contrib/admin/templates/admin/login.html index 0773132cec..5dd953bc23 100644 --- a/django/contrib/admin/templates/admin/login.html +++ b/django/contrib/admin/templates/admin/login.html @@ -2,12 +2,14 @@ {% load i18n %} {% block stylesheet %}{% load adminmedia %}{% admin_media_prefix %}css/login.css{% endblock %} + {% block bodyclass %}login{% endblock %} + {% block content_title %}{% endblock %} + {% block breadcrumbs %}{% endblock %} {% block content %} - {% if error_message %}

{{ error_message }}

{% endif %} diff --git a/django/contrib/admin/templates/admin/object_history.html b/django/contrib/admin/templates/admin/object_history.html index 00e47259bf..19c037cda9 100644 --- a/django/contrib/admin/templates/admin/object_history.html +++ b/django/contrib/admin/templates/admin/object_history.html @@ -1,16 +1,15 @@ {% extends "admin/base_site.html" %} {% load i18n %} + {% block breadcrumbs %} {% endblock %} {% block content %} -
{% if action_list %} - @@ -29,14 +28,9 @@ {% endfor %}
- {% else %} -

{% trans "This object doesn't have a change history. It probably wasn't added via this admin site." %}

- {% endif %} -
- {% endblock %} diff --git a/django/contrib/admin/templates/admin/search_form.html b/django/contrib/admin/templates/admin/search_form.html index 445cca3089..b232aa917d 100644 --- a/django/contrib/admin/templates/admin/search_form.html +++ b/django/contrib/admin/templates/admin/search_form.html @@ -1,6 +1,6 @@ {% load adminmedia %} {% load i18n %} -{% if cl.lookup_opts.admin.search_fields %} +{% if cl.search_fields %}
{% endblock %} + diff --git a/django/contrib/admin/templates/registration/password_change_done.html b/django/contrib/admin/templates/registration/password_change_done.html index 4498c63a37..252572001d 100644 --- a/django/contrib/admin/templates/registration/password_change_done.html +++ b/django/contrib/admin/templates/registration/password_change_done.html @@ -1,5 +1,6 @@ {% extends "admin/base_site.html" %} {% load i18n %} +{% block userlinks %}{% trans 'Documentation' %} / {% trans 'Change password' %} / {% trans 'Log out' %}{% endblock %} {% block breadcrumbs %}{% endblock %} {% block title %}{% trans 'Password change successful' %}{% endblock %} diff --git a/django/contrib/admin/templates/registration/password_change_form.html b/django/contrib/admin/templates/registration/password_change_form.html index cd2b1e0c8a..036d56212c 100644 --- a/django/contrib/admin/templates/registration/password_change_form.html +++ b/django/contrib/admin/templates/registration/password_change_form.html @@ -1,5 +1,6 @@ {% extends "admin/base_site.html" %} {% load i18n %} +{% block userlinks %}{% trans 'Documentation' %} / {% trans 'Change password' %} / {% trans 'Log out' %}{% endblock %} {% block breadcrumbs %}{% endblock %} {% block title %}{% trans 'Password change' %}{% endblock %} diff --git a/django/contrib/admin/templates/registration/password_reset_form.html b/django/contrib/admin/templates/registration/password_reset_form.html index 423821ba60..d8c7d03f93 100644 --- a/django/contrib/admin/templates/registration/password_reset_form.html +++ b/django/contrib/admin/templates/registration/password_reset_form.html @@ -12,7 +12,7 @@

{% trans "Forgotten your password? Enter your e-mail address below, and we'll reset your password and e-mail the new one to you." %}

-{% if form.email.errors %}{{ form.email.html_error_list }}{% endif %} +{% if form.email.errors %}{{ form.email.errors }}{% endif %}

{{ form.email }}

diff --git a/django/contrib/admin/templates/widget/date_time.html b/django/contrib/admin/templates/widget/date_time.html deleted file mode 100644 index cbd4a2e1c6..0000000000 --- a/django/contrib/admin/templates/widget/date_time.html +++ /dev/null @@ -1,5 +0,0 @@ -{% load i18n %} -

- {% trans "Date:" %} {{ bound_field.form_fields.0 }}
- {% trans "Time:" %} {{ bound_field.form_fields.1 }} -

diff --git a/django/contrib/admin/templates/widget/default.html b/django/contrib/admin/templates/widget/default.html deleted file mode 100644 index 0af231ddcb..0000000000 --- a/django/contrib/admin/templates/widget/default.html +++ /dev/null @@ -1 +0,0 @@ -{% load admin_modify %}{% output_all bound_field.form_fields %} diff --git a/django/contrib/admin/templates/widget/file.html b/django/contrib/admin/templates/widget/file.html deleted file mode 100644 index e584abf956..0000000000 --- a/django/contrib/admin/templates/widget/file.html +++ /dev/null @@ -1,4 +0,0 @@ -{% load admin_modify i18n %}{% if bound_field.original_value %} -{% trans "Currently:" %} {{ bound_field.original_value|escape }}
-{% trans "Change:" %}{% output_all bound_field.form_fields %} -{% else %} {% output_all bound_field.form_fields %} {% endif %} diff --git a/django/contrib/admin/templates/widget/foreign.html b/django/contrib/admin/templates/widget/foreign.html deleted file mode 100644 index 6b43d044bd..0000000000 --- a/django/contrib/admin/templates/widget/foreign.html +++ /dev/null @@ -1,20 +0,0 @@ -{% load admin_modify adminmedia %} -{% output_all bound_field.form_fields %} -{% if bound_field.raw_id_admin %} - {% if bound_field.field.rel.limit_choices_to %} - Lookup - {% else %} - Lookup - {% endif %} -{% else %} -{% if bound_field.needs_add_label %} - Add Another -{% endif %}{% endif %} -{% if change %} - {% if bound_field.field.primary_key %} - {{ bound_field.original_value }} - {% endif %} - {% if bound_field.raw_id_admin %} - {% if bound_field.existing_display %} {{ bound_field.existing_display|truncatewords:"14" }}{% endif %} - {% endif %} -{% endif %} diff --git a/django/contrib/admin/templates/widget/many_to_many.html b/django/contrib/admin/templates/widget/many_to_many.html deleted file mode 100644 index a93aa65f73..0000000000 --- a/django/contrib/admin/templates/widget/many_to_many.html +++ /dev/null @@ -1 +0,0 @@ -{% include "widget/foreign.html" %} diff --git a/django/contrib/admin/templates/widget/one_to_one.html b/django/contrib/admin/templates/widget/one_to_one.html deleted file mode 100644 index a79a12314f..0000000000 --- a/django/contrib/admin/templates/widget/one_to_one.html +++ /dev/null @@ -1,2 +0,0 @@ -{% if add %}{% include "widget/foreign.html" %}{% endif %} -{% if change %}{% if bound_field.existing_display %} {{ bound_field.existing_display|truncatewords:"14" }}{% endif %}{% endif %} diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py index 6db59ea338..87fad70ec3 100644 --- a/django/contrib/admin/templatetags/admin_list.py +++ b/django/contrib/admin/templatetags/admin_list.py @@ -71,7 +71,7 @@ pagination = register.inclusion_tag('admin/pagination.html')(pagination) def result_headers(cl): lookup_opts = cl.lookup_opts - for i, field_name in enumerate(lookup_opts.admin.list_display): + for i, field_name in enumerate(cl.list_display): try: f = lookup_opts.get_field(field_name) admin_order_field = None @@ -123,7 +123,7 @@ def _boolean_icon(field_val): def items_for_result(cl, result): first = True pk = cl.lookup_opts.pk.attname - for field_name in cl.lookup_opts.admin.list_display: + for field_name in cl.list_display: row_class = '' try: f = cl.lookup_opts.get_field(field_name) @@ -189,7 +189,7 @@ def items_for_result(cl, result): if force_unicode(result_repr) == '': result_repr = mark_safe(' ') # If list_display_links not defined, add the link tag to the first field - if (first and not cl.lookup_opts.admin.list_display_links) or field_name in cl.lookup_opts.admin.list_display_links: + if (first and not cl.list_display_links) or field_name in cl.list_display_links: table_tag = {True:'th', False:'td'}[first] first = False url = cl.url_for_result(result) @@ -212,8 +212,8 @@ def result_list(cl): result_list = register.inclusion_tag("admin/change_list_results.html")(result_list) def date_hierarchy(cl): - if cl.lookup_opts.admin.date_hierarchy: - field_name = cl.lookup_opts.admin.date_hierarchy + if cl.date_hierarchy: + field_name = cl.date_hierarchy year_field = '%s__year' % field_name month_field = '%s__month' % field_name day_field = '%s__day' % field_name @@ -280,10 +280,6 @@ def search_form(cl): } search_form = register.inclusion_tag('admin/search_form.html')(search_form) -def filter(cl, spec): +def admin_list_filter(cl, spec): return {'title': spec.title(), 'choices' : list(spec.choices(cl))} -filter = register.inclusion_tag('admin/filter.html')(filter) - -def filters(cl): - return {'cl': cl} -filters = register.inclusion_tag('admin/filters.html')(filters) +admin_list_filter = register.inclusion_tag('admin/filter.html')(admin_list_filter) diff --git a/django/contrib/admin/templatetags/admin_modify.py b/django/contrib/admin/templatetags/admin_modify.py index ef33bb33b0..25d2d6774a 100644 --- a/django/contrib/admin/templatetags/admin_modify.py +++ b/django/contrib/admin/templatetags/admin_modify.py @@ -1,253 +1,21 @@ from django import template -from django.contrib.admin.views.main import AdminBoundField -from django.template import loader -from django.utils.text import capfirst -from django.utils.encoding import force_unicode -from django.utils.safestring import mark_safe -from django.utils.html import escape -from django.db import models -from django.db.models.fields import Field -from django.db.models.related import BoundRelatedObject -from django.conf import settings -import re register = template.Library() -word_re = re.compile('[A-Z][a-z]+') -absolute_url_re = re.compile(r'^(?:http(?:s)?:/)?/', re.IGNORECASE) - -def class_name_to_underscored(name): - return u'_'.join([s.lower() for s in word_re.findall(name)[:-1]]) - -def include_admin_script(script_path): - """ - Returns an HTML script element for including a script from the admin - media url (or other location if an absolute url is given). - - Example usage:: - - {% include_admin_script "js/calendar.js" %} - - could return:: - - ' - % script_path) -include_admin_script = register.simple_tag(include_admin_script) - def submit_row(context): opts = context['opts'] change = context['change'] is_popup = context['is_popup'] + save_as = context['save_as'] return { 'onclick_attrib': (opts.get_ordered_objects() and change and 'onclick="submitOrderForm();"' or ''), 'show_delete_link': (not is_popup and context['has_delete_permission'] and (change or context['show_delete'])), - 'show_save_as_new': not is_popup and change and opts.admin.save_as, - 'show_save_and_add_another': not is_popup and (not opts.admin.save_as or context['add']), + 'show_save_as_new': not is_popup and change and save_as, + 'show_save_and_add_another': context['has_add_permission'] and + not is_popup and (not save_as or context['add']), 'show_save_and_continue': not is_popup and context['has_change_permission'], 'show_save': True } submit_row = register.inclusion_tag('admin/submit_line.html', takes_context=True)(submit_row) - -def field_label(bound_field): - class_names = [] - if isinstance(bound_field.field, models.BooleanField): - class_names.append("vCheckboxLabel") - colon = "" - else: - if not bound_field.field.blank: - class_names.append('required') - if not bound_field.first: - class_names.append('inline') - colon = ":" - class_str = class_names and u' class="%s"' % u' '.join(class_names) or u'' - return mark_safe(u' ' % - (bound_field.element_id, class_str, - escape(force_unicode(capfirst(bound_field.field.verbose_name))), - colon)) -field_label = register.simple_tag(field_label) - -class FieldWidgetNode(template.Node): - nodelists = {} - default = None - - def __init__(self, bound_field_var): - self.bound_field_var = template.Variable(bound_field_var) - - def get_nodelist(cls, klass): - if klass not in cls.nodelists: - try: - field_class_name = klass.__name__ - template_name = u"widget/%s.html" % class_name_to_underscored(field_class_name) - nodelist = loader.get_template(template_name).nodelist - except template.TemplateDoesNotExist: - super_klass = bool(klass.__bases__) and klass.__bases__[0] or None - if super_klass and super_klass != Field: - nodelist = cls.get_nodelist(super_klass) - else: - if not cls.default: - cls.default = loader.get_template("widget/default.html").nodelist - nodelist = cls.default - - cls.nodelists[klass] = nodelist - return nodelist - else: - return cls.nodelists[klass] - get_nodelist = classmethod(get_nodelist) - - def render(self, context): - bound_field = self.bound_field_var.resolve(context) - - context.push() - context['bound_field'] = bound_field - - output = self.get_nodelist(bound_field.field.__class__).render(context) - context.pop() - return output - -class FieldWrapper(object): - def __init__(self, field ): - self.field = field - - def needs_header(self): - return not isinstance(self.field, models.AutoField) - - def header_class_attribute(self): - return self.field.blank and mark_safe(' class="optional"') or '' - - def use_raw_id_admin(self): - return isinstance(self.field.rel, (models.ManyToOneRel, models.ManyToManyRel)) \ - and self.field.rel.raw_id_admin - -class FormFieldCollectionWrapper(object): - def __init__(self, field_mapping, fields, index): - self.field_mapping = field_mapping - self.fields = fields - self.bound_fields = [AdminBoundField(field, self.field_mapping, field_mapping['original']) - for field in self.fields] - self.index = index - -class TabularBoundRelatedObject(BoundRelatedObject): - def __init__(self, related_object, field_mapping, original): - super(TabularBoundRelatedObject, self).__init__(related_object, field_mapping, original) - self.field_wrapper_list = [FieldWrapper(field) for field in self.relation.editable_fields()] - - fields = self.relation.editable_fields() - - self.form_field_collection_wrappers = [FormFieldCollectionWrapper(field_mapping, fields, i) - for (i,field_mapping) in self.field_mappings.items() ] - self.original_row_needed = max([fw.use_raw_id_admin() for fw in self.field_wrapper_list]) - self.show_url = original and hasattr(self.relation.opts, 'get_absolute_url') - - def template_name(self): - return "admin/edit_inline_tabular.html" - -class StackedBoundRelatedObject(BoundRelatedObject): - def __init__(self, related_object, field_mapping, original): - super(StackedBoundRelatedObject, self).__init__(related_object, field_mapping, original) - fields = self.relation.editable_fields() - self.field_mappings.fill() - self.form_field_collection_wrappers = [FormFieldCollectionWrapper(field_mapping ,fields, i) - for (i,field_mapping) in self.field_mappings.items()] - self.show_url = original and hasattr(self.relation.opts, 'get_absolute_url') - - def template_name(self): - return "admin/edit_inline_stacked.html" - -class EditInlineNode(template.Node): - def __init__(self, rel_var): - self.rel_var = template.Variable(rel_var) - - def render(self, context): - relation = self.rel_var.resolve(context) - context.push() - if relation.field.rel.edit_inline == models.TABULAR: - bound_related_object_class = TabularBoundRelatedObject - elif relation.field.rel.edit_inline == models.STACKED: - bound_related_object_class = StackedBoundRelatedObject - else: - bound_related_object_class = relation.field.rel.edit_inline - original = context.get('original', None) - bound_related_object = relation.bind(context['form'], original, bound_related_object_class) - context['bound_related_object'] = bound_related_object - t = loader.get_template(bound_related_object.template_name()) - output = t.render(context) - context.pop() - return output - -def output_all(form_fields): - return u''.join([force_unicode(f) for f in form_fields]) -output_all = register.simple_tag(output_all) - -def auto_populated_field_script(auto_pop_fields, change = False): - t = [] - for field in auto_pop_fields: - if change: - t.append(u'document.getElementById("id_%s")._changed = true;' % field.name) - else: - t.append(u'document.getElementById("id_%s").onchange = function() { this._changed = true; };' % field.name) - - add_values = u' + " " + '.join([u'document.getElementById("id_%s").value' % g for g in field.prepopulate_from]) - for f in field.prepopulate_from: - t.append(u'document.getElementById("id_%s").onkeyup = function() {' \ - ' var e = document.getElementById("id_%s");' \ - ' if(!e._changed) { e.value = URLify(%s, %s);} }; ' % ( - f, field.name, add_values, field.max_length)) - return mark_safe(u''.join(t)) -auto_populated_field_script = register.simple_tag(auto_populated_field_script) - -def filter_interface_script_maybe(bound_field): - f = bound_field.field - if f.rel and isinstance(f.rel, models.ManyToManyRel) and f.rel.filter_interface: - return mark_safe(u'\n' % ( - f.name, escape(f.verbose_name.replace('"', '\\"')), f.rel.filter_interface-1, settings.ADMIN_MEDIA_PREFIX)) - else: - return '' -filter_interface_script_maybe = register.simple_tag(filter_interface_script_maybe) - -def field_widget(parser, token): - bits = token.contents.split() - if len(bits) != 2: - raise template.TemplateSyntaxError, "%s takes 1 argument" % bits[0] - return FieldWidgetNode(bits[1]) -field_widget = register.tag(field_widget) - -def edit_inline(parser, token): - bits = token.contents.split() - if len(bits) != 2: - raise template.TemplateSyntaxError, "%s takes 1 argument" % bits[0] - return EditInlineNode(bits[1]) -edit_inline = register.tag(edit_inline) - -def admin_field_line(context, argument_val): - if isinstance(argument_val, AdminBoundField): - bound_fields = [argument_val] - else: - bound_fields = [bf for bf in argument_val] - add = context['add'] - change = context['change'] - - class_names = ['form-row'] - for bound_field in bound_fields: - for f in bound_field.form_fields: - if f.errors(): - class_names.append('errors') - break - - # Assumes BooleanFields won't be stacked next to each other! - if isinstance(bound_fields[0].field, models.BooleanField): - class_names.append('checkbox-row') - - return { - 'add': context['add'], - 'change': context['change'], - 'bound_fields': bound_fields, - 'class_names': " ".join(class_names), - } -admin_field_line = register.inclusion_tag('admin/field_line.html', takes_context=True)(admin_field_line) diff --git a/django/contrib/admin/templatetags/adminapplist.py b/django/contrib/admin/templatetags/adminapplist.py deleted file mode 100644 index bf394e2099..0000000000 --- a/django/contrib/admin/templatetags/adminapplist.py +++ /dev/null @@ -1,81 +0,0 @@ -from django import template -from django.db.models import get_models -from django.utils.encoding import force_unicode -from django.utils.safestring import mark_safe - -register = template.Library() - -class AdminApplistNode(template.Node): - def __init__(self, varname): - self.varname = varname - - def render(self, context): - from django.db import models - from django.utils.text import capfirst - app_list = [] - user = context['user'] - - for app in models.get_apps(): - # Determine the app_label. - app_models = get_models(app) - if not app_models: - continue - app_label = app_models[0]._meta.app_label - - has_module_perms = user.has_module_perms(app_label) - - if has_module_perms: - model_list = [] - for m in app_models: - if m._meta.admin: - perms = { - 'add': user.has_perm("%s.%s" % (app_label, m._meta.get_add_permission())), - 'change': user.has_perm("%s.%s" % (app_label, m._meta.get_change_permission())), - 'delete': user.has_perm("%s.%s" % (app_label, m._meta.get_delete_permission())), - } - - # Check whether user has any perm for this module. - # If so, add the module to the model_list. - if True in perms.values(): - model_list.append({ - 'name': force_unicode(capfirst(m._meta.verbose_name_plural)), - 'admin_url': mark_safe(u'%s/%s/' % (force_unicode(app_label), m.__name__.lower())), - 'perms': perms, - }) - - if model_list: - # Sort using verbose decorate-sort-undecorate pattern - # instead of key argument to sort() for python 2.3 compatibility - decorated = [(x['name'], x) for x in model_list] - decorated.sort() - model_list = [x for key, x in decorated] - - app_list.append({ - 'name': app_label.title(), - 'has_module_perms': has_module_perms, - 'models': model_list, - }) - context[self.varname] = app_list - return '' - -def get_admin_app_list(parser, token): - """ - Returns a list of installed applications and models for which the current user - has at least one permission. - - Syntax:: - - {% get_admin_app_list as [context_var_containing_app_list] %} - - Example usage:: - - {% get_admin_app_list as admin_app_list %} - """ - tokens = token.contents.split() - if len(tokens) < 3: - raise template.TemplateSyntaxError, "'%s' tag requires two arguments" % tokens[0] - if tokens[1] != 'as': - raise template.TemplateSyntaxError, "First argument to '%s' tag must be 'as'" % tokens[0] - return AdminApplistNode(tokens[2]) - -register.tag('get_admin_app_list', get_admin_app_list) diff --git a/django/contrib/admin/urls.py b/django/contrib/admin/urls.py deleted file mode 100644 index 436e9b7b23..0000000000 --- a/django/contrib/admin/urls.py +++ /dev/null @@ -1,43 +0,0 @@ -from django.conf import settings -from django.conf.urls.defaults import * - -if settings.USE_I18N: - i18n_view = 'django.views.i18n.javascript_catalog' -else: - i18n_view = 'django.views.i18n.null_javascript_catalog' - -urlpatterns = patterns('', - ('^$', 'django.contrib.admin.views.main.index'), - ('^r/', include('django.conf.urls.shortcut')), - ('^jsi18n/$', i18n_view, {'packages': 'django.conf'}), - ('^logout/$', 'django.contrib.auth.views.logout'), - ('^password_change/$', 'django.contrib.auth.views.password_change'), - ('^password_change/done/$', 'django.contrib.auth.views.password_change_done'), - ('^template_validator/$', 'django.contrib.admin.views.template.template_validator'), - - # Documentation - ('^doc/$', 'django.contrib.admin.views.doc.doc_index'), - ('^doc/bookmarklets/$', 'django.contrib.admin.views.doc.bookmarklets'), - ('^doc/tags/$', 'django.contrib.admin.views.doc.template_tag_index'), - ('^doc/filters/$', 'django.contrib.admin.views.doc.template_filter_index'), - ('^doc/views/$', 'django.contrib.admin.views.doc.view_index'), - ('^doc/views/(?P[^/]+)/$', 'django.contrib.admin.views.doc.view_detail'), - ('^doc/models/$', 'django.contrib.admin.views.doc.model_index'), - ('^doc/models/(?P[^\.]+)\.(?P[^/]+)/$', 'django.contrib.admin.views.doc.model_detail'), -# ('^doc/templates/$', 'django.views.admin.doc.template_index'), - ('^doc/templates/(?P