Commits

Anonymous committed 5eaf16f

gis: Merged revisions 7921,7926-7928,7938-7941,7945-7947,7949-7950,7952,7955-7956,7961,7964-7968,7970-7978 via svnmerge from trunk.

This includes the newforms-admin branch, and thus is backwards-incompatible. The geographic admin is _not_ in this changeset, and is forthcoming.

Comments (0)

Files changed (219)

     Arvis Bickovskis <viestards.lists@gmail.com>
     Paul Bissex <http://e-scribe.com/>
     Simon Blanchard
+    David Blewett <david@dawninglight.net>
     Matt Boersma <ogghead@gmail.com>
     boobsd@gmail.com
     Andrew Brehaut <http://brehaut.net/blog>
     Espen Grindhaug <http://grindhaug.org/>
     Thomas Güttler <hv@tbz-pariv.de>
     dAniel hAhler
+    hambaloney
     Brian Harring <ferringb@gmail.com>
     Brant Harris
     Hawkeye
     Baurzhan Ismagulov <ibr@radix50.net>
     james_027@yahoo.com
     jcrasta@gmail.com
+    jdetaeye
     Zak Johnson <zakj@nox.cx>
     Nis Jørgensen <nis@superlativ.dk>
     Michael Josephson <http://www.sdjournal.com/>
     Waylan Limberg <waylan@gmail.com>
     limodou
     Philip Lindborg <philip.lindborg@gmail.com>
+    Simon Litchfield <simon@quo.com.au>
     Daniel Lindsley <polarcowz@gmail.com>
     Trey Long <trey@ktrl.com>
     msaelices <msaelices@gmail.com>
     Matt McClanahan <http://mmcc.cx/>
     Martin Maney <http://www.chipy.org/Martin_Maney>
+    Petr Marhoun <petr.marhoun@gmail.com>
     masonsimon+django@gmail.com
     Manuzhai
     Petr Marhoun <petr.marhoun@gmail.com>
     mattycakes@gmail.com
     Jason McBrayer <http://www.carcosa.net/jason/>
     mccutchen@gmail.com
+    Christian Metts
     michael.mcewan@gmail.com
     michal@plovarna.cz
     Slawek Mikula <slawek dot mikula at gmail dot com>
     Eric Moritz <http://eric.themoritzfamily.com/>
     mrmachine <real.human@mrmachine.net>
     Robin Munn <http://www.geekforgod.com/>
+    msundstr
     Robert Myers <myer0052@gmail.com>
     Nebojša Dorđević
     Doug Napoleone <doug@dougma.com>
     peter@mymart.com
     pgross@thoughtworks.com
     phaedo <http://phaedo.cx/>
+    Julien Phalip <http://www.julienphalip.com>
     phil@produxion.net
     phil.h.smith@gmail.com
     Gustavo Picon
     Mihai Preda <mihai_preda@yahoo.com>
     Daniel Poelzleithner <http://poelzi.org/>
     polpak@yahoo.com
+    Matthias Pronk <django@masida.nl>
     Jyrki Pulliainen <jyrki.pulliainen@gmail.com>
     Johann Queuniet <johann.queuniet@adh.naellia.eu>
     Jan Rademaker
     Matt Riggott
     Henrique Romano <onaiort@gmail.com>
     Armin Ronacher
+    Daniel Roseman <http://roseman.org.uk/>    
     Brian Rosner <brosner@gmail.com>
     Oliver Rutherfurd <http://rutherfurd.net/>
     ryankanno

django/conf/project_template/urls.py

 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),
 )

django/contrib/admin/__init__.py

+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

django/contrib/admin/filterspecs.py

 
 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
 
         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):
         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:
 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)
 
 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
 
 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)
 # 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

django/contrib/admin/media/css/forms.css

 .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;}

django/contrib/admin/media/js/SelectFilter.js

