text: from django import forms, template
  from django.forms.formsets import all_valid
  from django.forms.models import modelform_factory, modelformset_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 import helpers
  from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects, model_ngettext, model_format_dict
  from django.contrib import messages
  from django.views.decorators.csrf import csrf_protect
  from django.core.exceptions import PermissionDenied, ValidationError
  from django.db import models, transaction
  from django.db.models.related import RelatedObject
  from django.db.models.fields import BLANK_CHOICE_DASH, FieldDoesNotExist
  from django.db.models.sql.constants import LOOKUP_SEP, QUERY_TERMS
  from django.http import Http404, HttpResponse, HttpResponseRedirect
  from django.shortcuts import get_object_or_404, render_to_response
  from django.utils.decorators import method_decorator
  from django.utils.datastructures import SortedDict
  from django.utils.functional import update_wrapper
  from django.utils.html import escape, escapejs
  from django.utils.safestring import mark_safe
  from django.utils.functional import curry
  from django.utils.text import capfirst, get_text_list
  from django.utils.translation import ugettext as _
  from django.utils.translation import ungettext, ugettext_lazy
  from django.utils.encoding import force_unicode
  
  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
  
  # Defaults for formfield_overrides. ModelAdmin subclasses can change this
  # by adding to ModelAdmin.formfield_overrides.
  
  FORMFIELD_FOR_DBFIELD_DEFAULTS = {
      models.DateTimeField: {
          'form_class': forms.SplitDateTimeField,
          'widget': widgets.AdminSplitDateTime
      },
      models.DateField:       {'widget': widgets.AdminDateWidget},
      models.TimeField:       {'widget': widgets.AdminTimeWidget},
      models.TextField:       {'widget': widgets.AdminTextareaWidget},
      models.URLField:        {'widget': widgets.AdminURLFieldWidget},
      models.IntegerField:    {'widget': widgets.AdminIntegerFieldWidget},
      models.BigIntegerField: {'widget': widgets.AdminIntegerFieldWidget},
      models.CharField:       {'widget': widgets.AdminTextInputWidget},
      models.ImageField:      {'widget': widgets.AdminFileWidget},
      models.FileField:       {'widget': widgets.AdminFileWidget},
  }
  
  csrf_protect_m = method_decorator(csrf_protect)
  
  class BaseModelAdmin(object):
      """Functionality common to both ModelAdmin and InlineAdmin."""
      __metaclass__ = forms.MediaDefiningClass
  
      raw_id_fields = ()
      fields = None
      exclude = None
      fieldsets = None
      form = forms.ModelForm
      filter_vertical = ()
      filter_horizontal = ()
      radio_fields = {}
      prepopulated_fields = {}
      formfield_overrides = {}
      readonly_fields = ()
  
      def __init__(self):
          overrides = FORMFIELD_FOR_DBFIELD_DEFAULTS.copy()
          overrides.update(self.formfield_overrides)
          self.formfield_overrides = overrides
  
      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.
          """
          request = kwargs.pop("request", None)
  
          # If the field specifies choices, we don't need to look for special
          # admin widgets - we just need to use a select widget of some kind.
          if db_field.choices:
              return self.formfield_for_choice_field(db_field, request, **kwargs)
  
          # ForeignKey or ManyToManyFields
          if isinstance(db_field, (models.ForeignKey, models.ManyToManyField)):
              # Combine the field kwargs with any options for formfield_overrides.
              # Make sure the passed in **kwargs override anything in
              # formfield_overrides because **kwargs is more specific, and should
              # always win.
              if db_field.__class__ in self.formfield_overrides:
                  kwargs = dict(self.formfield_overrides[db_field.__class__], **kwargs)
  
              # Get the correct formfield.
              if isinstance(db_field, models.ForeignKey):
                  formfield = self.formfield_for_foreignkey(db_field, request, **kwargs)
              elif isinstance(db_field, models.ManyToManyField):
                  formfield = self.formfield_for_manytomany(db_field, request, **kwargs)
  
              # For non-raw_id fields, wrap the widget with a wrapper that adds
              # extra HTML -- the "add other" interface -- to the end of the
              # rendered output. formfield can be None if it came from a
              # OneToOneField with parent_link=True or a M2M intermediary.
              if formfield and db_field.name not in self.raw_id_fields:
                  formfield.widget = widgets.RelatedFieldWidgetWrapper(formfield.widget, db_field.rel, self.admin_site)
  
              return formfield
  
          # If we've got overrides for the formfield defined, use 'em. **kwargs
          # passed to formfield_for_dbfield override the defaults.
          for klass in db_field.__class__.mro():
              if klass in self.formfield_overrides:
                  kwargs = dict(self.formfield_overrides[klass], **kwargs)
                  return db_field.formfield(**kwargs)
  
          # For any other type of field, just call its formfield() method.
          return db_field.formfield(**kwargs)
  
      def formfield_for_choice_field(self, db_field, request=None, **kwargs):
          """
          Get a form Field for a database Field that has declared choices.
          """
          # If the field is named as a radio_field, use a RadioSelect
          if db_field.name in self.radio_fields:
              # Avoid stomping on custom widget/choices arguments.
              if 'widget' not in kwargs:
                  kwargs['widget'] = widgets.AdminRadioSelect(attrs={
                      'class': get_ul_class(self.radio_fields[db_field.name]),
                  })
              if 'choices' not in kwargs:
                  kwargs['choices'] = db_field.get_choices(
                      include_blank = db_field.blank,
                      blank_choice=[('', _('None'))]
                  )
          return db_field.formfield(**kwargs)
  
      def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
          """
          Get a form Field for a ForeignKey.
          """
          db = kwargs.get('using')
          if db_field.name in self.raw_id_fields:
              kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel, using=db)
          elif 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
  
          return db_field.formfield(**kwargs)
  
      def formfield_for_manytomany(self, db_field, request=None, **kwargs):
          """
          Get a form Field for a ManyToManyField.
          """
          # If it uses an intermediary model that isn't auto created, don't show
          # a field in admin.
          if not db_field.rel.through._meta.auto_created:
              return None
          db = kwargs.get('using')
  
          if db_field.name in self.raw_id_fields:
              kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel, using=db)
              kwargs['help_text'] = ''
          elif db_field.name in (list(self.filter_vertical) + list(self.filter_horizontal)):
              kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, (db_field.name in self.filter_vertical))
  
          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)
  
      def get_readonly_fields(self, request, obj=None):
          return self.readonly_fields
  
      def lookup_allowed(self, lookup):
          parts = lookup.split(LOOKUP_SEP)
  
          # Last term in lookup is a query term (__exact, __startswith etc)
          # This term can be ignored.
          if len(parts) > 1 and parts[-1] in QUERY_TERMS:
              parts.pop()
  
          # Special case -- foo__id__exact and foo__id queries are implied
          # if foo has been specificially included in the lookup list; so
          # drop __id if it is the last part. However, first we need to find
          # the pk attribute name.
          model = self.model
          pk_attr_name = None
          for part in parts[:-1]:
              field, _, _, _ = model._meta.get_field_by_name(part)
              if hasattr(field, 'rel'):
                  model = field.rel.to
                  pk_attr_name = model._meta.pk.name
              elif isinstance(field, RelatedObject):
                  model = field.model
                  pk_attr_name = model._meta.pk.name
              else:
                  pk_attr_name = None
          if pk_attr_name and len(parts) > 1 and parts[-1] == pk_attr_name:
              parts.pop()
  
          try:
              self.model._meta.get_field_by_name(parts[0])
          except FieldDoesNotExist:
              # Lookups on non-existants fields are ok, since they're ignored
              # later.
              return True
          else:
              if len(parts) == 1:
                  return True
              clean_lookup = LOOKUP_SEP.join(parts)
              return clean_lookup in self.list_filter or clean_lookup == self.date_hierarchy
  
  class ModelAdmin(BaseModelAdmin):
      "Encapsulates all admin options and functionality for a given model."
  
      list_display = ('__str__',)
      list_display_links = ()
      list_filter = ()
      list_select_related = False
      list_per_page = 100
      list_editable = ()
      search_fields = ()
      date_hierarchy = None
      save_as = False
      save_on_top = False
      ordering = None
      inlines = []
  
      # Custom templates (designed to be over-ridden in subclasses)
      add_form_template = None
      change_form_template = None
      change_list_template = None
      delete_confirmation_template = None
      delete_selected_confirmation_template = None
      object_history_template = None
  
      # Actions
      actions = []
      action_form = helpers.ActionForm
      actions_on_top = True
      actions_on_bottom = False
      actions_selection_counter = True
  
      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)
          if 'action_checkbox' not in self.list_display and self.actions is not None:
              self.list_display = ['action_checkbox'] +  list(self.list_display)
          if not self.list_display_links:
              for name in self.list_display:
                  if name != 'action_checkbox':
                      self.list_display_links = [name]
                      break
          super(ModelAdmin, self).__init__()
  
      def get_urls(self):
          from django.conf.urls.defaults import patterns, url
  
          def wrap(view):
              def wrapper(*args, **kwargs):
                  return self.admin_site.admin_view(view)(*args, **kwargs)
              return update_wrapper(wrapper, view)
  
          info = self.model._meta.app_label, self.model._meta.module_name
  
          urlpatterns = patterns('',
              url(r'^$',
                  wrap(self.changelist_view),
                  name='%s_%s_changelist' % info),
              url(r'^add/$',
                  wrap(self.add_view),
                  name='%s_%s_add' % info),
              url(r'^(.+)/history/$',
                  wrap(self.history_view),
                  name='%s_%s_history' % info),
              url(r'^(.+)/delete/$',
                  wrap(self.delete_view),
                  name='%s_%s_delete' % info),
              url(r'^(.+)/$',
                  wrap(self.change_view),
                  name='%s_%s_change' % info),
          )
          return urlpatterns
  
      def urls(self):
          return self.get_urls()
      urls = property(urls)
  
      def _media(self):
          from django.conf import settings
  
          js = ['js/core.js', 'js/admin/RelatedObjectLookups.js',
                'js/jquery.min.js', 'js/jquery.init.js']
          if self.actions is not None:
              js.extend(['js/actions.min.js'])
          if self.prepopulated_fields:
              js.append('js/urlify.js')
              js.append('js/prepopulate.min.js')
          if self.opts.get_ordered_objects():
              js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.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.
          Can be overriden by the user in subclasses.
          """
          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, the default implementation doesn't examine the
          `obj` parameter.
  
          Can be overriden by the user in subclasses. In such case it should
          return True if the given request has permission to change the `obj`
          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, the default implementation doesn't examine the
          `obj` parameter.
  
          Can be overriden by the user in subclasses. In such case it should
          return True if the given request has permission to delete the `obj`
          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 get_model_perms(self, request):
          """
          Returns a dict of all perms for this model. This dict has the keys
          ``add``, ``change``, and ``delete`` mapping to the True/False for each
          of those actions.
          """
          return {
              'add': self.has_add_permission(request),
              'change': self.has_change_permission(request),
              'delete': self.has_delete_permission(request),
          }
  
      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, obj)
          fields = form.base_fields.keys() + list(self.get_readonly_fields(request, obj))
          return [(None, {'fields': fields})]
  
      def get_form(self, request, obj=None, **kwargs):
          """
          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
          if self.exclude is None:
              exclude = []
          else:
              exclude = list(self.exclude)
          exclude.extend(kwargs.get("exclude", []))
          exclude.extend(self.get_readonly_fields(request, obj))
          # if exclude is an empty list we pass None to be consistant with the
          # default on modelform_factory
          exclude = exclude or None
          defaults = {
              "form": self.form,
              "fields": fields,
              "exclude": exclude,
              "formfield_callback": curry(self.formfield_for_dbfield, request=request),
          }
          defaults.update(kwargs)
          return modelform_factory(self.model, **defaults)
  
      def get_changelist(self, request, **kwargs):
          """
          Returns the ChangeList class for use on the changelist page.
          """
          from django.contrib.admin.views.main import ChangeList
          return ChangeList
  
      def get_object(self, request, object_id):
          """
          Returns an instance matching the primary key provided. ``None``  is
          returned if no match is found (or the object_id failed validation
          against the primary key field).
          """
          queryset = self.queryset(request)
          model = queryset.model
          try:
              object_id = model._meta.pk.to_python(object_id)
              return queryset.get(pk=object_id)
          except (model.DoesNotExist, ValidationError):
              return None
  
      def get_changelist_form(self, request, **kwargs):
          """
          Returns a Form class for use in the Formset on the changelist page.
          """
          defaults = {
              "formfield_callback": curry(self.formfield_for_dbfield, request=request),
          }
          defaults.update(kwargs)
          return modelform_factory(self.model, **defaults)
  
      def get_changelist_formset(self, request, **kwargs):
          """
          Returns a FormSet class for use on the changelist page if list_editable
          is used.
          """
          defaults = {
              "formfield_callback": curry(self.formfield_for_dbfield, request=request),
          }
          defaults.update(kwargs)
          return modelformset_factory(self.model,
              self.get_changelist_form(request), extra=0,
              fields=self.list_editable, **defaults)
  
      def get_formsets(self, request, obj=None):
          for inline in self.inline_instances:
              yield inline.get_formset(request, obj)
  
      def log_addition(self, request, object):
          """
          Log that an object has been successfully added.
  
          The default implementation creates an admin LogEntry object.
          """
          from django.contrib.admin.models import LogEntry, ADDITION
          LogEntry.objects.log_action(
              user_id         = request.user.pk,
              content_type_id = ContentType.objects.get_for_model(object).pk,
              object_id       = object.pk,
              object_repr     = force_unicode(object),
              action_flag     = ADDITION
          )
  
      def log_change(self, request, object, message):
          """
          Log that an object has been successfully changed.
  
          The default implementation creates an admin LogEntry object.
          """
          from django.contrib.admin.models import LogEntry, CHANGE
          LogEntry.objects.log_action(
              user_id         = request.user.pk,
              content_type_id = ContentType.objects.get_for_model(object).pk,
              object_id       = object.pk,
              object_repr     = force_unicode(object),
              action_flag     = CHANGE,
              change_message  = message
          )
  
      def log_deletion(self, request, object, object_repr):
          """
          Log that an object has been successfully deleted. Note that since the
          object is deleted, it might no longer be safe to call *any* methods
          on the object, hence this method getting object_repr.
  
          The default implementation creates an admin LogEntry object.
          """
          from django.contrib.admin.models import LogEntry, DELETION
          LogEntry.objects.log_action(
              user_id         = request.user.id,
              content_type_id = ContentType.objects.get_for_model(self.model).pk,
              object_id       = object.pk,
              object_repr     = object_repr,
              action_flag     = DELETION
          )
  
      def action_checkbox(self, obj):
          """
          A list_display column containing a checkbox widget.
          """
          return helpers.checkbox.render(helpers.ACTION_CHECKBOX_NAME, force_unicode(obj.pk))
      action_checkbox.short_description = mark_safe('<input type="checkbox" id="action-toggle" />')
      action_checkbox.allow_tags = True
  
      def get_actions(self, request):
          """
          Return a dictionary mapping the names of all actions for this
          ModelAdmin to a tuple of (callable, name, description) for each action.
          """
          # If self.actions is explicitally set to None that means that we don't
          # want *any* actions enabled on this page.
          if self.actions is None:
              return []
  
          actions = []
  
          # Gather actions from the admin site first
          for (name, func) in self.admin_site.actions:
              description = getattr(func, 'short_description', name.replace('_', ' '))
              actions.append((func, name, description))
  
          # Then gather them from the model admin and all parent classes,
          # starting with self and working back up.
          for klass in self.__class__.mro()[::-1]:
              class_actions = getattr(klass, 'actions', [])
              # Avoid trying to iterate over None
              if not class_actions:
                  continue
              actions.extend([self.get_action(action) for action in class_actions])
  
          # get_action might have returned None, so filter any of those out.
          actions = filter(None, actions)
  
          # Convert the actions into a SortedDict keyed by name
          # and sorted by description.
          actions.sort(lambda a,b: cmp(a[2].lower(), b[2].lower()))
          actions = SortedDict([
              (name, (func, name, desc))
              for func, name, desc in actions
          ])
  
          return actions
  
      def get_action_choices(self, request, default_choices=BLANK_CHOICE_DASH):
          """
          Return a list of choices for use in a form object.  Each choice is a
          tuple (name, description).
          """
          choices = [] + default_choices
          for func, name, description in self.get_actions(request).itervalues():
              choice = (name, description % model_format_dict(self.opts))
              choices.append(choice)
          return choices
  
      def get_action(self, action):
          """
          Return a given action from a parameter, which can either be a callable,
          or the name of a method on the ModelAdmin.  Return is a tuple of
          (callable, name, description).
          """
          # If the action is a callable, just use it.
          if callable(action):
              func = action
              action = action.__name__
  
          # Next, look for a method. Grab it off self.__class__ to get an unbound
          # method instead of a bound one; this ensures that the calling
          # conventions are the same for functions and methods.
          elif hasattr(self.__class__, action):
              func = getattr(self.__class__, action)
  
          # Finally, look for a named method on the admin site
          else:
              try:
                  func = self.admin_site.get_action(action)
              except KeyError:
                  return None
  
          if hasattr(func, 'short_description'):
              description = func.short_description
          else:
              description = capfirst(action.replace('_', ' '))
          return func, action, description
  
      def construct_change_message(self, request, form, formsets):
          """
          Construct a change message from a changed object.
          """
          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': force_unicode(added_object._meta.verbose_name),
                                               'object': force_unicode(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': force_unicode(changed_object._meta.verbose_name),
                                               'object': force_unicode(changed_object)})
                  for deleted_object in formset.deleted_objects:
                      change_message.append(_('Deleted %(name)s "%(object)s".')
                                            % {'name': force_unicode(deleted_object._meta.verbose_name),
                                               'object': force_unicode(deleted_object)})
          change_message = ' '.join(change_message)
          return change_message or _('No fields changed.')
  
      def message_user(self, request, message):
          """
          Send a message to the user. The default implementation
          posts a message using the django.contrib.messages backend.
          """
          messages.info(request, message)
  
      def save_form(self, request, form, change):
          """
          Given a ModelForm return an unsaved instance. ``change`` is True if
          the object is being changed, and False if it's being added.
          """
          return form.save(commit=False)
  
      def save_model(self, request, obj, form, change):
          """
          Given a model instance save it to the database.
          """
          obj.save()
  
      def save_formset(self, request, form, formset, change):
          """
          Given an inline formset save it to the database.
          """
          formset.save()
  
      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,
          })
          if add and self.add_form_template is not None:
              form_template = self.add_form_template
          else:
              form_template = self.change_form_template
          context_instance = template.RequestContext(request, current_app=self.admin_site.name)
          return render_to_response(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=context_instance)
  
      def response_add(self, request, obj, post_url_continue='../%s/'):
          """
          Determines the HttpResponse for the add_view stage.
          """
          opts = obj._meta
          pk_value = obj._get_pk_val()
  
          msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj)}
          # Here, we distinguish between different save types by checking for
          # the presence of keys in request.POST.
          if request.POST.has_key("_continue"):
              self.message_user(request, 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), escapejs(obj)))
          elif request.POST.has_key("_addanother"):
              self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name)))
              return HttpResponseRedirect(request.path)
          else:
              self.message_user(request, 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)
  
      def response_change(self, request, obj):
          """
          Determines the HttpResponse for the change_view stage.
          """
          opts = obj._meta
          pk_value = obj._get_pk_val()
  
          msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj)}
          if request.POST.has_key("_continue"):
              self.message_user(request, 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"):
              msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': force_unicode(opts.verbose_name), 'obj': obj}
              self.message_user(request, msg)
              return HttpResponseRedirect("../%s/" % pk_value)
          elif request.POST.has_key("_addanother"):
              self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name)))
              return HttpResponseRedirect("../add/")
          else:
              self.message_user(request, msg)
              return HttpResponseRedirect("../")
  
      def response_action(self, request, queryset):
          """
          Handle an admin action. This is called if a request is POSTed to the
          changelist; it returns an HttpResponse if the action was handled, and
          None otherwise.
          """
  
          # There can be multiple action forms on the page (at the top
          # and bottom of the change list, for example). Get the action
          # whose button was pushed.
          try:
              action_index = int(request.POST.get('index', 0))
          except ValueError:
              action_index = 0
  
          # Construct the action form.
          data = request.POST.copy()
          data.pop(helpers.ACTION_CHECKBOX_NAME, None)
          data.pop("index", None)
  
          # Use the action whose button was pushed
          try:
              data.update({'action': data.getlist('action')[action_index]})
          except IndexError:
              # If we didn't get an action from the chosen form that's invalid
              # POST data, so by deleting action it'll fail the validation check
              # below. So no need to do anything here
              pass
  
          action_form = self.action_form(data, auto_id=None)
          action_form.fields['action'].choices = self.get_action_choices(request)
  
          # If the form's valid we can handle the action.
          if action_form.is_valid():
              action = action_form.cleaned_data['action']
              select_across = action_form.cleaned_data['select_across']
              func, name, description = self.get_actions(request)[action]
  
              # Get the list of selected PKs. If nothing's selected, we can't
              # perform an action on it, so bail. Except we want to perform
              # the action explicitly on all objects.
              selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME)
              if not selected and not select_across:
                  # Reminder that something needs to be selected or nothing will happen
                  msg = _("Items must be selected in order to perform "
                          "actions on them. No items have been changed.")
                  self.message_user(request, msg)
                  return None
  
              if not select_across:
                  # Perform the action only on the selected objects
                  queryset = queryset.filter(pk__in=selected)
  
              response = func(self, request, queryset)
  
              # Actions may return an HttpResponse, which will be used as the
              # response from the POST. If not, we'll be a good little HTTP
              # citizen and redirect back to the changelist page.
              if isinstance(response, HttpResponse):
                  return response
              else:
                  return HttpResponseRedirect(request.get_full_path())
          else:
              msg = _("No action selected.")
              self.message_user(request, msg)
              return None
  
      @csrf_protect_m
      @transaction.commit_on_success
      def add_view(self, request, form_url='', extra_context=None):
          "The 'add' admin view for this model."
          model = self.model
          opts = model._meta
  
          if not self.has_add_permission(request):
              raise PermissionDenied
  
          ModelForm = self.get_form(request)
          formsets = []
          if request.method == 'POST':
              form = ModelForm(request.POST, request.FILES)
              if form.is_valid():
                  new_object = self.save_form(request, form, change=False)
                  form_validated = True
              else:
                  form_validated = False
                  new_object = self.model()
              prefixes = {}
              for FormSet, inline in zip(self.get_formsets(request), self.inline_instances):
                  prefix = FormSet.get_default_prefix()
                  prefixes[prefix] = prefixes.get(prefix, 0) + 1
                  if prefixes[prefix] != 1:
                      prefix = "%s-%s" % (prefix, prefixes[prefix])
                  formset = FormSet(data=request.POST, files=request.FILES,
                                    instance=new_object,
                                    save_as_new=request.POST.has_key("_saveasnew"),
                                    prefix=prefix, queryset=inline.queryset(request))
                  formsets.append(formset)
              if all_valid(formsets) and form_validated:
                  self.save_model(request, new_object, form, change=False)
                  form.save_m2m()
                  for formset in formsets:
                      self.save_formset(request, form, formset, change=False)
  
                  self.log_addition(request, new_object)
                  return self.response_add(request, new_object)
          else:
              # Prepare the dict of initial data from the request.
              # We have to special-case M2Ms as a list of comma-separated PKs.
              initial = dict(request.GET.items())
              for k in initial:
                  try:
                      f = opts.get_field(k)
                  except models.FieldDoesNotExist:
                      continue
                  if isinstance(f, models.ManyToManyField):
                      initial[k] = initial[k].split(",")
              form = ModelForm(initial=initial)
              prefixes = {}
              for FormSet, inline in zip(self.get_formsets(request),
                                         self.inline_instances):
                  prefix = FormSet.get_default_prefix()
                  prefixes[prefix] = prefixes.get(prefix, 0) + 1
                  if prefixes[prefix] != 1:
                      prefix = "%s-%s" % (prefix, prefixes[prefix])
                  formset = FormSet(instance=self.model(), prefix=prefix,
                                    queryset=inline.queryset(request))
                  formsets.append(formset)
  
          adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)),
              self.prepopulated_fields, self.get_readonly_fields(request),
              model_admin=self)
          media = self.media + adminForm.media
  
          inline_admin_formsets = []
          for inline, formset in zip(self.inline_instances, formsets):
              fieldsets = list(inline.get_fieldsets(request))
              readonly = list(inline.get_readonly_fields(request))
              inline_admin_formset = helpers.InlineAdminFormSet(inline, formset,
                  fieldsets, readonly, model_admin=self)
              inline_admin_formsets.append(inline_admin_formset)
              media = media + inline_admin_formset.media
  
          context = {
              'title': _('Add %s') % force_unicode(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': helpers.AdminErrorList(form, formsets),
              'root_path': self.admin_site.root_path,
              'app_label': opts.app_label,
          }
          context.update(extra_context or {})
          return self.render_change_form(request, context, form_url=form_url, add=True)
  
      @csrf_protect_m
      @transaction.commit_on_success
      def change_view(self, request, object_id, extra_context=None):
          "The 'change' admin view for this model."
          model = self.model
          opts = model._meta
  
          obj = self.get_object(request, unquote(object_id))
  
          if not self.has_change_permission(request, obj):
              raise PermissionDenied
  
          if obj is None:
              raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_unicode(opts.verbose_name), 'key': escape(object_id)})
  
          if request.method == 'POST' and request.POST.has_key("_saveasnew"):
              return self.add_view(request, form_url='../add/')
  
          ModelForm = self.get_form(request, obj)
          formsets = []
          if request.method == 'POST':
              form = ModelForm(request.POST, request.FILES, instance=obj)
              if form.is_valid():
                  form_validated = True
                  new_object = self.save_form(request, form, change=True)
              else:
                  form_validated = False
                  new_object = obj
              prefixes = {}
              for FormSet, inline in zip(self.get_formsets(request, new_object),
                                         self.inline_instances):
                  prefix = FormSet.get_default_prefix()
                  prefixes[prefix] = prefixes.get(prefix, 0) + 1
                  if prefixes[prefix] != 1:
                      prefix = "%s-%s" % (prefix, prefixes[prefix])
                  formset = FormSet(request.POST, request.FILES,
                                    instance=new_object, prefix=prefix,
                                    queryset=inline.queryset(request))
  
                  formsets.append(formset)
  
              if all_valid(formsets) and form_validated:
                  self.save_model(request, new_object, form, change=True)
                  form.save_m2m()
                  for formset in formsets:
                      self.save_formset(request, form, formset, change=True)
  
                  change_message = self.construct_change_message(request, form, formsets)
                  self.log_change(request, new_object, change_message)
                  return self.response_change(request, new_object)
  
          else:
              form = ModelForm(instance=obj)
              prefixes = {}
              for FormSet, inline in zip(self.get_formsets(request, obj), self.inline_instances):
                  prefix = FormSet.get_default_prefix()
                  prefixes[prefix] = prefixes.get(prefix, 0) + 1
                  if prefixes[prefix] != 1:
                      prefix = "%s-%s" % (prefix, prefixes[prefix])
                  formset = FormSet(instance=obj, prefix=prefix,
                                    queryset=inline.queryset(request))
                  formsets.append(formset)
  
          adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj),
              self.prepopulated_fields, self.get_readonly_fields(request, obj),
              model_admin=self)
          media = self.media + adminForm.media
  
          inline_admin_formsets = []
          for inline, formset in zip(self.inline_instances, formsets):
              fieldsets = list(inline.get_fieldsets(request, obj))
              readonly = list(inline.get_readonly_fields(request, obj))
              inline_admin_formset = helpers.InlineAdminFormSet(inline, formset,
                  fieldsets, readonly, model_admin=self)
              inline_admin_formsets.append(inline_admin_formset)
              media = media + inline_admin_formset.media
  
          context = {
              'title': _('Change %s') % force_unicode(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': helpers.AdminErrorList(form, formsets),
              'root_path': self.admin_site.root_path,
              'app_label': opts.app_label,
          }
          context.update(extra_context or {})
          return self.render_change_form(request, context, change=True, obj=obj)
  
      @csrf_protect_m
      def changelist_view(self, request, extra_context=None):
          "The 'change list' admin view for this model."
          from django.contrib.admin.views.main import ERROR_FLAG
          opts = self.model._meta
          app_label = opts.app_label
          if not self.has_change_permission(request, None):
              raise PermissionDenied
  
          # Check actions to see if any are available on this changelist
          actions = self.get_actions(request)
  
          # Remove action checkboxes if there aren't any actions available.
          list_display = list(self.list_display)
          if not actions:
              try:
                  list_display.remove('action_checkbox')
              except ValueError:
                  pass
  
          ChangeList = self.get_changelist(request)
          try:
              cl = ChangeList(request, self.model, list_display, self.list_display_links, self.list_filter,
                  self.date_hierarchy, self.search_fields, self.list_select_related, self.list_per_page, self.list_editable, 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')
  
          # If the request was POSTed, this might be a bulk action or a bulk
          # edit. Try to look up an action or confirmation first, but if this
          # isn't an action the POST will fall through to the bulk edit check,
          # below.
          action_failed = False
          selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME)
  
          # Actions with no confirmation
          if (actions and request.method == 'POST' and
                  'index' in request.POST and '_save' not in request.POST):
              if selected:
                  response = self.response_action(request, queryset=cl.get_query_set())
                  if response:
                      return response
                  else:
                      action_failed = True
              else:
                  msg = _("Items must be selected in order to perform "
                          "actions on them. No items have been changed.")
                  self.message_user(request, msg)
                  action_failed = True
  
          # Actions with confirmation
          if (actions and request.method == 'POST' and
                  helpers.ACTION_CHECKBOX_NAME in request.POST and
                  'index' not in request.POST and '_save' not in request.POST):
              if selected:
                  response = self.response_action(request, queryset=cl.get_query_set())
                  if response:
                      return response
                  else:
                      action_failed = True
  
          # If we're allowing changelist editing, we need to construct a formset
          # for the changelist given all the fields to be edited. Then we'll
          # use the formset to validate/process POSTed data.
          formset = cl.formset = None
  
          # Handle POSTed bulk-edit data.
          if (request.method == "POST" and self.list_editable and
                  '_save' in request.POST and not action_failed):
              FormSet = self.get_changelist_formset(request)
              formset = cl.formset = FormSet(request.POST, request.FILES, queryset=cl.result_list)
              if formset.is_valid():
                  changecount = 0
                  for form in formset.forms:
                      if form.has_changed():
                          obj = self.save_form(request, form, change=True)
                          self.save_model(request, obj, form, change=True)
                          form.save_m2m()
                          change_msg = self.construct_change_message(request, form, None)
                          self.log_change(request, obj, change_msg)
                          changecount += 1
  
                  if changecount:
                      if changecount == 1:
                          name = force_unicode(opts.verbose_name)
                      else:
                          name = force_unicode(opts.verbose_name_plural)
                      msg = ungettext("%(count)s %(name)s was changed successfully.",
                                      "%(count)s %(name)s were changed successfully.",
                                      changecount) % {'count': changecount,
                                                      'name': name,
                                                      'obj': force_unicode(obj)}
                      self.message_user(request, msg)
  
                  return HttpResponseRedirect(request.get_full_path())
  
          # Handle GET -- construct a formset for display.
          elif self.list_editable:
              FormSet = self.get_changelist_formset(request)
              formset = cl.formset = FormSet(queryset=cl.result_list)
  
          # Build the list of media to be used by the formset.
          if formset:
              media = self.media + formset.media
          else:
              media = self.media
  
          # Build the action form and populate it with available actions.
          if actions:
              action_form = self.action_form(auto_id=None)
              action_form.fields['action'].choices = self.get_action_choices(request)
          else:
              action_form = None
  
          selection_note_all = ungettext('%(total_count)s selected',
              'All %(total_count)s selected', cl.result_count)
  
          context = {
              'module_name': force_unicode(opts.verbose_name_plural),
              'selection_note': _('0 of %(cnt)s selected') % {'cnt': len(cl.result_list)},
              'selection_note_all': selection_note_all % {'total_count': cl.result_count},
              'title': cl.title,
              'is_popup': cl.is_popup,
              'cl': cl,
              'media': media,
              'has_add_permission': self.has_add_permission(request),
              'root_path': self.admin_site.root_path,
              'app_label': app_label,
              'action_form': action_form,
              'actions_on_top': self.actions_on_top,
              'actions_on_bottom': self.actions_on_bottom,
              'actions_selection_counter': self.actions_selection_counter,
          }
          context.update(extra_context or {})
          context_instance = template.RequestContext(request, current_app=self.admin_site.name)
          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=context_instance)
  
      @csrf_protect_m
      def delete_view(self, request, object_id, extra_context=None):
          "The 'delete' admin view for this model."
          opts = self.model._meta
          app_label = opts.app_label
  
          obj = self.get_object(request, unquote(object_id))
  
          if not self.has_delete_permission(request, obj):
              raise PermissionDenied
  
          if obj is None:
              raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_unicode(opts.verbose_name), 'key': escape(object_id)})
  
          # Populate deleted_objects, a data structure of all related objects that
          # will also be deleted.
          (deleted_objects, perms_needed) = get_deleted_objects((obj,), opts, request.user, self.admin_site)
  
          if request.POST: # The user has already confirmed the deletion.
              if perms_needed:
                  raise PermissionDenied
              obj_display = force_unicode(obj)
              self.log_deletion(request, obj, obj_display)
              obj.delete()
  
              self.message_user(request, _('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": force_unicode(opts.verbose_name),
              "object": obj,
              "deleted_objects": deleted_objects,
              "perms_lacking": perms_needed,
              "opts": opts,
              "root_path": self.admin_site.root_path,
              "app_label": app_label,
          }
          context.update(extra_context or {})
          context_instance = template.RequestContext(request, current_app=self.admin_site.name)
          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=context_instance)
  
      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
          app_label = opts.app_label
          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=unquote(object_id))
          context = {
              'title': _('Change history: %s') % force_unicode(obj),
              'action_list': action_list,
              'module_name': capfirst(force_unicode(opts.verbose_name_plural)),
              'object': obj,
              'root_path': self.admin_site.root_path,
              'app_label': app_label,
          }
          context.update(extra_context or {})
          context_instance = template.RequestContext(request, current_app=self.admin_site.name)
          return render_to_response(self.object_history_template or [
              "admin/%s/%s/object_history.html" % (app_label, opts.object_name.lower()),
              "admin/%s/object_history.html" % app_label,
              "admin/object_history.html"
          ], context, context_instance=context_instance)
  
      #
      # DEPRECATED methods.
      #
      def __call__(self, request, url):
          """
          DEPRECATED: this is the old way of URL resolution, replaced by
          ``get_urls()``. This only called by AdminSite.root(), which is also
          deprecated.
  
          Again, remember that the following code only exists for
          backwards-compatibility. Any new URLs, changes to existing URLs, or
          whatever need to be done up in get_urls(), above!
  
          This function still exists for backwards-compatibility; it will be
          removed in Django 1.3.
          """
          # Delegate to the appropriate method, based on the URL.
          if url is None:
              return self.changelist_view(request)
          elif url == "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))
  
  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 = None
      template = None
      verbose_name = None
      verbose_name_plural = None
      can_delete = True
  
      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 _media(self):
          from django.conf import settings
          js = ['js/jquery.min.js', 'js/jquery.init.js', 'js/inlines.min.js']
          if self.prepopulated_fields:
              js.append('js/urlify.js')
              js.append('js/prepopulate.min.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 get_formset(self, request, obj=None, **kwargs):
          """Returns a BaseInlineFormSet class for use in admin add/change views."""
          if self.declared_fieldsets:
              fields = flatten_fieldsets(self.declared_fieldsets)
          else:
              fields = None
          if self.exclude is None:
              exclude = []
          else:
              exclude = list(self.exclude)
          exclude.extend(kwargs.get("exclude", []))
          exclude.extend(self.get_readonly_fields(request, obj))
          # if exclude is an empty list we use None, since that's the actual
          # default
          exclude = exclude or None
          defaults = {
              "form": self.form,
              "formset": self.formset,
              "fk_name": self.fk_name,
              "fields": fields,
              "exclude": exclude,
              "formfield_callback": curry(self.formfield_for_dbfield, request=request),
              "extra": self.extra,
              "max_num": self.max_num,
              "can_delete": self.can_delete,
          }
          defaults.update(kwargs)
          return inlineformset_factory(self.parent_model, self.model, **defaults)
  
      def get_fieldsets(self, request, obj=None):
          if self.declared_fieldsets:
              return self.declared_fieldsets
          form = self.get_formset(request).form
          fields = form.base_fields.keys() + list(self.get_readonly_fields(request, obj))
          return [(None, {'fields': fields})]
  
      def queryset(self, request):
          return self.model._default_manager.all()
  
  class StackedInline(InlineModelAdmin):
      template = 'admin/edit_inline/stacked.html'
  
  class TabularInline(InlineModelAdmin):
      template = 'admin/edit_inline/tabular.html'
  