app/django/contrib/admin/options.py
changeset 323 ff1a9aa48cfd
equal deleted inserted replaced
322:6641e941ef1e 323:ff1a9aa48cfd
       
     1 from django import forms, template
       
     2 from django.forms.formsets import all_valid
       
     3 from django.forms.models import modelform_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 quote, unquote, flatten_fieldsets, get_deleted_objects
       
     9 from django.core.exceptions import PermissionDenied
       
    10 from django.db import models, transaction
       
    11 from django.http import Http404, HttpResponse, HttpResponseRedirect
       
    12 from django.shortcuts import get_object_or_404, render_to_response
       
    13 from django.utils.html import escape
       
    14 from django.utils.safestring import mark_safe
       
    15 from django.utils.text import capfirst, get_text_list
       
    16 from django.utils.translation import ugettext as _
       
    17 from django.utils.encoding import force_unicode
       
    18 try:
       
    19     set
       
    20 except NameError:
       
    21     from sets import Set as set     # Python 2.3 fallback
       
    22 
       
    23 HORIZONTAL, VERTICAL = 1, 2
       
    24 # returns the <ul> class for a given radio_admin field
       
    25 get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '')
       
    26 
       
    27 class IncorrectLookupParameters(Exception):
       
    28     pass
       
    29 
       
    30 class BaseModelAdmin(object):
       
    31     """Functionality common to both ModelAdmin and InlineAdmin."""
       
    32     raw_id_fields = ()
       
    33     fields = None
       
    34     exclude = None
       
    35     fieldsets = None
       
    36     form = forms.ModelForm
       
    37     filter_vertical = ()
       
    38     filter_horizontal = ()
       
    39     radio_fields = {}
       
    40     prepopulated_fields = {}
       
    41 
       
    42     def formfield_for_dbfield(self, db_field, **kwargs):
       
    43         """
       
    44         Hook for specifying the form Field instance for a given database Field
       
    45         instance.
       
    46 
       
    47         If kwargs are given, they're passed to the form Field's constructor.
       
    48         """
       
    49         
       
    50         # If the field specifies choices, we don't need to look for special
       
    51         # admin widgets - we just need to use a select widget of some kind.
       
    52         if db_field.choices:
       
    53             if db_field.name in self.radio_fields:
       
    54                 # If the field is named as a radio_field, use a RadioSelect
       
    55                 kwargs['widget'] = widgets.AdminRadioSelect(attrs={
       
    56                     'class': get_ul_class(self.radio_fields[db_field.name]),
       
    57                 })
       
    58                 kwargs['choices'] = db_field.get_choices(
       
    59                     include_blank = db_field.blank,
       
    60                     blank_choice=[('', _('None'))]
       
    61                 )
       
    62                 return db_field.formfield(**kwargs)
       
    63             else:
       
    64                 # Otherwise, use the default select widget.
       
    65                 return db_field.formfield(**kwargs)
       
    66 
       
    67         # For DateTimeFields, use a special field and widget.
       
    68         if isinstance(db_field, models.DateTimeField):
       
    69             kwargs['form_class'] = forms.SplitDateTimeField
       
    70             kwargs['widget'] = widgets.AdminSplitDateTime()
       
    71             return db_field.formfield(**kwargs)
       
    72 
       
    73         # For DateFields, add a custom CSS class.
       
    74         if isinstance(db_field, models.DateField):
       
    75             kwargs['widget'] = widgets.AdminDateWidget
       
    76             return db_field.formfield(**kwargs)
       
    77 
       
    78         # For TimeFields, add a custom CSS class.
       
    79         if isinstance(db_field, models.TimeField):
       
    80             kwargs['widget'] = widgets.AdminTimeWidget
       
    81             return db_field.formfield(**kwargs)
       
    82         
       
    83         # For TextFields, add a custom CSS class.
       
    84         if isinstance(db_field, models.TextField):
       
    85             kwargs['widget'] = widgets.AdminTextareaWidget
       
    86             return db_field.formfield(**kwargs)
       
    87         
       
    88         # For URLFields, add a custom CSS class.
       
    89         if isinstance(db_field, models.URLField):
       
    90             kwargs['widget'] = widgets.AdminURLFieldWidget
       
    91             return db_field.formfield(**kwargs)
       
    92         
       
    93         # For IntegerFields, add a custom CSS class.
       
    94         if isinstance(db_field, models.IntegerField):
       
    95             kwargs['widget'] = widgets.AdminIntegerFieldWidget
       
    96             return db_field.formfield(**kwargs)
       
    97 
       
    98         # For CommaSeparatedIntegerFields, add a custom CSS class.
       
    99         if isinstance(db_field, models.CommaSeparatedIntegerField):
       
   100             kwargs['widget'] = widgets.AdminCommaSeparatedIntegerFieldWidget
       
   101             return db_field.formfield(**kwargs)
       
   102 
       
   103         # For TextInputs, add a custom CSS class.
       
   104         if isinstance(db_field, models.CharField):
       
   105             kwargs['widget'] = widgets.AdminTextInputWidget
       
   106             return db_field.formfield(**kwargs)
       
   107     
       
   108         # For FileFields and ImageFields add a link to the current file.
       
   109         if isinstance(db_field, models.ImageField) or isinstance(db_field, models.FileField):
       
   110             kwargs['widget'] = widgets.AdminFileWidget
       
   111             return db_field.formfield(**kwargs)
       
   112 
       
   113         # For ForeignKey or ManyToManyFields, use a special widget.
       
   114         if isinstance(db_field, (models.ForeignKey, models.ManyToManyField)):
       
   115             if isinstance(db_field, models.ForeignKey) and db_field.name in self.raw_id_fields:
       
   116                 kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel)
       
   117             elif isinstance(db_field, models.ForeignKey) and db_field.name in self.radio_fields:
       
   118                 kwargs['widget'] = widgets.AdminRadioSelect(attrs={
       
   119                     'class': get_ul_class(self.radio_fields[db_field.name]),
       
   120                 })
       
   121                 kwargs['empty_label'] = db_field.blank and _('None') or None
       
   122             else:
       
   123                 if isinstance(db_field, models.ManyToManyField):
       
   124                     # If it uses an intermediary model, don't show field in admin.
       
   125                     if db_field.rel.through is not None:
       
   126                         return None
       
   127                     elif db_field.name in self.raw_id_fields:
       
   128                         kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel)
       
   129                         kwargs['help_text'] = ''
       
   130                     elif db_field.name in (list(self.filter_vertical) + list(self.filter_horizontal)):
       
   131                         kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, (db_field.name in self.filter_vertical))
       
   132             # Wrap the widget's render() method with a method that adds
       
   133             # extra HTML to the end of the rendered output.
       
   134             formfield = db_field.formfield(**kwargs)
       
   135             # Don't wrap raw_id fields. Their add function is in the popup window.
       
   136             if not db_field.name in self.raw_id_fields:
       
   137                 # formfield can be None if it came from a OneToOneField with
       
   138                 # parent_link=True
       
   139                 if formfield is not None:
       
   140                     formfield.widget = widgets.RelatedFieldWidgetWrapper(formfield.widget, db_field.rel, self.admin_site)
       
   141             return formfield
       
   142 
       
   143         # For any other type of field, just call its formfield() method.
       
   144         return db_field.formfield(**kwargs)
       
   145 
       
   146     def _declared_fieldsets(self):
       
   147         if self.fieldsets:
       
   148             return self.fieldsets
       
   149         elif self.fields:
       
   150             return [(None, {'fields': self.fields})]
       
   151         return None
       
   152     declared_fieldsets = property(_declared_fieldsets)
       
   153 
       
   154 class ModelAdmin(BaseModelAdmin):
       
   155     "Encapsulates all admin options and functionality for a given model."
       
   156     __metaclass__ = forms.MediaDefiningClass
       
   157 
       
   158     list_display = ('__str__',)
       
   159     list_display_links = ()
       
   160     list_filter = ()
       
   161     list_select_related = False
       
   162     list_per_page = 100
       
   163     search_fields = ()
       
   164     date_hierarchy = None
       
   165     save_as = False
       
   166     save_on_top = False
       
   167     ordering = None
       
   168     inlines = []
       
   169 
       
   170     # Custom templates (designed to be over-ridden in subclasses)
       
   171     change_form_template = None
       
   172     change_list_template = None
       
   173     delete_confirmation_template = None
       
   174     object_history_template = None
       
   175 
       
   176     def __init__(self, model, admin_site):
       
   177         self.model = model
       
   178         self.opts = model._meta
       
   179         self.admin_site = admin_site
       
   180         self.inline_instances = []
       
   181         for inline_class in self.inlines:
       
   182             inline_instance = inline_class(self.model, self.admin_site)
       
   183             self.inline_instances.append(inline_instance)
       
   184         super(ModelAdmin, self).__init__()
       
   185 
       
   186     def __call__(self, request, url):
       
   187         # Delegate to the appropriate method, based on the URL.
       
   188         if url is None:
       
   189             return self.changelist_view(request)
       
   190         elif url == "add":
       
   191             return self.add_view(request)
       
   192         elif url.endswith('/history'):
       
   193             return self.history_view(request, unquote(url[:-8]))
       
   194         elif url.endswith('/delete'):
       
   195             return self.delete_view(request, unquote(url[:-7]))
       
   196         else:
       
   197             return self.change_view(request, unquote(url))
       
   198 
       
   199     def _media(self):
       
   200         from django.conf import settings
       
   201 
       
   202         js = ['js/core.js', 'js/admin/RelatedObjectLookups.js']
       
   203         if self.prepopulated_fields:
       
   204             js.append('js/urlify.js')
       
   205         if self.opts.get_ordered_objects():
       
   206             js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.js'])
       
   207 
       
   208         return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js])
       
   209     media = property(_media)
       
   210 
       
   211     def has_add_permission(self, request):
       
   212         "Returns True if the given request has permission to add an object."
       
   213         opts = self.opts
       
   214         return request.user.has_perm(opts.app_label + '.' + opts.get_add_permission())
       
   215 
       
   216     def has_change_permission(self, request, obj=None):
       
   217         """
       
   218         Returns True if the given request has permission to change the given
       
   219         Django model instance.
       
   220 
       
   221         If `obj` is None, this should return True if the given request has
       
   222         permission to change *any* object of the given type.
       
   223         """
       
   224         opts = self.opts
       
   225         return request.user.has_perm(opts.app_label + '.' + opts.get_change_permission())
       
   226 
       
   227     def has_delete_permission(self, request, obj=None):
       
   228         """
       
   229         Returns True if the given request has permission to change the given
       
   230         Django model instance.
       
   231 
       
   232         If `obj` is None, this should return True if the given request has
       
   233         permission to delete *any* object of the given type.
       
   234         """
       
   235         opts = self.opts
       
   236         return request.user.has_perm(opts.app_label + '.' + opts.get_delete_permission())
       
   237 
       
   238     def queryset(self, request):
       
   239         """
       
   240         Returns a QuerySet of all model instances that can be edited by the
       
   241         admin site. This is used by changelist_view.
       
   242         """
       
   243         qs = self.model._default_manager.get_query_set()
       
   244         # TODO: this should be handled by some parameter to the ChangeList.
       
   245         ordering = self.ordering or () # otherwise we might try to *None, which is bad ;)
       
   246         if ordering:
       
   247             qs = qs.order_by(*ordering)
       
   248         return qs
       
   249 
       
   250     def get_fieldsets(self, request, obj=None):
       
   251         "Hook for specifying fieldsets for the add form."
       
   252         if self.declared_fieldsets:
       
   253             return self.declared_fieldsets
       
   254         form = self.get_form(request, obj)
       
   255         return [(None, {'fields': form.base_fields.keys()})]
       
   256 
       
   257     def get_form(self, request, obj=None, **kwargs):
       
   258         """
       
   259         Returns a Form class for use in the admin add view. This is used by
       
   260         add_view and change_view.
       
   261         """
       
   262         if self.declared_fieldsets:
       
   263             fields = flatten_fieldsets(self.declared_fieldsets)
       
   264         else:
       
   265             fields = None
       
   266         if self.exclude is None:
       
   267             exclude = []
       
   268         else:
       
   269             exclude = list(self.exclude)
       
   270         defaults = {
       
   271             "form": self.form,
       
   272             "fields": fields,
       
   273             "exclude": exclude + kwargs.get("exclude", []),
       
   274             "formfield_callback": self.formfield_for_dbfield,
       
   275         }
       
   276         defaults.update(kwargs)
       
   277         return modelform_factory(self.model, **defaults)
       
   278 
       
   279     def get_formsets(self, request, obj=None):
       
   280         for inline in self.inline_instances:
       
   281             yield inline.get_formset(request, obj)
       
   282             
       
   283     def log_addition(self, request, object):
       
   284         """
       
   285         Log that an object has been successfully added. 
       
   286         
       
   287         The default implementation creates an admin LogEntry object.
       
   288         """
       
   289         from django.contrib.admin.models import LogEntry, ADDITION
       
   290         LogEntry.objects.log_action(
       
   291             user_id         = request.user.pk, 
       
   292             content_type_id = ContentType.objects.get_for_model(object).pk,
       
   293             object_id       = object.pk,
       
   294             object_repr     = force_unicode(object), 
       
   295             action_flag     = ADDITION
       
   296         )
       
   297         
       
   298     def log_change(self, request, object, message):
       
   299         """
       
   300         Log that an object has been successfully changed. 
       
   301         
       
   302         The default implementation creates an admin LogEntry object.
       
   303         """
       
   304         from django.contrib.admin.models import LogEntry, CHANGE
       
   305         LogEntry.objects.log_action(
       
   306             user_id         = request.user.pk, 
       
   307             content_type_id = ContentType.objects.get_for_model(object).pk, 
       
   308             object_id       = object.pk, 
       
   309             object_repr     = force_unicode(object), 
       
   310             action_flag     = CHANGE, 
       
   311             change_message  = message
       
   312         )
       
   313         
       
   314     def log_deletion(self, request, object, object_repr):
       
   315         """
       
   316         Log that an object has been successfully deleted. Note that since the
       
   317         object is deleted, it might no longer be safe to call *any* methods
       
   318         on the object, hence this method getting object_repr.
       
   319         
       
   320         The default implementation creates an admin LogEntry object.
       
   321         """
       
   322         from django.contrib.admin.models import LogEntry, DELETION
       
   323         LogEntry.objects.log_action(
       
   324             user_id         = request.user.id, 
       
   325             content_type_id = ContentType.objects.get_for_model(self.model).pk, 
       
   326             object_id       = object.pk, 
       
   327             object_repr     = object_repr,
       
   328             action_flag     = DELETION
       
   329         )
       
   330         
       
   331     
       
   332     def construct_change_message(self, request, form, formsets):
       
   333         """
       
   334         Construct a change message from a changed object.
       
   335         """
       
   336         change_message = []
       
   337         if form.changed_data:
       
   338             change_message.append(_('Changed %s.') % get_text_list(form.changed_data, _('and')))
       
   339 
       
   340         if formsets:
       
   341             for formset in formsets:
       
   342                 for added_object in formset.new_objects:
       
   343                     change_message.append(_('Added %(name)s "%(object)s".')
       
   344                                           % {'name': added_object._meta.verbose_name,
       
   345                                              'object': added_object})
       
   346                 for changed_object, changed_fields in formset.changed_objects:
       
   347                     change_message.append(_('Changed %(list)s for %(name)s "%(object)s".')
       
   348                                           % {'list': get_text_list(changed_fields, _('and')),
       
   349                                              'name': changed_object._meta.verbose_name,
       
   350                                              'object': changed_object})
       
   351                 for deleted_object in formset.deleted_objects:
       
   352                     change_message.append(_('Deleted %(name)s "%(object)s".')
       
   353                                           % {'name': deleted_object._meta.verbose_name,
       
   354                                              'object': deleted_object})
       
   355         change_message = ' '.join(change_message)
       
   356         return change_message or _('No fields changed.')
       
   357     
       
   358     def message_user(self, request, message):
       
   359         """
       
   360         Send a message to the user. The default implementation 
       
   361         posts a message using the auth Message object.
       
   362         """
       
   363         request.user.message_set.create(message=message)
       
   364 
       
   365     def save_form(self, request, form, change):
       
   366         """
       
   367         Given a ModelForm return an unsaved instance. ``change`` is True if
       
   368         the object is being changed, and False if it's being added.
       
   369         """
       
   370         return form.save(commit=False)
       
   371     
       
   372     def save_model(self, request, obj, form, change):
       
   373         """
       
   374         Given a model instance save it to the database.
       
   375         """
       
   376         obj.save()
       
   377 
       
   378     def save_formset(self, request, form, formset, change):
       
   379         """
       
   380         Given an inline formset save it to the database.
       
   381         """
       
   382         formset.save()
       
   383 
       
   384     def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
       
   385         opts = self.model._meta
       
   386         app_label = opts.app_label
       
   387         ordered_objects = opts.get_ordered_objects()
       
   388         context.update({
       
   389             'add': add,
       
   390             'change': change,
       
   391             'has_add_permission': self.has_add_permission(request),
       
   392             'has_change_permission': self.has_change_permission(request, obj),
       
   393             'has_delete_permission': self.has_delete_permission(request, obj),
       
   394             'has_file_field': True, # FIXME - this should check if form or formsets have a FileField,
       
   395             'has_absolute_url': hasattr(self.model, 'get_absolute_url'),
       
   396             'ordered_objects': ordered_objects,
       
   397             'form_url': mark_safe(form_url),
       
   398             'opts': opts,
       
   399             'content_type_id': ContentType.objects.get_for_model(self.model).id,
       
   400             'save_as': self.save_as,
       
   401             'save_on_top': self.save_on_top,
       
   402             'root_path': self.admin_site.root_path,
       
   403         })
       
   404         return render_to_response(self.change_form_template or [
       
   405             "admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()),
       
   406             "admin/%s/change_form.html" % app_label,
       
   407             "admin/change_form.html"
       
   408         ], context, context_instance=template.RequestContext(request))
       
   409     
       
   410     def response_add(self, request, obj, post_url_continue='../%s/'):
       
   411         """
       
   412         Determines the HttpResponse for the add_view stage.
       
   413         """
       
   414         opts = obj._meta
       
   415         pk_value = obj._get_pk_val()
       
   416         
       
   417         msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj)}
       
   418         # Here, we distinguish between different save types by checking for
       
   419         # the presence of keys in request.POST.
       
   420         if request.POST.has_key("_continue"):
       
   421             self.message_user(request, msg + ' ' + _("You may edit it again below."))
       
   422             if request.POST.has_key("_popup"):
       
   423                 post_url_continue += "?_popup=1"
       
   424             return HttpResponseRedirect(post_url_continue % pk_value)
       
   425         
       
   426         if request.POST.has_key("_popup"):
       
   427             return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script>' % \
       
   428                 # escape() calls force_unicode.
       
   429                 (escape(pk_value), escape(obj)))
       
   430         elif request.POST.has_key("_addanother"):
       
   431             self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name)))
       
   432             return HttpResponseRedirect(request.path)
       
   433         else:
       
   434             self.message_user(request, msg)
       
   435 
       
   436             # Figure out where to redirect. If the user has change permission,
       
   437             # redirect to the change-list page for this object. Otherwise,
       
   438             # redirect to the admin index.
       
   439             if self.has_change_permission(request, None):
       
   440                 post_url = '../'
       
   441             else:
       
   442                 post_url = '../../../'
       
   443             return HttpResponseRedirect(post_url)
       
   444     
       
   445     def response_change(self, request, obj):
       
   446         """
       
   447         Determines the HttpResponse for the change_view stage.
       
   448         """
       
   449         opts = obj._meta
       
   450         pk_value = obj._get_pk_val()
       
   451         
       
   452         msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj)}
       
   453         if request.POST.has_key("_continue"):
       
   454             self.message_user(request, msg + ' ' + _("You may edit it again below."))
       
   455             if request.REQUEST.has_key('_popup'):
       
   456                 return HttpResponseRedirect(request.path + "?_popup=1")
       
   457             else:
       
   458                 return HttpResponseRedirect(request.path)
       
   459         elif request.POST.has_key("_saveasnew"):
       
   460             msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': force_unicode(opts.verbose_name), 'obj': obj}
       
   461             self.message_user(request, msg)
       
   462             return HttpResponseRedirect("../%s/" % pk_value)
       
   463         elif request.POST.has_key("_addanother"):
       
   464             self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name)))
       
   465             return HttpResponseRedirect("../add/")
       
   466         else:
       
   467             self.message_user(request, msg)
       
   468             return HttpResponseRedirect("../")
       
   469 
       
   470     def add_view(self, request, form_url='', extra_context=None):
       
   471         "The 'add' admin view for this model."
       
   472         model = self.model
       
   473         opts = model._meta
       
   474 
       
   475         if not self.has_add_permission(request):
       
   476             raise PermissionDenied
       
   477 
       
   478         ModelForm = self.get_form(request)
       
   479         formsets = []
       
   480         if request.method == 'POST':
       
   481             form = ModelForm(request.POST, request.FILES)
       
   482             if form.is_valid():
       
   483                 form_validated = True
       
   484                 new_object = self.save_form(request, form, change=False)
       
   485             else:
       
   486                 form_validated = False
       
   487                 new_object = self.model()
       
   488             for FormSet in self.get_formsets(request):
       
   489                 formset = FormSet(data=request.POST, files=request.FILES,
       
   490                                   instance=new_object,
       
   491                                   save_as_new=request.POST.has_key("_saveasnew"))
       
   492                 formsets.append(formset)
       
   493             if all_valid(formsets) and form_validated:
       
   494                 self.save_model(request, new_object, form, change=False)
       
   495                 form.save_m2m()
       
   496                 for formset in formsets:
       
   497                     self.save_formset(request, form, formset, change=False)
       
   498                 
       
   499                 self.log_addition(request, new_object)
       
   500                 return self.response_add(request, new_object)
       
   501         else:
       
   502             # Prepare the dict of initial data from the request.
       
   503             # We have to special-case M2Ms as a list of comma-separated PKs.
       
   504             initial = dict(request.GET.items())
       
   505             for k in initial:
       
   506                 try:
       
   507                     f = opts.get_field(k)
       
   508                 except models.FieldDoesNotExist:
       
   509                     continue
       
   510                 if isinstance(f, models.ManyToManyField):
       
   511                     initial[k] = initial[k].split(",")
       
   512             form = ModelForm(initial=initial)
       
   513             for FormSet in self.get_formsets(request):
       
   514                 formset = FormSet(instance=self.model())
       
   515                 formsets.append(formset)
       
   516 
       
   517         adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)), self.prepopulated_fields)
       
   518         media = self.media + adminForm.media
       
   519 
       
   520         inline_admin_formsets = []
       
   521         for inline, formset in zip(self.inline_instances, formsets):
       
   522             fieldsets = list(inline.get_fieldsets(request))
       
   523             inline_admin_formset = helpers.InlineAdminFormSet(inline, formset, fieldsets)
       
   524             inline_admin_formsets.append(inline_admin_formset)
       
   525             media = media + inline_admin_formset.media
       
   526 
       
   527         context = {
       
   528             'title': _('Add %s') % force_unicode(opts.verbose_name),
       
   529             'adminform': adminForm,
       
   530             'is_popup': request.REQUEST.has_key('_popup'),
       
   531             'show_delete': False,
       
   532             'media': mark_safe(media),
       
   533             'inline_admin_formsets': inline_admin_formsets,
       
   534             'errors': helpers.AdminErrorList(form, formsets),
       
   535             'root_path': self.admin_site.root_path,
       
   536             'app_label': opts.app_label,
       
   537         }
       
   538         context.update(extra_context or {})
       
   539         return self.render_change_form(request, context, add=True)
       
   540     add_view = transaction.commit_on_success(add_view)
       
   541 
       
   542     def change_view(self, request, object_id, extra_context=None):
       
   543         "The 'change' admin view for this model."
       
   544         model = self.model
       
   545         opts = model._meta
       
   546 
       
   547         try:
       
   548             obj = model._default_manager.get(pk=object_id)
       
   549         except model.DoesNotExist:
       
   550             # Don't raise Http404 just yet, because we haven't checked
       
   551             # permissions yet. We don't want an unauthenticated user to be able
       
   552             # to determine whether a given object exists.
       
   553             obj = None
       
   554 
       
   555         if not self.has_change_permission(request, obj):
       
   556             raise PermissionDenied
       
   557 
       
   558         if obj is None:
       
   559             raise Http404('%s object with primary key %r does not exist.' % (force_unicode(opts.verbose_name), escape(object_id)))
       
   560 
       
   561         if request.method == 'POST' and request.POST.has_key("_saveasnew"):
       
   562             return self.add_view(request, form_url='../../add/')
       
   563 
       
   564         ModelForm = self.get_form(request, obj)
       
   565         formsets = []
       
   566         if request.method == 'POST':
       
   567             form = ModelForm(request.POST, request.FILES, instance=obj)
       
   568             if form.is_valid():
       
   569                 form_validated = True
       
   570                 new_object = self.save_form(request, form, change=True)
       
   571             else:
       
   572                 form_validated = False
       
   573                 new_object = obj
       
   574             for FormSet in self.get_formsets(request, new_object):
       
   575                 formset = FormSet(request.POST, request.FILES,
       
   576                                   instance=new_object)
       
   577                 formsets.append(formset)
       
   578 
       
   579             if all_valid(formsets) and form_validated:
       
   580                 self.save_model(request, new_object, form, change=True)
       
   581                 form.save_m2m()
       
   582                 for formset in formsets:
       
   583                     self.save_formset(request, form, formset, change=True)
       
   584                 
       
   585                 change_message = self.construct_change_message(request, form, formsets)
       
   586                 self.log_change(request, new_object, change_message)
       
   587                 return self.response_change(request, new_object)
       
   588         else:
       
   589             form = ModelForm(instance=obj)
       
   590             for FormSet in self.get_formsets(request, obj):
       
   591                 formset = FormSet(instance=obj)
       
   592                 formsets.append(formset)
       
   593 
       
   594         adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj), self.prepopulated_fields)
       
   595         media = self.media + adminForm.media
       
   596 
       
   597         inline_admin_formsets = []
       
   598         for inline, formset in zip(self.inline_instances, formsets):
       
   599             fieldsets = list(inline.get_fieldsets(request, obj))
       
   600             inline_admin_formset = helpers.InlineAdminFormSet(inline, formset, fieldsets)
       
   601             inline_admin_formsets.append(inline_admin_formset)
       
   602             media = media + inline_admin_formset.media
       
   603 
       
   604         context = {
       
   605             'title': _('Change %s') % force_unicode(opts.verbose_name),
       
   606             'adminform': adminForm,
       
   607             'object_id': object_id,
       
   608             'original': obj,
       
   609             'is_popup': request.REQUEST.has_key('_popup'),
       
   610             'media': mark_safe(media),
       
   611             'inline_admin_formsets': inline_admin_formsets,
       
   612             'errors': helpers.AdminErrorList(form, formsets),
       
   613             'root_path': self.admin_site.root_path,
       
   614             'app_label': opts.app_label,
       
   615         }
       
   616         context.update(extra_context or {})
       
   617         return self.render_change_form(request, context, change=True, obj=obj)
       
   618     change_view = transaction.commit_on_success(change_view)
       
   619 
       
   620     def changelist_view(self, request, extra_context=None):
       
   621         "The 'change list' admin view for this model."
       
   622         from django.contrib.admin.views.main import ChangeList, ERROR_FLAG
       
   623         opts = self.model._meta
       
   624         app_label = opts.app_label
       
   625         if not self.has_change_permission(request, None):
       
   626             raise PermissionDenied
       
   627         try:
       
   628             cl = ChangeList(request, self.model, self.list_display, self.list_display_links, self.list_filter,
       
   629                 self.date_hierarchy, self.search_fields, self.list_select_related, self.list_per_page, self)
       
   630         except IncorrectLookupParameters:
       
   631             # Wacky lookup parameters were given, so redirect to the main
       
   632             # changelist page, without parameters, and pass an 'invalid=1'
       
   633             # parameter via the query string. If wacky parameters were given and
       
   634             # the 'invalid=1' parameter was already in the query string, something
       
   635             # is screwed up with the database, so display an error page.
       
   636             if ERROR_FLAG in request.GET.keys():
       
   637                 return render_to_response('admin/invalid_setup.html', {'title': _('Database error')})
       
   638             return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1')
       
   639 
       
   640         context = {
       
   641             'title': cl.title,
       
   642             'is_popup': cl.is_popup,
       
   643             'cl': cl,
       
   644             'has_add_permission': self.has_add_permission(request),
       
   645             'root_path': self.admin_site.root_path,
       
   646             'app_label': app_label,
       
   647         }
       
   648         context.update(extra_context or {})
       
   649         return render_to_response(self.change_list_template or [
       
   650             'admin/%s/%s/change_list.html' % (app_label, opts.object_name.lower()),
       
   651             'admin/%s/change_list.html' % app_label,
       
   652             'admin/change_list.html'
       
   653         ], context, context_instance=template.RequestContext(request))
       
   654 
       
   655     def delete_view(self, request, object_id, extra_context=None):
       
   656         "The 'delete' admin view for this model."
       
   657         opts = self.model._meta
       
   658         app_label = opts.app_label
       
   659 
       
   660         try:
       
   661             obj = self.model._default_manager.get(pk=object_id)
       
   662         except self.model.DoesNotExist:
       
   663             # Don't raise Http404 just yet, because we haven't checked
       
   664             # permissions yet. We don't want an unauthenticated user to be able
       
   665             # to determine whether a given object exists.
       
   666             obj = None
       
   667 
       
   668         if not self.has_delete_permission(request, obj):
       
   669             raise PermissionDenied
       
   670 
       
   671         if obj is None:
       
   672             raise Http404('%s object with primary key %r does not exist.' % (force_unicode(opts.verbose_name), escape(object_id)))
       
   673 
       
   674         # Populate deleted_objects, a data structure of all related objects that
       
   675         # will also be deleted.
       
   676         deleted_objects = [mark_safe(u'%s: <a href="../../%s/">%s</a>' % (escape(force_unicode(capfirst(opts.verbose_name))), quote(object_id), escape(obj))), []]
       
   677         perms_needed = set()
       
   678         get_deleted_objects(deleted_objects, perms_needed, request.user, obj, opts, 1, self.admin_site)
       
   679 
       
   680         if request.POST: # The user has already confirmed the deletion.
       
   681             if perms_needed:
       
   682                 raise PermissionDenied
       
   683             obj_display = force_unicode(obj)
       
   684             obj.delete()
       
   685             
       
   686             self.log_deletion(request, obj, obj_display)
       
   687             self.message_user(request, _('The %(name)s "%(obj)s" was deleted successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj_display)})
       
   688             
       
   689             if not self.has_change_permission(request, None):
       
   690                 return HttpResponseRedirect("../../../../")
       
   691             return HttpResponseRedirect("../../")
       
   692 
       
   693         context = {
       
   694             "title": _("Are you sure?"),
       
   695             "object_name": force_unicode(opts.verbose_name),
       
   696             "object": obj,
       
   697             "deleted_objects": deleted_objects,
       
   698             "perms_lacking": perms_needed,
       
   699             "opts": opts,
       
   700             "root_path": self.admin_site.root_path,
       
   701             "app_label": app_label,
       
   702         }
       
   703         context.update(extra_context or {})
       
   704         return render_to_response(self.delete_confirmation_template or [
       
   705             "admin/%s/%s/delete_confirmation.html" % (app_label, opts.object_name.lower()),
       
   706             "admin/%s/delete_confirmation.html" % app_label,
       
   707             "admin/delete_confirmation.html"
       
   708         ], context, context_instance=template.RequestContext(request))
       
   709 
       
   710     def history_view(self, request, object_id, extra_context=None):
       
   711         "The 'history' admin view for this model."
       
   712         from django.contrib.admin.models import LogEntry
       
   713         model = self.model
       
   714         opts = model._meta
       
   715         app_label = opts.app_label
       
   716         action_list = LogEntry.objects.filter(
       
   717             object_id = object_id,
       
   718             content_type__id__exact = ContentType.objects.get_for_model(model).id
       
   719         ).select_related().order_by('action_time')
       
   720         # If no history was found, see whether this object even exists.
       
   721         obj = get_object_or_404(model, pk=object_id)
       
   722         context = {
       
   723             'title': _('Change history: %s') % force_unicode(obj),
       
   724             'action_list': action_list,
       
   725             'module_name': capfirst(force_unicode(opts.verbose_name_plural)),
       
   726             'object': obj,
       
   727             'root_path': self.admin_site.root_path,
       
   728             'app_label': app_label,
       
   729         }
       
   730         context.update(extra_context or {})
       
   731         return render_to_response(self.object_history_template or [
       
   732             "admin/%s/%s/object_history.html" % (app_label, opts.object_name.lower()),
       
   733             "admin/%s/object_history.html" % app_label,
       
   734             "admin/object_history.html"
       
   735         ], context, context_instance=template.RequestContext(request))
       
   736 
       
   737 class InlineModelAdmin(BaseModelAdmin):
       
   738     """
       
   739     Options for inline editing of ``model`` instances.
       
   740 
       
   741     Provide ``name`` to specify the attribute name of the ``ForeignKey`` from
       
   742     ``model`` to its parent. This is required if ``model`` has more than one
       
   743     ``ForeignKey`` to its parent.
       
   744     """
       
   745     model = None
       
   746     fk_name = None
       
   747     formset = BaseInlineFormSet
       
   748     extra = 3
       
   749     max_num = 0
       
   750     template = None
       
   751     verbose_name = None
       
   752     verbose_name_plural = None
       
   753 
       
   754     def __init__(self, parent_model, admin_site):
       
   755         self.admin_site = admin_site
       
   756         self.parent_model = parent_model
       
   757         self.opts = self.model._meta
       
   758         super(InlineModelAdmin, self).__init__()
       
   759         if self.verbose_name is None:
       
   760             self.verbose_name = self.model._meta.verbose_name
       
   761         if self.verbose_name_plural is None:
       
   762             self.verbose_name_plural = self.model._meta.verbose_name_plural
       
   763     
       
   764     def _media(self):
       
   765         from django.conf import settings
       
   766         js = []
       
   767         if self.prepopulated_fields:
       
   768             js.append('js/urlify.js')
       
   769         if self.filter_vertical or self.filter_horizontal:
       
   770             js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js'])
       
   771         return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js])
       
   772     media = property(_media)
       
   773 
       
   774     def get_formset(self, request, obj=None, **kwargs):
       
   775         """Returns a BaseInlineFormSet class for use in admin add/change views."""
       
   776         if self.declared_fieldsets:
       
   777             fields = flatten_fieldsets(self.declared_fieldsets)
       
   778         else:
       
   779             fields = None
       
   780         if self.exclude is None:
       
   781             exclude = []
       
   782         else:
       
   783             exclude = list(self.exclude)
       
   784         defaults = {
       
   785             "form": self.form,
       
   786             "formset": self.formset,
       
   787             "fk_name": self.fk_name,
       
   788             "fields": fields,
       
   789             "exclude": exclude + kwargs.get("exclude", []),
       
   790             "formfield_callback": self.formfield_for_dbfield,
       
   791             "extra": self.extra,
       
   792             "max_num": self.max_num,
       
   793         }
       
   794         defaults.update(kwargs)
       
   795         return inlineformset_factory(self.parent_model, self.model, **defaults)
       
   796 
       
   797     def get_fieldsets(self, request, obj=None):
       
   798         if self.declared_fieldsets:
       
   799             return self.declared_fieldsets
       
   800         form = self.get_formset(request).form
       
   801         return [(None, {'fields': form.base_fields.keys()})]
       
   802 
       
   803 class StackedInline(InlineModelAdmin):
       
   804     template = 'admin/edit_inline/stacked.html'
       
   805 
       
   806 class TabularInline(InlineModelAdmin):
       
   807     template = 'admin/edit_inline/tabular.html'