-/*
-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;
-    }
-}

django/contrib/admin/media/js/admin/CollapsedFieldsets.js

         // Returns true if any fields in the fieldset have validation errors.
         var divs = fs.getElementsByTagName('div');
         for (var i=0; i<divs.length; i++) {
-            if (divs[i].className.match(/\berror\b/)) {
+            if (divs[i].className.match(/\berrors\b/)) {
                 return true;
             }
         }

django/contrib/admin/media/js/admin/RelatedObjectLookups.js

-// Handles related-objects functionality: lookup link for raw_id_admin=True
+// Handles related-objects functionality: lookup link for raw_id_fields
 // and Add Another links.
 
 function html_unescape(text) {
 function dismissRelatedLookupPopup(win, chosenId) {
     var name = win.name.replace(/___/g, '.');
     var elem = document.getElementById(name);
-    if (elem.className.indexOf('vRawIdAdminField') != -1 && elem.value) {
+    if (elem.className.indexOf('vManyToManyRawIdAdminField') != -1 && elem.value) {
         elem.value += ',' + chosenId;
     } else {
         document.getElementById(name).value = chosenId;

django/contrib/admin/models.py

 from django.db import models
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.auth.models import User
+from django.contrib.admin.util import quote
 from django.utils.translation import ugettext_lazy as _
 from django.utils.encoding import smart_unicode
 from django.utils.safestring import mark_safe
         Returns the admin URL to edit the object represented by this log entry.
         This is relative to the Django admin index page.
         """
-        return mark_safe(u"%s/%s/%s/" % (self.content_type.app_label, self.content_type.model, self.object_id))
+        return mark_safe(u"%s/%s/%s/" % (self.content_type.app_label, self.content_type.model, quote(self.object_id)))

django/contrib/admin/options.py

+from django import oldforms, template
+from django import forms
+from django.forms.formsets import all_valid
+from django.forms.models import modelform_factory, inlineformset_factory
+from django.forms.models import BaseInlineFormset
+from django.contrib.contenttypes.models import ContentType
+from django.contrib.admin import widgets
+from django.contrib.admin.util import quote, unquote, get_deleted_objects
+from django.core.exceptions import ImproperlyConfigured, PermissionDenied
+from django.db import models, transaction
+from django.http import Http404, HttpResponse, HttpResponseRedirect
+from django.shortcuts import get_object_or_404, render_to_response
+from django.utils.html import escape
+from django.utils.safestring import mark_safe
+from django.utils.text import capfirst, get_text_list
+from django.utils.translation import ugettext as _
+from django.utils.encoding import force_unicode
+import sets
+
+HORIZONTAL, VERTICAL = 1, 2
+# returns the <ul> 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('<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script>' % \
+                # 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: <a href="../../%s/">%s</a>' % (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.forms.formsets import DELETION_FIELD_NAME
+        return AdminField(self.form, DELETION_FIELD_NAME, False)
+
+    def ordering_field(self):
+        from django.forms.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())

django/contrib/admin/sites.py

+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()

django/contrib/admin/templates/admin/auth/user/add_form.html

 <fieldset class="module aligned">
 
 <div class="form-row">
-  {{ form.username.html_error_list }}
+  {{ form.username.errors }}
+  {# TODO: get required class on label_tag #}
   <label for="id_username" class="required">{% trans 'Username' %}:</label> {{ form.username }}
-  <p class="help">{{ username_help_text }}</p>
+  <p class="help">{{ form.username.help_text }}</p>
 </div>
 
 <div class="form-row">
-  {{ form.password1.html_error_list }}
+  {{ form.password1.errors }}
+  {# TODO: get required class on label_tag #}
   <label for="id_password1" class="required">{% trans 'Password' %}:</label> {{ form.password1 }}
 </div>
 
 <div class="form-row">
-  {{ form.password2.html_error_list }}
+  {{ form.password2.errors }}
+  {# TODO: get required class on label_tag #}
   <label for="id_password2" class="required">{% trans 'Password (again)' %}:</label> {{ form.password2 }}
   <p class="help">{% trans 'Enter the same password as above, for verification.' %}</p>
 </div>
 
+<script type="text/javascript">document.getElementById("id_username").focus();</script>
+
 </fieldset>
 {% endblock %}

django/contrib/admin/templates/admin/auth/user/change_password.html

 {% load i18n admin_modify adminmedia %}
 {% block extrahead %}{{ block.super }}
 <script type="text/javascript" src="../../../../jsi18n/"></script>
-{% 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 %}
 <form action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form">{% block form_top %}{% endblock %}
 <div>
 {% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %}
-{% if form.error_dict %}
+{% if form.errors %}
     <p class="errornote">
-    {% 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 %}
     </p>
 {% endif %}
 
 <fieldset class="module aligned">
 
 <div class="form-row">
-  {{ form.password1.html_error_list }}
+  {{ form.password1.errors }}
+  {# TODO: get required class on label_tag #}
   <label for="id_password1" class="required">{% trans 'Password' %}:</label> {{ form.password1 }}
 </div>
 
 <div class="form-row">
-  {{ form.password2.html_error_list }}
+  {{ form.password2.errors }}
+  {# TODO: get required class on label_tag #}
   <label for="id_password2" class="required">{% trans 'Password (again)' %}:</label> {{ form.password2 }}
   <p class="help">{% trans 'Enter the same password as above, for verification.' %}</p>
 </div>
 <input type="submit" value="{% trans 'Change password' %}" class="default" />
 </div>
 
-<script type="text/javascript">document.getElementById("{{ first_form_field_id }}").focus();</script>
+<script type="text/javascript">document.getElementById("id_password1").focus();</script>
 </div>
 </form></div>
 {% endblock %}

django/contrib/admin/templates/admin/base.html

         {% block branding %}{% endblock %}
         </div>
         {% if user.is_authenticated and user.is_staff %}
-        <div id="user-tools">
-        {% trans 'Welcome,' %} <strong>{% if user.first_name %}{{ user.first_name|escape }}{% else %}{{ user.username }}{% endif %}</strong>.
-        {% block userlinks %}
-        <a href="{% url django.contrib.admin.views.doc.doc_index %}">{% trans 'Documentation' %}</a>
-        / <a href="{% url django.contrib.auth.views.password_change %}">{% trans 'Change password' %}</a>
-        / <a href="{% url django.contrib.auth.views.logout %}">{% trans 'Log out' %}</a>
-        {% endblock %}
-        </div>
+        <div id="user-tools">{% trans 'Welcome,' %} <strong>{% if user.first_name %}{{ user.first_name|escape }}{% else %}{{ user.username }}{% endif %}</strong>. {% block userlinks %}<a href="{{ root_path }}doc/">{% trans 'Documentation' %}</a> / <a href="{{ root_path }}password_change/">{% trans 'Change password' %}</a> / <a href="{{ root_path }}logout/">{% trans 'Log out' %}</a>{% endblock %}</div>
         {% endif %}
         {% block nav-global %}{% endblock %}
     </div>

django/contrib/admin/templates/admin/change_form.html

 {% extends "admin/base_site.html" %}
 {% load i18n admin_modify adminmedia %}
+
 {% block extrahead %}{{ block.super }}
 <script type="text/javascript" src="../../../jsi18n/"></script>
-{% 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 %}
 <div class="breadcrumbs">
      <a href="../../../">{% trans "Home" %}</a> &rsaquo;
      {% if add %}{% trans "Add" %} {{ opts.verbose_name }}{% else %}{{ original|truncatewords:"18" }}{% endif %}
 </div>
 {% endif %}{% endblock %}
+
 {% block content %}<div id="content-main">
 {% block object-tools %}
 {% if change %}{% if not is_popup %}
 <form {% if has_file_field %}enctype="multipart/form-data" {% endif %}action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form">{% block form_top %}{% endblock %}
 <div>
 {% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %}
-{% if opts.admin.save_on_top %}{% submit_row %}{% endif %}
-{% if form.error_dict %}
+{% if save_on_top %}{% submit_row %}{% endif %}
+{% if errors %}
     <p class="errornote">
-    {% 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 %}
     </p>
+    <ul class="errorlist">{% for error in adminform.form.non_field_errors %}<li>{{ error }}</li>{% endfor %}</ul>
 {% endif %}
-{% for bound_field_set in bound_field_sets %}
-   <fieldset class="module aligned {{ bound_field_set.classes }}">
-    {% if bound_field_set.name %}<h2>{{ bound_field_set.name }}</h2>{% endif %}
-    {% if bound_field_set.description %}<div class="description">{{ bound_field_set.description|safe }}</div>{% 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 %}
+
+{% for fieldset in adminform %}
+  {% include "admin/includes/fieldset.html" %}
+{% endfor %}
+
+{% block after_field_sets %}{% endblock %}
+
+{% for inline_admin_formset in inline_admin_formsets %}
+    {% include inline_admin_formset.opts.template %}
+{% endfor %}
+
+{% block after_related_objects %}{% endblock %}
+
+{% submit_row %}
+
+{% if add %}
+   <script type="text/javascript">document.getElementById("{{ adminform.first_field.auto_id }}").focus();</script>
+{% endif %}
+
+{# JavaScript for prepopulated fields #}
+
+{% if add %}
+<script type="text/javascript">
+{% for field in adminform.prepopulated_fields %}
+    document.getElementById("{{ field.field.auto_id }}").onchange = function() { this._changed = true; };
+    {% for dependency in field.dependencies %}
+    document.getElementById("{{ dependency.auto_id }}").onkeyup = function() {
+        var e = document.getElementById("{{ field.field.auto_id }}");
+        if (!e._changed) { e.value = URLify({% for innerdep in field.dependencies %}document.getElementById("{{ innerdep.auto_id }}").value{% if not forloop.last %} + ' ' + {% endif %}{% endfor %}, {{ field.field.field.max_length }}); }
+    }
     {% endfor %}
-   </fieldset>
 {% endfor %}
-{% block after_field_sets %}{% endblock %}
-{% if change %}
-   {% if ordered_objects %}
-   <fieldset class="module"><h2>{% trans "Ordering" %}</h2>
-   <div class="form-row{% if form.order_.errors %} error{% endif %} ">
-   {% if form.order_.errors %}{{ form.order_.html_error_list }}{% endif %}
-   <p><label for="id_order_">{% trans "Order:" %}</label> {{ form.order_ }}</p>
-   </div></fieldset>
-   {% endif %}
+</script>
 {% endif %}
-{% for related_object in inline_related_objects %}{% edit_inline related_object %}{% endfor %}
-{% block after_related_objects %}{% endblock %}
-{% submit_row %}
-{% if add %}
-   <script type="text/javascript">document.getElementById("{{ first_form_field_id }}").focus();</script>
-{% endif %}
-{% if auto_populated_fields %}
-   <script type="text/javascript">
-   {% auto_populated_field_script auto_populated_fields change %}
-   </script>
-{% endif %}
+
 </div>
 </form></div>
 {% endblock %}

django/contrib/admin/templates/admin/change_list.html

 {% extends "admin/base_site.html" %}
 {% load adminmedia admin_list i18n %}
+
 {% block stylesheet %}{% admin_media_prefix %}css/changelists.css{% endblock %}
+