web/lib/django/contrib/admin/options.py
changeset 38 77b6da96e6f1
equal deleted inserted replaced
37:8d941af65caf 38:77b6da96e6f1
       
     1 from django import forms, template
       
     2 from django.forms.formsets import all_valid
       
     3 from django.forms.models import modelform_factory, modelformset_factory, inlineformset_factory
       
     4 from django.forms.models import BaseInlineFormSet
       
     5 from django.contrib.contenttypes.models import ContentType
       
     6 from django.contrib.admin import widgets
       
     7 from django.contrib.admin import helpers
       
     8 from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects, model_ngettext, model_format_dict
       
     9 from django.contrib import messages
       
    10 from django.views.decorators.csrf import csrf_protect
       
    11 from django.core.exceptions import PermissionDenied, ValidationError
       
    12 from django.db import models, transaction
       
    13 from django.db.models.fields import BLANK_CHOICE_DASH
       
    14 from django.http import Http404, HttpResponse, HttpResponseRedirect
       
    15 from django.shortcuts import get_object_or_404, render_to_response
       
    16 from django.utils.decorators import method_decorator
       
    17 from django.utils.datastructures import SortedDict
       
    18 from django.utils.functional import update_wrapper
       
    19 from django.utils.html import escape
       
    20 from django.utils.safestring import mark_safe
       
    21 from django.utils.functional import curry
       
    22 from django.utils.text import capfirst, get_text_list
       
    23 from django.utils.translation import ugettext as _
       
    24 from django.utils.translation import ungettext, ugettext_lazy
       
    25 from django.utils.encoding import force_unicode
       
    26 
       
    27 HORIZONTAL, VERTICAL = 1, 2
       
    28 # returns the <ul> class for a given radio_admin field
       
    29 get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '')
       
    30 
       
    31 class IncorrectLookupParameters(Exception):
       
    32     pass
       
    33 
       
    34 # Defaults for formfield_overrides. ModelAdmin subclasses can change this
       
    35 # by adding to ModelAdmin.formfield_overrides.
       
    36 
       
    37 FORMFIELD_FOR_DBFIELD_DEFAULTS = {
       
    38     models.DateTimeField: {
       
    39         'form_class': forms.SplitDateTimeField,
       
    40         'widget': widgets.AdminSplitDateTime
       
    41     },
       
    42     models.DateField:       {'widget': widgets.AdminDateWidget},
       
    43     models.TimeField:       {'widget': widgets.AdminTimeWidget},
       
    44     models.TextField:       {'widget': widgets.AdminTextareaWidget},
       
    45     models.URLField:        {'widget': widgets.AdminURLFieldWidget},
       
    46     models.IntegerField:    {'widget': widgets.AdminIntegerFieldWidget},
       
    47     models.BigIntegerField: {'widget': widgets.AdminIntegerFieldWidget},
       
    48     models.CharField:       {'widget': widgets.AdminTextInputWidget},
       
    49     models.ImageField:      {'widget': widgets.AdminFileWidget},
       
    50     models.FileField:       {'widget': widgets.AdminFileWidget},
       
    51 }
       
    52 
       
    53 csrf_protect_m = method_decorator(csrf_protect)
       
    54 
       
    55 class BaseModelAdmin(object):
       
    56     """Functionality common to both ModelAdmin and InlineAdmin."""
       
    57     __metaclass__ = forms.MediaDefiningClass
       
    58 
       
    59     raw_id_fields = ()
       
    60     fields = None
       
    61     exclude = None
       
    62     fieldsets = None
       
    63     form = forms.ModelForm
       
    64     filter_vertical = ()
       
    65     filter_horizontal = ()
       
    66     radio_fields = {}
       
    67     prepopulated_fields = {}
       
    68     formfield_overrides = {}
       
    69     readonly_fields = ()
       
    70 
       
    71     def __init__(self):
       
    72         overrides = FORMFIELD_FOR_DBFIELD_DEFAULTS.copy()
       
    73         overrides.update(self.formfield_overrides)
       
    74         self.formfield_overrides = overrides
       
    75 
       
    76     def formfield_for_dbfield(self, db_field, **kwargs):
       
    77         """
       
    78         Hook for specifying the form Field instance for a given database Field
       
    79         instance.
       
    80 
       
    81         If kwargs are given, they're passed to the form Field's constructor.
       
    82         """
       
    83         request = kwargs.pop("request", None)
       
    84 
       
    85         # If the field specifies choices, we don't need to look for special
       
    86         # admin widgets - we just need to use a select widget of some kind.
       
    87         if db_field.choices:
       
    88             return self.formfield_for_choice_field(db_field, request, **kwargs)
       
    89 
       
    90         # ForeignKey or ManyToManyFields
       
    91         if isinstance(db_field, (models.ForeignKey, models.ManyToManyField)):
       
    92             # Combine the field kwargs with any options for formfield_overrides.
       
    93             # Make sure the passed in **kwargs override anything in
       
    94             # formfield_overrides because **kwargs is more specific, and should
       
    95             # always win.
       
    96             if db_field.__class__ in self.formfield_overrides:
       
    97                 kwargs = dict(self.formfield_overrides[db_field.__class__], **kwargs)
       
    98 
       
    99             # Get the correct formfield.
       
   100             if isinstance(db_field, models.ForeignKey):
       
   101                 formfield = self.formfield_for_foreignkey(db_field, request, **kwargs)
       
   102             elif isinstance(db_field, models.ManyToManyField):
       
   103                 formfield = self.formfield_for_manytomany(db_field, request, **kwargs)
       
   104 
       
   105             # For non-raw_id fields, wrap the widget with a wrapper that adds
       
   106             # extra HTML -- the "add other" interface -- to the end of the
       
   107             # rendered output. formfield can be None if it came from a
       
   108             # OneToOneField with parent_link=True or a M2M intermediary.
       
   109             if formfield and db_field.name not in self.raw_id_fields:
       
   110                 formfield.widget = widgets.RelatedFieldWidgetWrapper(formfield.widget, db_field.rel, self.admin_site)
       
   111 
       
   112             return formfield
       
   113 
       
   114         # If we've got overrides for the formfield defined, use 'em. **kwargs
       
   115         # passed to formfield_for_dbfield override the defaults.
       
   116         for klass in db_field.__class__.mro():
       
   117             if klass in self.formfield_overrides:
       
   118                 kwargs = dict(self.formfield_overrides[klass], **kwargs)
       
   119                 return db_field.formfield(**kwargs)
       
   120 
       
   121         # For any other type of field, just call its formfield() method.
       
   122         return db_field.formfield(**kwargs)
       
   123 
       
   124     def formfield_for_choice_field(self, db_field, request=None, **kwargs):
       
   125         """
       
   126         Get a form Field for a database Field that has declared choices.
       
   127         """
       
   128         # If the field is named as a radio_field, use a RadioSelect
       
   129         if db_field.name in self.radio_fields:
       
   130             # Avoid stomping on custom widget/choices arguments.
       
   131             if 'widget' not in kwargs:
       
   132                 kwargs['widget'] = widgets.AdminRadioSelect(attrs={
       
   133                     'class': get_ul_class(self.radio_fields[db_field.name]),
       
   134                 })
       
   135             if 'choices' not in kwargs:
       
   136                 kwargs['choices'] = db_field.get_choices(
       
   137                     include_blank = db_field.blank,
       
   138                     blank_choice=[('', _('None'))]
       
   139                 )
       
   140         return db_field.formfield(**kwargs)
       
   141 
       
   142     def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
       
   143         """
       
   144         Get a form Field for a ForeignKey.
       
   145         """
       
   146         db = kwargs.get('using')
       
   147         if db_field.name in self.raw_id_fields:
       
   148             kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel, using=db)
       
   149         elif db_field.name in self.radio_fields:
       
   150             kwargs['widget'] = widgets.AdminRadioSelect(attrs={
       
   151                 'class': get_ul_class(self.radio_fields[db_field.name]),
       
   152             })
       
   153             kwargs['empty_label'] = db_field.blank and _('None') or None
       
   154 
       
   155         return db_field.formfield(**kwargs)
       
   156 
       
   157     def formfield_for_manytomany(self, db_field, request=None, **kwargs):
       
   158         """
       
   159         Get a form Field for a ManyToManyField.
       
   160         """
       
   161         # If it uses an intermediary model that isn't auto created, don't show
       
   162         # a field in admin.
       
   163         if not db_field.rel.through._meta.auto_created:
       
   164             return None
       
   165         db = kwargs.get('using')
       
   166 
       
   167         if db_field.name in self.raw_id_fields:
       
   168             kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel, using=db)
       
   169             kwargs['help_text'] = ''
       
   170         elif db_field.name in (list(self.filter_vertical) + list(self.filter_horizontal)):
       
   171             kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, (db_field.name in self.filter_vertical))
       
   172 
       
   173         return db_field.formfield(**kwargs)
       
   174 
       
   175     def _declared_fieldsets(self):
       
   176         if self.fieldsets:
       
   177             return self.fieldsets
       
   178         elif self.fields:
       
   179             return [(None, {'fields': self.fields})]
       
   180         return None
       
   181     declared_fieldsets = property(_declared_fieldsets)
       
   182 
       
   183     def get_readonly_fields(self, request, obj=None):
       
   184         return self.readonly_fields
       
   185 
       
   186 class ModelAdmin(BaseModelAdmin):
       
   187     "Encapsulates all admin options and functionality for a given model."
       
   188 
       
   189     list_display = ('__str__',)
       
   190     list_display_links = ()
       
   191     list_filter = ()
       
   192     list_select_related = False
       
   193     list_per_page = 100
       
   194     list_editable = ()
       
   195     search_fields = ()
       
   196     date_hierarchy = None
       
   197     save_as = False
       
   198     save_on_top = False
       
   199     ordering = None
       
   200     inlines = []
       
   201 
       
   202     # Custom templates (designed to be over-ridden in subclasses)
       
   203     add_form_template = None
       
   204     change_form_template = None
       
   205     change_list_template = None
       
   206     delete_confirmation_template = None
       
   207     delete_selected_confirmation_template = None
       
   208     object_history_template = None
       
   209 
       
   210     # Actions
       
   211     actions = []
       
   212     action_form = helpers.ActionForm
       
   213     actions_on_top = True
       
   214     actions_on_bottom = False
       
   215     actions_selection_counter = True
       
   216 
       
   217     def __init__(self, model, admin_site):
       
   218         self.model = model
       
   219         self.opts = model._meta
       
   220         self.admin_site = admin_site
       
   221         self.inline_instances = []
       
   222         for inline_class in self.inlines:
       
   223             inline_instance = inline_class(self.model, self.admin_site)
       
   224             self.inline_instances.append(inline_instance)
       
   225         if 'action_checkbox' not in self.list_display and self.actions is not None:
       
   226             self.list_display = ['action_checkbox'] +  list(self.list_display)
       
   227         if not self.list_display_links:
       
   228             for name in self.list_display:
       
   229                 if name != 'action_checkbox':
       
   230                     self.list_display_links = [name]
       
   231                     break
       
   232         super(ModelAdmin, self).__init__()
       
   233 
       
   234     def get_urls(self):
       
   235         from django.conf.urls.defaults import patterns, url
       
   236 
       
   237         def wrap(view):
       
   238             def wrapper(*args, **kwargs):
       
   239                 return self.admin_site.admin_view(view)(*args, **kwargs)
       
   240             return update_wrapper(wrapper, view)
       
   241 
       
   242         info = self.model._meta.app_label, self.model._meta.module_name
       
   243 
       
   244         urlpatterns = patterns('',
       
   245             url(r'^$',
       
   246                 wrap(self.changelist_view),
       
   247                 name='%s_%s_changelist' % info),
       
   248             url(r'^add/$',
       
   249                 wrap(self.add_view),
       
   250                 name='%s_%s_add' % info),
       
   251             url(r'^(.+)/history/$',
       
   252                 wrap(self.history_view),
       
   253                 name='%s_%s_history' % info),
       
   254             url(r'^(.+)/delete/$',
       
   255                 wrap(self.delete_view),
       
   256                 name='%s_%s_delete' % info),
       
   257             url(r'^(.+)/$',
       
   258                 wrap(self.change_view),
       
   259                 name='%s_%s_change' % info),
       
   260         )
       
   261         return urlpatterns
       
   262 
       
   263     def urls(self):
       
   264         return self.get_urls()
       
   265     urls = property(urls)
       
   266 
       
   267     def _media(self):
       
   268         from django.conf import settings
       
   269 
       
   270         js = ['js/core.js', 'js/admin/RelatedObjectLookups.js',
       
   271               'js/jquery.min.js', 'js/jquery.init.js']
       
   272         if self.actions is not None:
       
   273             js.extend(['js/actions.min.js'])
       
   274         if self.prepopulated_fields:
       
   275             js.append('js/urlify.js')
       
   276             js.append('js/prepopulate.min.js')
       
   277         if self.opts.get_ordered_objects():
       
   278             js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.js'])
       
   279 
       
   280         return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js])
       
   281     media = property(_media)
       
   282 
       
   283     def has_add_permission(self, request):
       
   284         "Returns True if the given request has permission to add an object."
       
   285         opts = self.opts
       
   286         return request.user.has_perm(opts.app_label + '.' + opts.get_add_permission())
       
   287 
       
   288     def has_change_permission(self, request, obj=None):
       
   289         """
       
   290         Returns True if the given request has permission to change the given
       
   291         Django model instance.
       
   292 
       
   293         If `obj` is None, this should return True if the given request has
       
   294         permission to change *any* object of the given type.
       
   295         """
       
   296         opts = self.opts
       
   297         return request.user.has_perm(opts.app_label + '.' + opts.get_change_permission())
       
   298 
       
   299     def has_delete_permission(self, request, obj=None):
       
   300         """
       
   301         Returns True if the given request has permission to change the given
       
   302         Django model instance.
       
   303 
       
   304         If `obj` is None, this should return True if the given request has
       
   305         permission to delete *any* object of the given type.
       
   306         """
       
   307         opts = self.opts
       
   308         return request.user.has_perm(opts.app_label + '.' + opts.get_delete_permission())
       
   309 
       
   310     def get_model_perms(self, request):
       
   311         """
       
   312         Returns a dict of all perms for this model. This dict has the keys
       
   313         ``add``, ``change``, and ``delete`` mapping to the True/False for each
       
   314         of those actions.
       
   315         """
       
   316         return {
       
   317             'add': self.has_add_permission(request),
       
   318             'change': self.has_change_permission(request),
       
   319             'delete': self.has_delete_permission(request),
       
   320         }
       
   321 
       
   322     def queryset(self, request):
       
   323         """
       
   324         Returns a QuerySet of all model instances that can be edited by the
       
   325         admin site. This is used by changelist_view.
       
   326         """
       
   327         qs = self.model._default_manager.get_query_set()
       
   328         # TODO: this should be handled by some parameter to the ChangeList.
       
   329         ordering = self.ordering or () # otherwise we might try to *None, which is bad ;)
       
   330         if ordering:
       
   331             qs = qs.order_by(*ordering)
       
   332         return qs
       
   333 
       
   334     def get_fieldsets(self, request, obj=None):
       
   335         "Hook for specifying fieldsets for the add form."
       
   336         if self.declared_fieldsets:
       
   337             return self.declared_fieldsets
       
   338         form = self.get_form(request, obj)
       
   339         fields = form.base_fields.keys() + list(self.get_readonly_fields(request, obj))
       
   340         return [(None, {'fields': fields})]
       
   341 
       
   342     def get_form(self, request, obj=None, **kwargs):
       
   343         """
       
   344         Returns a Form class for use in the admin add view. This is used by
       
   345         add_view and change_view.
       
   346         """
       
   347         if self.declared_fieldsets:
       
   348             fields = flatten_fieldsets(self.declared_fieldsets)
       
   349         else:
       
   350             fields = None
       
   351         if self.exclude is None:
       
   352             exclude = []
       
   353         else:
       
   354             exclude = list(self.exclude)
       
   355         exclude.extend(kwargs.get("exclude", []))
       
   356         exclude.extend(self.get_readonly_fields(request, obj))
       
   357         # if exclude is an empty list we pass None to be consistant with the
       
   358         # default on modelform_factory
       
   359         exclude = exclude or None
       
   360         defaults = {
       
   361             "form": self.form,
       
   362             "fields": fields,
       
   363             "exclude": exclude,
       
   364             "formfield_callback": curry(self.formfield_for_dbfield, request=request),
       
   365         }
       
   366         defaults.update(kwargs)
       
   367         return modelform_factory(self.model, **defaults)
       
   368 
       
   369     def get_changelist(self, request, **kwargs):
       
   370         """
       
   371         Returns the ChangeList class for use on the changelist page.
       
   372         """
       
   373         from django.contrib.admin.views.main import ChangeList
       
   374         return ChangeList
       
   375 
       
   376     def get_object(self, request, object_id):
       
   377         """
       
   378         Returns an instance matching the primary key provided. ``None``  is
       
   379         returned if no match is found (or the object_id failed validation
       
   380         against the primary key field).
       
   381         """
       
   382         queryset = self.queryset(request)
       
   383         model = queryset.model
       
   384         try:
       
   385             object_id = model._meta.pk.to_python(object_id)
       
   386             return queryset.get(pk=object_id)
       
   387         except (model.DoesNotExist, ValidationError):
       
   388             return None
       
   389 
       
   390     def get_changelist_form(self, request, **kwargs):
       
   391         """
       
   392         Returns a Form class for use in the Formset on the changelist page.
       
   393         """
       
   394         defaults = {
       
   395             "formfield_callback": curry(self.formfield_for_dbfield, request=request),
       
   396         }
       
   397         defaults.update(kwargs)
       
   398         return modelform_factory(self.model, **defaults)
       
   399 
       
   400     def get_changelist_formset(self, request, **kwargs):
       
   401         """
       
   402         Returns a FormSet class for use on the changelist page if list_editable
       
   403         is used.
       
   404         """
       
   405         defaults = {
       
   406             "formfield_callback": curry(self.formfield_for_dbfield, request=request),
       
   407         }
       
   408         defaults.update(kwargs)
       
   409         return modelformset_factory(self.model,
       
   410             self.get_changelist_form(request), extra=0,
       
   411             fields=self.list_editable, **defaults)
       
   412 
       
   413     def get_formsets(self, request, obj=None):
       
   414         for inline in self.inline_instances:
       
   415             yield inline.get_formset(request, obj)
       
   416 
       
   417     def log_addition(self, request, object):
       
   418         """
       
   419         Log that an object has been successfully added.
       
   420 
       
   421         The default implementation creates an admin LogEntry object.
       
   422         """
       
   423         from django.contrib.admin.models import LogEntry, ADDITION
       
   424         LogEntry.objects.log_action(
       
   425             user_id         = request.user.pk,
       
   426             content_type_id = ContentType.objects.get_for_model(object).pk,
       
   427             object_id       = object.pk,
       
   428             object_repr     = force_unicode(object),
       
   429             action_flag     = ADDITION
       
   430         )
       
   431 
       
   432     def log_change(self, request, object, message):
       
   433         """
       
   434         Log that an object has been successfully changed.
       
   435 
       
   436         The default implementation creates an admin LogEntry object.
       
   437         """
       
   438         from django.contrib.admin.models import LogEntry, CHANGE
       
   439         LogEntry.objects.log_action(
       
   440             user_id         = request.user.pk,
       
   441             content_type_id = ContentType.objects.get_for_model(object).pk,
       
   442             object_id       = object.pk,
       
   443             object_repr     = force_unicode(object),
       
   444             action_flag     = CHANGE,
       
   445             change_message  = message
       
   446         )
       
   447 
       
   448     def log_deletion(self, request, object, object_repr):
       
   449         """
       
   450         Log that an object has been successfully deleted. Note that since the
       
   451         object is deleted, it might no longer be safe to call *any* methods
       
   452         on the object, hence this method getting object_repr.
       
   453 
       
   454         The default implementation creates an admin LogEntry object.
       
   455         """
       
   456         from django.contrib.admin.models import LogEntry, DELETION
       
   457         LogEntry.objects.log_action(
       
   458             user_id         = request.user.id,
       
   459             content_type_id = ContentType.objects.get_for_model(self.model).pk,
       
   460             object_id       = object.pk,
       
   461             object_repr     = object_repr,
       
   462             action_flag     = DELETION
       
   463         )
       
   464 
       
   465     def action_checkbox(self, obj):
       
   466         """
       
   467         A list_display column containing a checkbox widget.
       
   468         """
       
   469         return helpers.checkbox.render(helpers.ACTION_CHECKBOX_NAME, force_unicode(obj.pk))
       
   470     action_checkbox.short_description = mark_safe('<input type="checkbox" id="action-toggle" />')
       
   471     action_checkbox.allow_tags = True
       
   472 
       
   473     def get_actions(self, request):
       
   474         """
       
   475         Return a dictionary mapping the names of all actions for this
       
   476         ModelAdmin to a tuple of (callable, name, description) for each action.
       
   477         """
       
   478         # If self.actions is explicitally set to None that means that we don't
       
   479         # want *any* actions enabled on this page.
       
   480         if self.actions is None:
       
   481             return []
       
   482 
       
   483         actions = []
       
   484 
       
   485         # Gather actions from the admin site first
       
   486         for (name, func) in self.admin_site.actions:
       
   487             description = getattr(func, 'short_description', name.replace('_', ' '))
       
   488             actions.append((func, name, description))
       
   489 
       
   490         # Then gather them from the model admin and all parent classes,
       
   491         # starting with self and working back up.
       
   492         for klass in self.__class__.mro()[::-1]:
       
   493             class_actions = getattr(klass, 'actions', [])
       
   494             # Avoid trying to iterate over None
       
   495             if not class_actions:
       
   496                 continue
       
   497             actions.extend([self.get_action(action) for action in class_actions])
       
   498 
       
   499         # get_action might have returned None, so filter any of those out.
       
   500         actions = filter(None, actions)
       
   501 
       
   502         # Convert the actions into a SortedDict keyed by name
       
   503         # and sorted by description.
       
   504         actions.sort(lambda a,b: cmp(a[2].lower(), b[2].lower()))
       
   505         actions = SortedDict([
       
   506             (name, (func, name, desc))
       
   507             for func, name, desc in actions
       
   508         ])
       
   509 
       
   510         return actions
       
   511 
       
   512     def get_action_choices(self, request, default_choices=BLANK_CHOICE_DASH):
       
   513         """
       
   514         Return a list of choices for use in a form object.  Each choice is a
       
   515         tuple (name, description).
       
   516         """
       
   517         choices = [] + default_choices
       
   518         for func, name, description in self.get_actions(request).itervalues():
       
   519             choice = (name, description % model_format_dict(self.opts))
       
   520             choices.append(choice)
       
   521         return choices
       
   522 
       
   523     def get_action(self, action):
       
   524         """
       
   525         Return a given action from a parameter, which can either be a callable,
       
   526         or the name of a method on the ModelAdmin.  Return is a tuple of
       
   527         (callable, name, description).
       
   528         """
       
   529         # If the action is a callable, just use it.
       
   530         if callable(action):
       
   531             func = action
       
   532             action = action.__name__
       
   533 
       
   534         # Next, look for a method. Grab it off self.__class__ to get an unbound
       
   535         # method instead of a bound one; this ensures that the calling
       
   536         # conventions are the same for functions and methods.
       
   537         elif hasattr(self.__class__, action):
       
   538             func = getattr(self.__class__, action)
       
   539 
       
   540         # Finally, look for a named method on the admin site
       
   541         else:
       
   542             try:
       
   543                 func = self.admin_site.get_action(action)
       
   544             except KeyError:
       
   545                 return None
       
   546 
       
   547         if hasattr(func, 'short_description'):
       
   548             description = func.short_description
       
   549         else:
       
   550             description = capfirst(action.replace('_', ' '))
       
   551         return func, action, description
       
   552 
       
   553     def construct_change_message(self, request, form, formsets):
       
   554         """
       
   555         Construct a change message from a changed object.
       
   556         """
       
   557         change_message = []
       
   558         if form.changed_data:
       
   559             change_message.append(_('Changed %s.') % get_text_list(form.changed_data, _('and')))
       
   560 
       
   561         if formsets:
       
   562             for formset in formsets:
       
   563                 for added_object in formset.new_objects:
       
   564                     change_message.append(_('Added %(name)s "%(object)s".')
       
   565                                           % {'name': force_unicode(added_object._meta.verbose_name),
       
   566                                              'object': force_unicode(added_object)})
       
   567                 for changed_object, changed_fields in formset.changed_objects:
       
   568                     change_message.append(_('Changed %(list)s for %(name)s "%(object)s".')
       
   569                                           % {'list': get_text_list(changed_fields, _('and')),
       
   570                                              'name': force_unicode(changed_object._meta.verbose_name),
       
   571                                              'object': force_unicode(changed_object)})
       
   572                 for deleted_object in formset.deleted_objects:
       
   573                     change_message.append(_('Deleted %(name)s "%(object)s".')
       
   574                                           % {'name': force_unicode(deleted_object._meta.verbose_name),
       
   575                                              'object': force_unicode(deleted_object)})
       
   576         change_message = ' '.join(change_message)
       
   577         return change_message or _('No fields changed.')
       
   578 
       
   579     def message_user(self, request, message):
       
   580         """
       
   581         Send a message to the user. The default implementation
       
   582         posts a message using the django.contrib.messages backend.
       
   583         """
       
   584         messages.info(request, message)
       
   585 
       
   586     def save_form(self, request, form, change):
       
   587         """
       
   588         Given a ModelForm return an unsaved instance. ``change`` is True if
       
   589         the object is being changed, and False if it's being added.
       
   590         """
       
   591         return form.save(commit=False)
       
   592 
       
   593     def save_model(self, request, obj, form, change):
       
   594         """
       
   595         Given a model instance save it to the database.
       
   596         """
       
   597         obj.save()
       
   598 
       
   599     def save_formset(self, request, form, formset, change):
       
   600         """
       
   601         Given an inline formset save it to the database.
       
   602         """
       
   603         formset.save()
       
   604 
       
   605     def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
       
   606         opts = self.model._meta
       
   607         app_label = opts.app_label
       
   608         ordered_objects = opts.get_ordered_objects()
       
   609         context.update({
       
   610             'add': add,
       
   611             'change': change,
       
   612             'has_add_permission': self.has_add_permission(request),
       
   613             'has_change_permission': self.has_change_permission(request, obj),
       
   614             'has_delete_permission': self.has_delete_permission(request, obj),
       
   615             'has_file_field': True, # FIXME - this should check if form or formsets have a FileField,
       
   616             'has_absolute_url': hasattr(self.model, 'get_absolute_url'),
       
   617             'ordered_objects': ordered_objects,
       
   618             'form_url': mark_safe(form_url),
       
   619             'opts': opts,
       
   620             'content_type_id': ContentType.objects.get_for_model(self.model).id,
       
   621             'save_as': self.save_as,
       
   622             'save_on_top': self.save_on_top,
       
   623             'root_path': self.admin_site.root_path,
       
   624         })
       
   625         if add and self.add_form_template is not None:
       
   626             form_template = self.add_form_template
       
   627         else:
       
   628             form_template = self.change_form_template
       
   629         context_instance = template.RequestContext(request, current_app=self.admin_site.name)
       
   630         return render_to_response(form_template or [
       
   631             "admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()),
       
   632             "admin/%s/change_form.html" % app_label,
       
   633             "admin/change_form.html"
       
   634         ], context, context_instance=context_instance)
       
   635 
       
   636     def response_add(self, request, obj, post_url_continue='../%s/'):
       
   637         """
       
   638         Determines the HttpResponse for the add_view stage.
       
   639         """
       
   640         opts = obj._meta
       
   641         pk_value = obj._get_pk_val()
       
   642 
       
   643         msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj)}
       
   644         # Here, we distinguish between different save types by checking for
       
   645         # the presence of keys in request.POST.
       
   646         if request.POST.has_key("_continue"):
       
   647             self.message_user(request, msg + ' ' + _("You may edit it again below."))
       
   648             if request.POST.has_key("_popup"):
       
   649                 post_url_continue += "?_popup=1"
       
   650             return HttpResponseRedirect(post_url_continue % pk_value)
       
   651 
       
   652         if request.POST.has_key("_popup"):
       
   653             return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script>' % \
       
   654                 # escape() calls force_unicode.
       
   655                 (escape(pk_value), escape(obj)))
       
   656         elif request.POST.has_key("_addanother"):
       
   657             self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name)))
       
   658             return HttpResponseRedirect(request.path)
       
   659         else:
       
   660             self.message_user(request, msg)
       
   661 
       
   662             # Figure out where to redirect. If the user has change permission,
       
   663             # redirect to the change-list page for this object. Otherwise,
       
   664             # redirect to the admin index.
       
   665             if self.has_change_permission(request, None):
       
   666                 post_url = '../'
       
   667             else:
       
   668                 post_url = '../../../'
       
   669             return HttpResponseRedirect(post_url)
       
   670 
       
   671     def response_change(self, request, obj):
       
   672         """
       
   673         Determines the HttpResponse for the change_view stage.
       
   674         """
       
   675         opts = obj._meta
       
   676         pk_value = obj._get_pk_val()
       
   677 
       
   678         msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj)}
       
   679         if request.POST.has_key("_continue"):
       
   680             self.message_user(request, msg + ' ' + _("You may edit it again below."))
       
   681             if request.REQUEST.has_key('_popup'):
       
   682                 return HttpResponseRedirect(request.path + "?_popup=1")
       
   683             else:
       
   684                 return HttpResponseRedirect(request.path)
       
   685         elif request.POST.has_key("_saveasnew"):
       
   686             msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': force_unicode(opts.verbose_name), 'obj': obj}
       
   687             self.message_user(request, msg)
       
   688             return HttpResponseRedirect("../%s/" % pk_value)
       
   689         elif request.POST.has_key("_addanother"):
       
   690             self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name)))
       
   691             return HttpResponseRedirect("../add/")
       
   692         else:
       
   693             self.message_user(request, msg)
       
   694             return HttpResponseRedirect("../")
       
   695 
       
   696     def response_action(self, request, queryset):
       
   697         """
       
   698         Handle an admin action. This is called if a request is POSTed to the
       
   699         changelist; it returns an HttpResponse if the action was handled, and
       
   700         None otherwise.
       
   701         """
       
   702 
       
   703         # There can be multiple action forms on the page (at the top
       
   704         # and bottom of the change list, for example). Get the action
       
   705         # whose button was pushed.
       
   706         try:
       
   707             action_index = int(request.POST.get('index', 0))
       
   708         except ValueError:
       
   709             action_index = 0
       
   710 
       
   711         # Construct the action form.
       
   712         data = request.POST.copy()
       
   713         data.pop(helpers.ACTION_CHECKBOX_NAME, None)
       
   714         data.pop("index", None)
       
   715 
       
   716         # Use the action whose button was pushed
       
   717         try:
       
   718             data.update({'action': data.getlist('action')[action_index]})
       
   719         except IndexError:
       
   720             # If we didn't get an action from the chosen form that's invalid
       
   721             # POST data, so by deleting action it'll fail the validation check
       
   722             # below. So no need to do anything here
       
   723             pass
       
   724 
       
   725         action_form = self.action_form(data, auto_id=None)
       
   726         action_form.fields['action'].choices = self.get_action_choices(request)
       
   727 
       
   728         # If the form's valid we can handle the action.
       
   729         if action_form.is_valid():
       
   730             action = action_form.cleaned_data['action']
       
   731             select_across = action_form.cleaned_data['select_across']
       
   732             func, name, description = self.get_actions(request)[action]
       
   733 
       
   734             # Get the list of selected PKs. If nothing's selected, we can't
       
   735             # perform an action on it, so bail. Except we want to perform
       
   736             # the action explicitly on all objects.
       
   737             selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME)
       
   738             if not selected and not select_across:
       
   739                 # Reminder that something needs to be selected or nothing will happen
       
   740                 msg = _("Items must be selected in order to perform "
       
   741                         "actions on them. No items have been changed.")
       
   742                 self.message_user(request, msg)
       
   743                 return None
       
   744 
       
   745             if not select_across:
       
   746                 # Perform the action only on the selected objects
       
   747                 queryset = queryset.filter(pk__in=selected)
       
   748 
       
   749             response = func(self, request, queryset)
       
   750 
       
   751             # Actions may return an HttpResponse, which will be used as the
       
   752             # response from the POST. If not, we'll be a good little HTTP
       
   753             # citizen and redirect back to the changelist page.
       
   754             if isinstance(response, HttpResponse):
       
   755                 return response
       
   756             else:
       
   757                 return HttpResponseRedirect(".")
       
   758         else:
       
   759             msg = _("No action selected.")
       
   760             self.message_user(request, msg)
       
   761             return None
       
   762 
       
   763     @csrf_protect_m
       
   764     @transaction.commit_on_success
       
   765     def add_view(self, request, form_url='', extra_context=None):
       
   766         "The 'add' admin view for this model."
       
   767         model = self.model
       
   768         opts = model._meta
       
   769 
       
   770         if not self.has_add_permission(request):
       
   771             raise PermissionDenied
       
   772 
       
   773         ModelForm = self.get_form(request)
       
   774         formsets = []
       
   775         if request.method == 'POST':
       
   776             form = ModelForm(request.POST, request.FILES)
       
   777             if form.is_valid():
       
   778                 new_object = self.save_form(request, form, change=False)
       
   779                 form_validated = True
       
   780             else:
       
   781                 form_validated = False
       
   782                 new_object = self.model()
       
   783             prefixes = {}
       
   784             for FormSet, inline in zip(self.get_formsets(request), self.inline_instances):
       
   785                 prefix = FormSet.get_default_prefix()
       
   786                 prefixes[prefix] = prefixes.get(prefix, 0) + 1
       
   787                 if prefixes[prefix] != 1:
       
   788                     prefix = "%s-%s" % (prefix, prefixes[prefix])
       
   789                 formset = FormSet(data=request.POST, files=request.FILES,
       
   790                                   instance=new_object,
       
   791                                   save_as_new=request.POST.has_key("_saveasnew"),
       
   792                                   prefix=prefix, queryset=inline.queryset(request))
       
   793                 formsets.append(formset)
       
   794             if all_valid(formsets) and form_validated:
       
   795                 self.save_model(request, new_object, form, change=False)
       
   796                 form.save_m2m()
       
   797                 for formset in formsets:
       
   798                     self.save_formset(request, form, formset, change=False)
       
   799 
       
   800                 self.log_addition(request, new_object)
       
   801                 return self.response_add(request, new_object)
       
   802         else:
       
   803             # Prepare the dict of initial data from the request.
       
   804             # We have to special-case M2Ms as a list of comma-separated PKs.
       
   805             initial = dict(request.GET.items())
       
   806             for k in initial:
       
   807                 try:
       
   808                     f = opts.get_field(k)
       
   809                 except models.FieldDoesNotExist:
       
   810                     continue
       
   811                 if isinstance(f, models.ManyToManyField):
       
   812                     initial[k] = initial[k].split(",")
       
   813             form = ModelForm(initial=initial)
       
   814             prefixes = {}
       
   815             for FormSet, inline in zip(self.get_formsets(request),
       
   816                                        self.inline_instances):
       
   817                 prefix = FormSet.get_default_prefix()
       
   818                 prefixes[prefix] = prefixes.get(prefix, 0) + 1
       
   819                 if prefixes[prefix] != 1:
       
   820                     prefix = "%s-%s" % (prefix, prefixes[prefix])
       
   821                 formset = FormSet(instance=self.model(), prefix=prefix,
       
   822                                   queryset=inline.queryset(request))
       
   823                 formsets.append(formset)
       
   824 
       
   825         adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)),
       
   826             self.prepopulated_fields, self.get_readonly_fields(request),
       
   827             model_admin=self)
       
   828         media = self.media + adminForm.media
       
   829 
       
   830         inline_admin_formsets = []
       
   831         for inline, formset in zip(self.inline_instances, formsets):
       
   832             fieldsets = list(inline.get_fieldsets(request))
       
   833             readonly = list(inline.get_readonly_fields(request))
       
   834             inline_admin_formset = helpers.InlineAdminFormSet(inline, formset,
       
   835                 fieldsets, readonly, model_admin=self)
       
   836             inline_admin_formsets.append(inline_admin_formset)
       
   837             media = media + inline_admin_formset.media
       
   838 
       
   839         context = {
       
   840             'title': _('Add %s') % force_unicode(opts.verbose_name),
       
   841             'adminform': adminForm,
       
   842             'is_popup': request.REQUEST.has_key('_popup'),
       
   843             'show_delete': False,
       
   844             'media': mark_safe(media),
       
   845             'inline_admin_formsets': inline_admin_formsets,
       
   846             'errors': helpers.AdminErrorList(form, formsets),
       
   847             'root_path': self.admin_site.root_path,
       
   848             'app_label': opts.app_label,
       
   849         }
       
   850         context.update(extra_context or {})
       
   851         return self.render_change_form(request, context, form_url=form_url, add=True)
       
   852 
       
   853     @csrf_protect_m
       
   854     @transaction.commit_on_success
       
   855     def change_view(self, request, object_id, extra_context=None):
       
   856         "The 'change' admin view for this model."
       
   857         model = self.model
       
   858         opts = model._meta
       
   859 
       
   860         obj = self.get_object(request, unquote(object_id))
       
   861 
       
   862         if not self.has_change_permission(request, obj):
       
   863             raise PermissionDenied
       
   864 
       
   865         if obj is None:
       
   866             raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_unicode(opts.verbose_name), 'key': escape(object_id)})
       
   867 
       
   868         if request.method == 'POST' and request.POST.has_key("_saveasnew"):
       
   869             return self.add_view(request, form_url='../add/')
       
   870 
       
   871         ModelForm = self.get_form(request, obj)
       
   872         formsets = []
       
   873         if request.method == 'POST':
       
   874             form = ModelForm(request.POST, request.FILES, instance=obj)
       
   875             if form.is_valid():
       
   876                 form_validated = True
       
   877                 new_object = self.save_form(request, form, change=True)
       
   878             else:
       
   879                 form_validated = False
       
   880                 new_object = obj
       
   881             prefixes = {}
       
   882             for FormSet, inline in zip(self.get_formsets(request, new_object),
       
   883                                        self.inline_instances):
       
   884                 prefix = FormSet.get_default_prefix()
       
   885                 prefixes[prefix] = prefixes.get(prefix, 0) + 1
       
   886                 if prefixes[prefix] != 1:
       
   887                     prefix = "%s-%s" % (prefix, prefixes[prefix])
       
   888                 formset = FormSet(request.POST, request.FILES,
       
   889                                   instance=new_object, prefix=prefix,
       
   890                                   queryset=inline.queryset(request))
       
   891 
       
   892                 formsets.append(formset)
       
   893 
       
   894             if all_valid(formsets) and form_validated:
       
   895                 self.save_model(request, new_object, form, change=True)
       
   896                 form.save_m2m()
       
   897                 for formset in formsets:
       
   898                     self.save_formset(request, form, formset, change=True)
       
   899 
       
   900                 change_message = self.construct_change_message(request, form, formsets)
       
   901                 self.log_change(request, new_object, change_message)
       
   902                 return self.response_change(request, new_object)
       
   903 
       
   904         else:
       
   905             form = ModelForm(instance=obj)
       
   906             prefixes = {}
       
   907             for FormSet, inline in zip(self.get_formsets(request, obj), self.inline_instances):
       
   908                 prefix = FormSet.get_default_prefix()
       
   909                 prefixes[prefix] = prefixes.get(prefix, 0) + 1
       
   910                 if prefixes[prefix] != 1:
       
   911                     prefix = "%s-%s" % (prefix, prefixes[prefix])
       
   912                 formset = FormSet(instance=obj, prefix=prefix,
       
   913                                   queryset=inline.queryset(request))
       
   914                 formsets.append(formset)
       
   915 
       
   916         adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj),
       
   917             self.prepopulated_fields, self.get_readonly_fields(request, obj),
       
   918             model_admin=self)
       
   919         media = self.media + adminForm.media
       
   920 
       
   921         inline_admin_formsets = []
       
   922         for inline, formset in zip(self.inline_instances, formsets):
       
   923             fieldsets = list(inline.get_fieldsets(request, obj))
       
   924             readonly = list(inline.get_readonly_fields(request, obj))
       
   925             inline_admin_formset = helpers.InlineAdminFormSet(inline, formset,
       
   926                 fieldsets, readonly, model_admin=self)
       
   927             inline_admin_formsets.append(inline_admin_formset)
       
   928             media = media + inline_admin_formset.media
       
   929 
       
   930         context = {
       
   931             'title': _('Change %s') % force_unicode(opts.verbose_name),
       
   932             'adminform': adminForm,
       
   933             'object_id': object_id,
       
   934             'original': obj,
       
   935             'is_popup': request.REQUEST.has_key('_popup'),
       
   936             'media': mark_safe(media),
       
   937             'inline_admin_formsets': inline_admin_formsets,
       
   938             'errors': helpers.AdminErrorList(form, formsets),
       
   939             'root_path': self.admin_site.root_path,
       
   940             'app_label': opts.app_label,
       
   941         }
       
   942         context.update(extra_context or {})
       
   943         return self.render_change_form(request, context, change=True, obj=obj)
       
   944 
       
   945     @csrf_protect_m
       
   946     def changelist_view(self, request, extra_context=None):
       
   947         "The 'change list' admin view for this model."
       
   948         from django.contrib.admin.views.main import ERROR_FLAG
       
   949         opts = self.model._meta
       
   950         app_label = opts.app_label
       
   951         if not self.has_change_permission(request, None):
       
   952             raise PermissionDenied
       
   953 
       
   954         # Check actions to see if any are available on this changelist
       
   955         actions = self.get_actions(request)
       
   956 
       
   957         # Remove action checkboxes if there aren't any actions available.
       
   958         list_display = list(self.list_display)
       
   959         if not actions:
       
   960             try:
       
   961                 list_display.remove('action_checkbox')
       
   962             except ValueError:
       
   963                 pass
       
   964 
       
   965         ChangeList = self.get_changelist(request)
       
   966         try:
       
   967             cl = ChangeList(request, self.model, list_display, self.list_display_links, self.list_filter,
       
   968                 self.date_hierarchy, self.search_fields, self.list_select_related, self.list_per_page, self.list_editable, self)
       
   969         except IncorrectLookupParameters:
       
   970             # Wacky lookup parameters were given, so redirect to the main
       
   971             # changelist page, without parameters, and pass an 'invalid=1'
       
   972             # parameter via the query string. If wacky parameters were given
       
   973             # and the 'invalid=1' parameter was already in the query string,
       
   974             # something is screwed up with the database, so display an error
       
   975             # page.
       
   976             if ERROR_FLAG in request.GET.keys():
       
   977                 return render_to_response('admin/invalid_setup.html', {'title': _('Database error')})
       
   978             return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1')
       
   979 
       
   980         # If the request was POSTed, this might be a bulk action or a bulk
       
   981         # edit. Try to look up an action or confirmation first, but if this
       
   982         # isn't an action the POST will fall through to the bulk edit check,
       
   983         # below.
       
   984         action_failed = False
       
   985         selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME)
       
   986 
       
   987         # Actions with no confirmation
       
   988         if (actions and request.method == 'POST' and
       
   989                 'index' in request.POST and '_save' not in request.POST):
       
   990             if selected:
       
   991                 response = self.response_action(request, queryset=cl.get_query_set())
       
   992                 if response:
       
   993                     return response
       
   994                 else:
       
   995                     action_failed = True
       
   996             else:
       
   997                 msg = _("Items must be selected in order to perform "
       
   998                         "actions on them. No items have been changed.")
       
   999                 self.message_user(request, msg)
       
  1000                 action_failed = True
       
  1001 
       
  1002         # Actions with confirmation
       
  1003         if (actions and request.method == 'POST' and
       
  1004                 helpers.ACTION_CHECKBOX_NAME in request.POST and
       
  1005                 'index' not in request.POST and '_save' not in request.POST):
       
  1006             if selected:
       
  1007                 response = self.response_action(request, queryset=cl.get_query_set())
       
  1008                 if response:
       
  1009                     return response
       
  1010                 else:
       
  1011                     action_failed = True
       
  1012 
       
  1013         # If we're allowing changelist editing, we need to construct a formset
       
  1014         # for the changelist given all the fields to be edited. Then we'll
       
  1015         # use the formset to validate/process POSTed data.
       
  1016         formset = cl.formset = None
       
  1017 
       
  1018         # Handle POSTed bulk-edit data.
       
  1019         if (request.method == "POST" and self.list_editable and
       
  1020                 '_save' in request.POST and not action_failed):
       
  1021             FormSet = self.get_changelist_formset(request)
       
  1022             formset = cl.formset = FormSet(request.POST, request.FILES, queryset=cl.result_list)
       
  1023             if formset.is_valid():
       
  1024                 changecount = 0
       
  1025                 for form in formset.forms:
       
  1026                     if form.has_changed():
       
  1027                         obj = self.save_form(request, form, change=True)
       
  1028                         self.save_model(request, obj, form, change=True)
       
  1029                         form.save_m2m()
       
  1030                         change_msg = self.construct_change_message(request, form, None)
       
  1031                         self.log_change(request, obj, change_msg)
       
  1032                         changecount += 1
       
  1033 
       
  1034                 if changecount:
       
  1035                     if changecount == 1:
       
  1036                         name = force_unicode(opts.verbose_name)
       
  1037                     else:
       
  1038                         name = force_unicode(opts.verbose_name_plural)
       
  1039                     msg = ungettext("%(count)s %(name)s was changed successfully.",
       
  1040                                     "%(count)s %(name)s were changed successfully.",
       
  1041                                     changecount) % {'count': changecount,
       
  1042                                                     'name': name,
       
  1043                                                     'obj': force_unicode(obj)}
       
  1044                     self.message_user(request, msg)
       
  1045 
       
  1046                 return HttpResponseRedirect(request.get_full_path())
       
  1047 
       
  1048         # Handle GET -- construct a formset for display.
       
  1049         elif self.list_editable:
       
  1050             FormSet = self.get_changelist_formset(request)
       
  1051             formset = cl.formset = FormSet(queryset=cl.result_list)
       
  1052 
       
  1053         # Build the list of media to be used by the formset.
       
  1054         if formset:
       
  1055             media = self.media + formset.media
       
  1056         else:
       
  1057             media = self.media
       
  1058 
       
  1059         # Build the action form and populate it with available actions.
       
  1060         if actions:
       
  1061             action_form = self.action_form(auto_id=None)
       
  1062             action_form.fields['action'].choices = self.get_action_choices(request)
       
  1063         else:
       
  1064             action_form = None
       
  1065 
       
  1066         selection_note_all = ungettext('%(total_count)s selected',
       
  1067             'All %(total_count)s selected', cl.result_count)
       
  1068 
       
  1069         context = {
       
  1070             'module_name': force_unicode(opts.verbose_name_plural),
       
  1071             'selection_note': _('0 of %(cnt)s selected') % {'cnt': len(cl.result_list)},
       
  1072             'selection_note_all': selection_note_all % {'total_count': cl.result_count},
       
  1073             'title': cl.title,
       
  1074             'is_popup': cl.is_popup,
       
  1075             'cl': cl,
       
  1076             'media': media,
       
  1077             'has_add_permission': self.has_add_permission(request),
       
  1078             'root_path': self.admin_site.root_path,
       
  1079             'app_label': app_label,
       
  1080             'action_form': action_form,
       
  1081             'actions_on_top': self.actions_on_top,
       
  1082             'actions_on_bottom': self.actions_on_bottom,
       
  1083             'actions_selection_counter': self.actions_selection_counter,
       
  1084         }
       
  1085         context.update(extra_context or {})
       
  1086         context_instance = template.RequestContext(request, current_app=self.admin_site.name)
       
  1087         return render_to_response(self.change_list_template or [
       
  1088             'admin/%s/%s/change_list.html' % (app_label, opts.object_name.lower()),
       
  1089             'admin/%s/change_list.html' % app_label,
       
  1090             'admin/change_list.html'
       
  1091         ], context, context_instance=context_instance)
       
  1092 
       
  1093     @csrf_protect_m
       
  1094     def delete_view(self, request, object_id, extra_context=None):
       
  1095         "The 'delete' admin view for this model."
       
  1096         opts = self.model._meta
       
  1097         app_label = opts.app_label
       
  1098 
       
  1099         obj = self.get_object(request, unquote(object_id))
       
  1100 
       
  1101         if not self.has_delete_permission(request, obj):
       
  1102             raise PermissionDenied
       
  1103 
       
  1104         if obj is None:
       
  1105             raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_unicode(opts.verbose_name), 'key': escape(object_id)})
       
  1106 
       
  1107         # Populate deleted_objects, a data structure of all related objects that
       
  1108         # will also be deleted.
       
  1109         (deleted_objects, perms_needed) = get_deleted_objects((obj,), opts, request.user, self.admin_site)
       
  1110 
       
  1111         if request.POST: # The user has already confirmed the deletion.
       
  1112             if perms_needed:
       
  1113                 raise PermissionDenied
       
  1114             obj_display = force_unicode(obj)
       
  1115             self.log_deletion(request, obj, obj_display)
       
  1116             obj.delete()
       
  1117 
       
  1118             self.message_user(request, _('The %(name)s "%(obj)s" was deleted successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj_display)})
       
  1119 
       
  1120             if not self.has_change_permission(request, None):
       
  1121                 return HttpResponseRedirect("../../../../")
       
  1122             return HttpResponseRedirect("../../")
       
  1123 
       
  1124         context = {
       
  1125             "title": _("Are you sure?"),
       
  1126             "object_name": force_unicode(opts.verbose_name),
       
  1127             "object": obj,
       
  1128             "deleted_objects": deleted_objects,
       
  1129             "perms_lacking": perms_needed,
       
  1130             "opts": opts,
       
  1131             "root_path": self.admin_site.root_path,
       
  1132             "app_label": app_label,
       
  1133         }
       
  1134         context.update(extra_context or {})
       
  1135         context_instance = template.RequestContext(request, current_app=self.admin_site.name)
       
  1136         return render_to_response(self.delete_confirmation_template or [
       
  1137             "admin/%s/%s/delete_confirmation.html" % (app_label, opts.object_name.lower()),
       
  1138             "admin/%s/delete_confirmation.html" % app_label,
       
  1139             "admin/delete_confirmation.html"
       
  1140         ], context, context_instance=context_instance)
       
  1141 
       
  1142     def history_view(self, request, object_id, extra_context=None):
       
  1143         "The 'history' admin view for this model."
       
  1144         from django.contrib.admin.models import LogEntry
       
  1145         model = self.model
       
  1146         opts = model._meta
       
  1147         app_label = opts.app_label
       
  1148         action_list = LogEntry.objects.filter(
       
  1149             object_id = object_id,
       
  1150             content_type__id__exact = ContentType.objects.get_for_model(model).id
       
  1151         ).select_related().order_by('action_time')
       
  1152         # If no history was found, see whether this object even exists.
       
  1153         obj = get_object_or_404(model, pk=unquote(object_id))
       
  1154         context = {
       
  1155             'title': _('Change history: %s') % force_unicode(obj),
       
  1156             'action_list': action_list,
       
  1157             'module_name': capfirst(force_unicode(opts.verbose_name_plural)),
       
  1158             'object': obj,
       
  1159             'root_path': self.admin_site.root_path,
       
  1160             'app_label': app_label,
       
  1161         }
       
  1162         context.update(extra_context or {})
       
  1163         context_instance = template.RequestContext(request, current_app=self.admin_site.name)
       
  1164         return render_to_response(self.object_history_template or [
       
  1165             "admin/%s/%s/object_history.html" % (app_label, opts.object_name.lower()),
       
  1166             "admin/%s/object_history.html" % app_label,
       
  1167             "admin/object_history.html"
       
  1168         ], context, context_instance=context_instance)
       
  1169 
       
  1170     #
       
  1171     # DEPRECATED methods.
       
  1172     #
       
  1173     def __call__(self, request, url):
       
  1174         """
       
  1175         DEPRECATED: this is the old way of URL resolution, replaced by
       
  1176         ``get_urls()``. This only called by AdminSite.root(), which is also
       
  1177         deprecated.
       
  1178 
       
  1179         Again, remember that the following code only exists for
       
  1180         backwards-compatibility. Any new URLs, changes to existing URLs, or
       
  1181         whatever need to be done up in get_urls(), above!
       
  1182 
       
  1183         This function still exists for backwards-compatibility; it will be
       
  1184         removed in Django 1.3.
       
  1185         """
       
  1186         # Delegate to the appropriate method, based on the URL.
       
  1187         if url is None:
       
  1188             return self.changelist_view(request)
       
  1189         elif url == "add":
       
  1190             return self.add_view(request)
       
  1191         elif url.endswith('/history'):
       
  1192             return self.history_view(request, unquote(url[:-8]))
       
  1193         elif url.endswith('/delete'):
       
  1194             return self.delete_view(request, unquote(url[:-7]))
       
  1195         else:
       
  1196             return self.change_view(request, unquote(url))
       
  1197 
       
  1198 class InlineModelAdmin(BaseModelAdmin):
       
  1199     """
       
  1200     Options for inline editing of ``model`` instances.
       
  1201 
       
  1202     Provide ``name`` to specify the attribute name of the ``ForeignKey`` from
       
  1203     ``model`` to its parent. This is required if ``model`` has more than one
       
  1204     ``ForeignKey`` to its parent.
       
  1205     """
       
  1206     model = None
       
  1207     fk_name = None
       
  1208     formset = BaseInlineFormSet
       
  1209     extra = 3
       
  1210     max_num = None
       
  1211     template = None
       
  1212     verbose_name = None
       
  1213     verbose_name_plural = None
       
  1214     can_delete = True
       
  1215 
       
  1216     def __init__(self, parent_model, admin_site):
       
  1217         self.admin_site = admin_site
       
  1218         self.parent_model = parent_model
       
  1219         self.opts = self.model._meta
       
  1220         super(InlineModelAdmin, self).__init__()
       
  1221         if self.verbose_name is None:
       
  1222             self.verbose_name = self.model._meta.verbose_name
       
  1223         if self.verbose_name_plural is None:
       
  1224             self.verbose_name_plural = self.model._meta.verbose_name_plural
       
  1225 
       
  1226     def _media(self):
       
  1227         from django.conf import settings
       
  1228         js = ['js/jquery.min.js', 'js/jquery.init.js', 'js/inlines.min.js']
       
  1229         if self.prepopulated_fields:
       
  1230             js.append('js/urlify.js')
       
  1231             js.append('js/prepopulate.min.js')
       
  1232         if self.filter_vertical or self.filter_horizontal:
       
  1233             js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js'])
       
  1234         return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js])
       
  1235     media = property(_media)
       
  1236 
       
  1237     def get_formset(self, request, obj=None, **kwargs):
       
  1238         """Returns a BaseInlineFormSet class for use in admin add/change views."""
       
  1239         if self.declared_fieldsets:
       
  1240             fields = flatten_fieldsets(self.declared_fieldsets)
       
  1241         else:
       
  1242             fields = None
       
  1243         if self.exclude is None:
       
  1244             exclude = []
       
  1245         else:
       
  1246             exclude = list(self.exclude)
       
  1247         exclude.extend(kwargs.get("exclude", []))
       
  1248         exclude.extend(self.get_readonly_fields(request, obj))
       
  1249         # if exclude is an empty list we use None, since that's the actual
       
  1250         # default
       
  1251         exclude = exclude or None
       
  1252         defaults = {
       
  1253             "form": self.form,
       
  1254             "formset": self.formset,
       
  1255             "fk_name": self.fk_name,
       
  1256             "fields": fields,
       
  1257             "exclude": exclude,
       
  1258             "formfield_callback": curry(self.formfield_for_dbfield, request=request),
       
  1259             "extra": self.extra,
       
  1260             "max_num": self.max_num,
       
  1261             "can_delete": self.can_delete,
       
  1262         }
       
  1263         defaults.update(kwargs)
       
  1264         return inlineformset_factory(self.parent_model, self.model, **defaults)
       
  1265 
       
  1266     def get_fieldsets(self, request, obj=None):
       
  1267         if self.declared_fieldsets:
       
  1268             return self.declared_fieldsets
       
  1269         form = self.get_formset(request).form
       
  1270         fields = form.base_fields.keys() + list(self.get_readonly_fields(request, obj))
       
  1271         return [(None, {'fields': fields})]
       
  1272 
       
  1273     def queryset(self, request):
       
  1274         return self.model._default_manager.all()
       
  1275 
       
  1276 class StackedInline(InlineModelAdmin):
       
  1277     template = 'admin/edit_inline/stacked.html'
       
  1278 
       
  1279 class TabularInline(InlineModelAdmin):
       
  1280     template = 'admin/edit_inline/tabular.html'