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