web/lib/django/contrib/admin/options.py
changeset 29 cc9b7e14412b
parent 0 0d40e90630ef
equal deleted inserted replaced
28:b758351d191f 29:cc9b7e14412b
     4 from django.forms.models import BaseInlineFormSet
     4 from django.forms.models import BaseInlineFormSet
     5 from django.contrib.contenttypes.models import ContentType
     5 from django.contrib.contenttypes.models import ContentType
     6 from django.contrib.admin import widgets
     6 from django.contrib.admin import widgets
     7 from django.contrib.admin import helpers
     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
     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
     9 from django.contrib import messages
       
    10 from django.views.decorators.csrf import csrf_protect
       
    11 from django.core.exceptions import PermissionDenied, ValidationError
    10 from django.db import models, transaction
    12 from django.db import models, transaction
    11 from django.db.models.fields import BLANK_CHOICE_DASH
    13 from django.db.models.fields import BLANK_CHOICE_DASH
    12 from django.http import Http404, HttpResponse, HttpResponseRedirect
    14 from django.http import Http404, HttpResponse, HttpResponseRedirect
    13 from django.shortcuts import get_object_or_404, render_to_response
    15 from django.shortcuts import get_object_or_404, render_to_response
       
    16 from django.utils.decorators import method_decorator
    14 from django.utils.datastructures import SortedDict
    17 from django.utils.datastructures import SortedDict
    15 from django.utils.functional import update_wrapper
    18 from django.utils.functional import update_wrapper
    16 from django.utils.html import escape
    19 from django.utils.html import escape
    17 from django.utils.safestring import mark_safe
    20 from django.utils.safestring import mark_safe
    18 from django.utils.functional import curry
    21 from django.utils.functional import curry
    19 from django.utils.text import capfirst, get_text_list
    22 from django.utils.text import capfirst, get_text_list
    20 from django.utils.translation import ugettext as _
    23 from django.utils.translation import ugettext as _
    21 from django.utils.translation import ungettext, ugettext_lazy
    24 from django.utils.translation import ungettext, ugettext_lazy
    22 from django.utils.encoding import force_unicode
    25 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 
    26 
    28 HORIZONTAL, VERTICAL = 1, 2
    27 HORIZONTAL, VERTICAL = 1, 2
    29 # returns the <ul> class for a given radio_admin field
    28 # returns the <ul> class for a given radio_admin field
    30 get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '')
    29 get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '')
    31 
    30 
    38 FORMFIELD_FOR_DBFIELD_DEFAULTS = {
    37 FORMFIELD_FOR_DBFIELD_DEFAULTS = {
    39     models.DateTimeField: {
    38     models.DateTimeField: {
    40         'form_class': forms.SplitDateTimeField,
    39         'form_class': forms.SplitDateTimeField,
    41         'widget': widgets.AdminSplitDateTime
    40         'widget': widgets.AdminSplitDateTime
    42     },
    41     },
    43     models.DateField:    {'widget': widgets.AdminDateWidget},
    42     models.DateField:       {'widget': widgets.AdminDateWidget},
    44     models.TimeField:    {'widget': widgets.AdminTimeWidget},
    43     models.TimeField:       {'widget': widgets.AdminTimeWidget},
    45     models.TextField:    {'widget': widgets.AdminTextareaWidget},
    44     models.TextField:       {'widget': widgets.AdminTextareaWidget},
    46     models.URLField:     {'widget': widgets.AdminURLFieldWidget},
    45     models.URLField:        {'widget': widgets.AdminURLFieldWidget},
    47     models.IntegerField: {'widget': widgets.AdminIntegerFieldWidget},
    46     models.IntegerField:    {'widget': widgets.AdminIntegerFieldWidget},
    48     models.CharField:    {'widget': widgets.AdminTextInputWidget},
    47     models.BigIntegerField: {'widget': widgets.AdminIntegerFieldWidget},
    49     models.ImageField:   {'widget': widgets.AdminFileWidget},
    48     models.CharField:       {'widget': widgets.AdminTextInputWidget},
    50     models.FileField:    {'widget': widgets.AdminFileWidget},
    49     models.ImageField:      {'widget': widgets.AdminFileWidget},
       
    50     models.FileField:       {'widget': widgets.AdminFileWidget},
    51 }
    51 }
    52 
    52 
       
    53 csrf_protect_m = method_decorator(csrf_protect)
    53 
    54 
    54 class BaseModelAdmin(object):
    55 class BaseModelAdmin(object):
    55     """Functionality common to both ModelAdmin and InlineAdmin."""
    56     """Functionality common to both ModelAdmin and InlineAdmin."""
       
    57     __metaclass__ = forms.MediaDefiningClass
    56 
    58 
    57     raw_id_fields = ()
    59     raw_id_fields = ()
    58     fields = None
    60     fields = None
    59     exclude = None
    61     exclude = None
    60     fieldsets = None
    62     fieldsets = None
    62     filter_vertical = ()
    64     filter_vertical = ()
    63     filter_horizontal = ()
    65     filter_horizontal = ()
    64     radio_fields = {}
    66     radio_fields = {}
    65     prepopulated_fields = {}
    67     prepopulated_fields = {}
    66     formfield_overrides = {}
    68     formfield_overrides = {}
       
    69     readonly_fields = ()
    67 
    70 
    68     def __init__(self):
    71     def __init__(self):
    69         self.formfield_overrides = dict(FORMFIELD_FOR_DBFIELD_DEFAULTS, **self.formfield_overrides)
    72         overrides = FORMFIELD_FOR_DBFIELD_DEFAULTS.copy()
       
    73         overrides.update(self.formfield_overrides)
       
    74         self.formfield_overrides = overrides
    70 
    75 
    71     def formfield_for_dbfield(self, db_field, **kwargs):
    76     def formfield_for_dbfield(self, db_field, **kwargs):
    72         """
    77         """
    73         Hook for specifying the form Field instance for a given database Field
    78         Hook for specifying the form Field instance for a given database Field
    74         instance.
    79         instance.
   136 
   141 
   137     def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
   142     def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
   138         """
   143         """
   139         Get a form Field for a ForeignKey.
   144         Get a form Field for a ForeignKey.
   140         """
   145         """
       
   146         db = kwargs.get('using')
   141         if db_field.name in self.raw_id_fields:
   147         if db_field.name in self.raw_id_fields:
   142             kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel)
   148             kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel, using=db)
   143         elif db_field.name in self.radio_fields:
   149         elif db_field.name in self.radio_fields:
   144             kwargs['widget'] = widgets.AdminRadioSelect(attrs={
   150             kwargs['widget'] = widgets.AdminRadioSelect(attrs={
   145                 'class': get_ul_class(self.radio_fields[db_field.name]),
   151                 'class': get_ul_class(self.radio_fields[db_field.name]),
   146             })
   152             })
   147             kwargs['empty_label'] = db_field.blank and _('None') or None
   153             kwargs['empty_label'] = db_field.blank and _('None') or None
   150 
   156 
   151     def formfield_for_manytomany(self, db_field, request=None, **kwargs):
   157     def formfield_for_manytomany(self, db_field, request=None, **kwargs):
   152         """
   158         """
   153         Get a form Field for a ManyToManyField.
   159         Get a form Field for a ManyToManyField.
   154         """
   160         """
   155         # If it uses an intermediary model, don't show field in admin.
   161         # If it uses an intermediary model that isn't auto created, don't show
   156         if db_field.rel.through is not None:
   162         # a field in admin.
       
   163         if not db_field.rel.through._meta.auto_created:
   157             return None
   164             return None
       
   165         db = kwargs.get('using')
   158 
   166 
   159         if db_field.name in self.raw_id_fields:
   167         if db_field.name in self.raw_id_fields:
   160             kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel)
   168             kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel, using=db)
   161             kwargs['help_text'] = ''
   169             kwargs['help_text'] = ''
   162         elif db_field.name in (list(self.filter_vertical) + list(self.filter_horizontal)):
   170         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))
   171             kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, (db_field.name in self.filter_vertical))
   164 
   172 
   165         return db_field.formfield(**kwargs)
   173         return db_field.formfield(**kwargs)
   170         elif self.fields:
   178         elif self.fields:
   171             return [(None, {'fields': self.fields})]
   179             return [(None, {'fields': self.fields})]
   172         return None
   180         return None
   173     declared_fieldsets = property(_declared_fieldsets)
   181     declared_fieldsets = property(_declared_fieldsets)
   174 
   182 
       
   183     def get_readonly_fields(self, request, obj=None):
       
   184         return self.readonly_fields
       
   185 
   175 class ModelAdmin(BaseModelAdmin):
   186 class ModelAdmin(BaseModelAdmin):
   176     "Encapsulates all admin options and functionality for a given model."
   187     "Encapsulates all admin options and functionality for a given model."
   177     __metaclass__ = forms.MediaDefiningClass
       
   178 
   188 
   179     list_display = ('__str__',)
   189     list_display = ('__str__',)
   180     list_display_links = ()
   190     list_display_links = ()
   181     list_filter = ()
   191     list_filter = ()
   182     list_select_related = False
   192     list_select_related = False
   188     save_on_top = False
   198     save_on_top = False
   189     ordering = None
   199     ordering = None
   190     inlines = []
   200     inlines = []
   191 
   201 
   192     # Custom templates (designed to be over-ridden in subclasses)
   202     # Custom templates (designed to be over-ridden in subclasses)
       
   203     add_form_template = None
   193     change_form_template = None
   204     change_form_template = None
   194     change_list_template = None
   205     change_list_template = None
   195     delete_confirmation_template = None
   206     delete_confirmation_template = None
       
   207     delete_selected_confirmation_template = None
   196     object_history_template = None
   208     object_history_template = None
   197 
   209 
   198     # Actions
   210     # Actions
   199     actions = []
   211     actions = []
   200     action_form = helpers.ActionForm
   212     action_form = helpers.ActionForm
   201     actions_on_top = True
   213     actions_on_top = True
   202     actions_on_bottom = False
   214     actions_on_bottom = False
       
   215     actions_selection_counter = True
   203 
   216 
   204     def __init__(self, model, admin_site):
   217     def __init__(self, model, admin_site):
   205         self.model = model
   218         self.model = model
   206         self.opts = model._meta
   219         self.opts = model._meta
   207         self.admin_site = admin_site
   220         self.admin_site = admin_site
   252     urls = property(urls)
   265     urls = property(urls)
   253 
   266 
   254     def _media(self):
   267     def _media(self):
   255         from django.conf import settings
   268         from django.conf import settings
   256 
   269 
   257         js = ['js/core.js', 'js/admin/RelatedObjectLookups.js']
   270         js = ['js/core.js', 'js/admin/RelatedObjectLookups.js',
       
   271               'js/jquery.min.js', 'js/jquery.init.js']
   258         if self.actions is not None:
   272         if self.actions is not None:
   259             js.extend(['js/getElementsBySelector.js', 'js/actions.js'])
   273             js.extend(['js/actions.min.js'])
   260         if self.prepopulated_fields:
   274         if self.prepopulated_fields:
   261             js.append('js/urlify.js')
   275             js.append('js/urlify.js')
       
   276             js.append('js/prepopulate.min.js')
   262         if self.opts.get_ordered_objects():
   277         if self.opts.get_ordered_objects():
   263             js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.js'])
   278             js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.js'])
   264 
   279 
   265         return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js])
   280         return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js])
   266     media = property(_media)
   281     media = property(_media)
   319     def get_fieldsets(self, request, obj=None):
   334     def get_fieldsets(self, request, obj=None):
   320         "Hook for specifying fieldsets for the add form."
   335         "Hook for specifying fieldsets for the add form."
   321         if self.declared_fieldsets:
   336         if self.declared_fieldsets:
   322             return self.declared_fieldsets
   337             return self.declared_fieldsets
   323         form = self.get_form(request, obj)
   338         form = self.get_form(request, obj)
   324         return [(None, {'fields': form.base_fields.keys()})]
   339         fields = form.base_fields.keys() + list(self.get_readonly_fields(request, obj))
       
   340         return [(None, {'fields': fields})]
   325 
   341 
   326     def get_form(self, request, obj=None, **kwargs):
   342     def get_form(self, request, obj=None, **kwargs):
   327         """
   343         """
   328         Returns a Form class for use in the admin add view. This is used by
   344         Returns a Form class for use in the admin add view. This is used by
   329         add_view and change_view.
   345         add_view and change_view.
   334             fields = None
   350             fields = None
   335         if self.exclude is None:
   351         if self.exclude is None:
   336             exclude = []
   352             exclude = []
   337         else:
   353         else:
   338             exclude = list(self.exclude)
   354             exclude = list(self.exclude)
       
   355         exclude.extend(kwargs.get("exclude", []))
       
   356         exclude.extend(self.get_readonly_fields(request, obj))
   339         # if exclude is an empty list we pass None to be consistant with the
   357         # if exclude is an empty list we pass None to be consistant with the
   340         # default on modelform_factory
   358         # default on modelform_factory
       
   359         exclude = exclude or None
   341         defaults = {
   360         defaults = {
   342             "form": self.form,
   361             "form": self.form,
   343             "fields": fields,
   362             "fields": fields,
   344             "exclude": (exclude + kwargs.get("exclude", [])) or None,
   363             "exclude": exclude,
   345             "formfield_callback": curry(self.formfield_for_dbfield, request=request),
   364             "formfield_callback": curry(self.formfield_for_dbfield, request=request),
   346         }
   365         }
   347         defaults.update(kwargs)
   366         defaults.update(kwargs)
   348         return modelform_factory(self.model, **defaults)
   367         return modelform_factory(self.model, **defaults)
       
   368 
       
   369     def get_changelist(self, request, **kwargs):
       
   370         """
       
   371         Returns the ChangeList class for use on the changelist page.
       
   372         """
       
   373         from django.contrib.admin.views.main import ChangeList
       
   374         return ChangeList
       
   375 
       
   376     def get_object(self, request, object_id):
       
   377         """
       
   378         Returns an instance matching the primary key provided. ``None``  is
       
   379         returned if no match is found (or the object_id failed validation
       
   380         against the primary key field).
       
   381         """
       
   382         queryset = self.queryset(request)
       
   383         model = queryset.model
       
   384         try:
       
   385             object_id = model._meta.pk.to_python(object_id)
       
   386             return queryset.get(pk=object_id)
       
   387         except (model.DoesNotExist, ValidationError):
       
   388             return None
   349 
   389 
   350     def get_changelist_form(self, request, **kwargs):
   390     def get_changelist_form(self, request, **kwargs):
   351         """
   391         """
   352         Returns a Form class for use in the Formset on the changelist page.
   392         Returns a Form class for use in the Formset on the changelist page.
   353         """
   393         """
   520 
   560 
   521         if formsets:
   561         if formsets:
   522             for formset in formsets:
   562             for formset in formsets:
   523                 for added_object in formset.new_objects:
   563                 for added_object in formset.new_objects:
   524                     change_message.append(_('Added %(name)s "%(object)s".')
   564                     change_message.append(_('Added %(name)s "%(object)s".')
   525                                           % {'name': added_object._meta.verbose_name,
   565                                           % {'name': force_unicode(added_object._meta.verbose_name),
   526                                              'object': force_unicode(added_object)})
   566                                              'object': force_unicode(added_object)})
   527                 for changed_object, changed_fields in formset.changed_objects:
   567                 for changed_object, changed_fields in formset.changed_objects:
   528                     change_message.append(_('Changed %(list)s for %(name)s "%(object)s".')
   568                     change_message.append(_('Changed %(list)s for %(name)s "%(object)s".')
   529                                           % {'list': get_text_list(changed_fields, _('and')),
   569                                           % {'list': get_text_list(changed_fields, _('and')),
   530                                              'name': changed_object._meta.verbose_name,
   570                                              'name': force_unicode(changed_object._meta.verbose_name),
   531                                              'object': force_unicode(changed_object)})
   571                                              'object': force_unicode(changed_object)})
   532                 for deleted_object in formset.deleted_objects:
   572                 for deleted_object in formset.deleted_objects:
   533                     change_message.append(_('Deleted %(name)s "%(object)s".')
   573                     change_message.append(_('Deleted %(name)s "%(object)s".')
   534                                           % {'name': deleted_object._meta.verbose_name,
   574                                           % {'name': force_unicode(deleted_object._meta.verbose_name),
   535                                              'object': force_unicode(deleted_object)})
   575                                              'object': force_unicode(deleted_object)})
   536         change_message = ' '.join(change_message)
   576         change_message = ' '.join(change_message)
   537         return change_message or _('No fields changed.')
   577         return change_message or _('No fields changed.')
   538 
   578 
   539     def message_user(self, request, message):
   579     def message_user(self, request, message):
   540         """
   580         """
   541         Send a message to the user. The default implementation
   581         Send a message to the user. The default implementation
   542         posts a message using the auth Message object.
   582         posts a message using the django.contrib.messages backend.
   543         """
   583         """
   544         request.user.message_set.create(message=message)
   584         messages.info(request, message)
   545 
   585 
   546     def save_form(self, request, form, change):
   586     def save_form(self, request, form, change):
   547         """
   587         """
   548         Given a ModelForm return an unsaved instance. ``change`` is True if
   588         Given a ModelForm return an unsaved instance. ``change`` is True if
   549         the object is being changed, and False if it's being added.
   589         the object is being changed, and False if it's being added.
   580             'content_type_id': ContentType.objects.get_for_model(self.model).id,
   620             'content_type_id': ContentType.objects.get_for_model(self.model).id,
   581             'save_as': self.save_as,
   621             'save_as': self.save_as,
   582             'save_on_top': self.save_on_top,
   622             'save_on_top': self.save_on_top,
   583             'root_path': self.admin_site.root_path,
   623             'root_path': self.admin_site.root_path,
   584         })
   624         })
       
   625         if add and self.add_form_template is not None:
       
   626             form_template = self.add_form_template
       
   627         else:
       
   628             form_template = self.change_form_template
   585         context_instance = template.RequestContext(request, current_app=self.admin_site.name)
   629         context_instance = template.RequestContext(request, current_app=self.admin_site.name)
   586         return render_to_response(self.change_form_template or [
   630         return render_to_response(form_template or [
   587             "admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()),
   631             "admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()),
   588             "admin/%s/change_form.html" % app_label,
   632             "admin/%s/change_form.html" % app_label,
   589             "admin/change_form.html"
   633             "admin/change_form.html"
   590         ], context, context_instance=context_instance)
   634         ], context, context_instance=context_instance)
   591 
   635 
   653         """
   697         """
   654         Handle an admin action. This is called if a request is POSTed to the
   698         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
   699         changelist; it returns an HttpResponse if the action was handled, and
   656         None otherwise.
   700         None otherwise.
   657         """
   701         """
       
   702 
   658         # There can be multiple action forms on the page (at the top
   703         # There can be multiple action forms on the page (at the top
   659         # and bottom of the change list, for example). Get the action
   704         # and bottom of the change list, for example). Get the action
   660         # whose button was pushed.
   705         # whose button was pushed.
   661         try:
   706         try:
   662             action_index = int(request.POST.get('index', 0))
   707             action_index = int(request.POST.get('index', 0))
   681         action_form.fields['action'].choices = self.get_action_choices(request)
   726         action_form.fields['action'].choices = self.get_action_choices(request)
   682 
   727 
   683         # If the form's valid we can handle the action.
   728         # If the form's valid we can handle the action.
   684         if action_form.is_valid():
   729         if action_form.is_valid():
   685             action = action_form.cleaned_data['action']
   730             action = action_form.cleaned_data['action']
       
   731             select_across = action_form.cleaned_data['select_across']
   686             func, name, description = self.get_actions(request)[action]
   732             func, name, description = self.get_actions(request)[action]
   687 
   733 
   688             # Get the list of selected PKs. If nothing's selected, we can't
   734             # Get the list of selected PKs. If nothing's selected, we can't
   689             # perform an action on it, so bail.
   735             # perform an action on it, so bail. Except we want to perform
       
   736             # the action explicitly on all objects.
   690             selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME)
   737             selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME)
   691             if not selected:
   738             if not selected and not select_across:
       
   739                 # Reminder that something needs to be selected or nothing will happen
       
   740                 msg = _("Items must be selected in order to perform "
       
   741                         "actions on them. No items have been changed.")
       
   742                 self.message_user(request, msg)
   692                 return None
   743                 return None
   693 
   744 
   694             response = func(self, request, queryset.filter(pk__in=selected))
   745             if not select_across:
       
   746                 # Perform the action only on the selected objects
       
   747                 queryset = queryset.filter(pk__in=selected)
       
   748 
       
   749             response = func(self, request, queryset)
   695 
   750 
   696             # Actions may return an HttpResponse, which will be used as the
   751             # 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
   752             # response from the POST. If not, we'll be a good little HTTP
   698             # citizen and redirect back to the changelist page.
   753             # citizen and redirect back to the changelist page.
   699             if isinstance(response, HttpResponse):
   754             if isinstance(response, HttpResponse):
   700                 return response
   755                 return response
   701             else:
   756             else:
   702                 return HttpResponseRedirect(".")
   757                 return HttpResponseRedirect(".")
   703 
   758         else:
       
   759             msg = _("No action selected.")
       
   760             self.message_user(request, msg)
       
   761             return None
       
   762 
       
   763     @csrf_protect_m
       
   764     @transaction.commit_on_success
   704     def add_view(self, request, form_url='', extra_context=None):
   765     def add_view(self, request, form_url='', extra_context=None):
   705         "The 'add' admin view for this model."
   766         "The 'add' admin view for this model."
   706         model = self.model
   767         model = self.model
   707         opts = model._meta
   768         opts = model._meta
   708 
   769 
   712         ModelForm = self.get_form(request)
   773         ModelForm = self.get_form(request)
   713         formsets = []
   774         formsets = []
   714         if request.method == 'POST':
   775         if request.method == 'POST':
   715             form = ModelForm(request.POST, request.FILES)
   776             form = ModelForm(request.POST, request.FILES)
   716             if form.is_valid():
   777             if form.is_valid():
       
   778                 new_object = self.save_form(request, form, change=False)
   717                 form_validated = True
   779                 form_validated = True
   718                 new_object = self.save_form(request, form, change=False)
       
   719             else:
   780             else:
   720                 form_validated = False
   781                 form_validated = False
   721                 new_object = self.model()
   782                 new_object = self.model()
   722             prefixes = {}
   783             prefixes = {}
   723             for FormSet in self.get_formsets(request):
   784             for FormSet, inline in zip(self.get_formsets(request), self.inline_instances):
   724                 prefix = FormSet.get_default_prefix()
   785                 prefix = FormSet.get_default_prefix()
   725                 prefixes[prefix] = prefixes.get(prefix, 0) + 1
   786                 prefixes[prefix] = prefixes.get(prefix, 0) + 1
   726                 if prefixes[prefix] != 1:
   787                 if prefixes[prefix] != 1:
   727                     prefix = "%s-%s" % (prefix, prefixes[prefix])
   788                     prefix = "%s-%s" % (prefix, prefixes[prefix])
   728                 formset = FormSet(data=request.POST, files=request.FILES,
   789                 formset = FormSet(data=request.POST, files=request.FILES,
   729                                   instance=new_object,
   790                                   instance=new_object,
   730                                   save_as_new=request.POST.has_key("_saveasnew"),
   791                                   save_as_new=request.POST.has_key("_saveasnew"),
   731                                   prefix=prefix)
   792                                   prefix=prefix, queryset=inline.queryset(request))
   732                 formsets.append(formset)
   793                 formsets.append(formset)
   733             if all_valid(formsets) and form_validated:
   794             if all_valid(formsets) and form_validated:
   734                 self.save_model(request, new_object, form, change=False)
   795                 self.save_model(request, new_object, form, change=False)
   735                 form.save_m2m()
   796                 form.save_m2m()
   736                 for formset in formsets:
   797                 for formset in formsets:
   749                     continue
   810                     continue
   750                 if isinstance(f, models.ManyToManyField):
   811                 if isinstance(f, models.ManyToManyField):
   751                     initial[k] = initial[k].split(",")
   812                     initial[k] = initial[k].split(",")
   752             form = ModelForm(initial=initial)
   813             form = ModelForm(initial=initial)
   753             prefixes = {}
   814             prefixes = {}
   754             for FormSet in self.get_formsets(request):
   815             for FormSet, inline in zip(self.get_formsets(request),
       
   816                                        self.inline_instances):
   755                 prefix = FormSet.get_default_prefix()
   817                 prefix = FormSet.get_default_prefix()
   756                 prefixes[prefix] = prefixes.get(prefix, 0) + 1
   818                 prefixes[prefix] = prefixes.get(prefix, 0) + 1
   757                 if prefixes[prefix] != 1:
   819                 if prefixes[prefix] != 1:
   758                     prefix = "%s-%s" % (prefix, prefixes[prefix])
   820                     prefix = "%s-%s" % (prefix, prefixes[prefix])
   759                 formset = FormSet(instance=self.model(), prefix=prefix)
   821                 formset = FormSet(instance=self.model(), prefix=prefix,
       
   822                                   queryset=inline.queryset(request))
   760                 formsets.append(formset)
   823                 formsets.append(formset)
   761 
   824 
   762         adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)), self.prepopulated_fields)
   825         adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)),
       
   826             self.prepopulated_fields, self.get_readonly_fields(request),
       
   827             model_admin=self)
   763         media = self.media + adminForm.media
   828         media = self.media + adminForm.media
   764 
   829 
   765         inline_admin_formsets = []
   830         inline_admin_formsets = []
   766         for inline, formset in zip(self.inline_instances, formsets):
   831         for inline, formset in zip(self.inline_instances, formsets):
   767             fieldsets = list(inline.get_fieldsets(request))
   832             fieldsets = list(inline.get_fieldsets(request))
   768             inline_admin_formset = helpers.InlineAdminFormSet(inline, formset, fieldsets)
   833             readonly = list(inline.get_readonly_fields(request))
       
   834             inline_admin_formset = helpers.InlineAdminFormSet(inline, formset,
       
   835                 fieldsets, readonly, model_admin=self)
   769             inline_admin_formsets.append(inline_admin_formset)
   836             inline_admin_formsets.append(inline_admin_formset)
   770             media = media + inline_admin_formset.media
   837             media = media + inline_admin_formset.media
   771 
   838 
   772         context = {
   839         context = {
   773             'title': _('Add %s') % force_unicode(opts.verbose_name),
   840             'title': _('Add %s') % force_unicode(opts.verbose_name),
   780             'root_path': self.admin_site.root_path,
   847             'root_path': self.admin_site.root_path,
   781             'app_label': opts.app_label,
   848             'app_label': opts.app_label,
   782         }
   849         }
   783         context.update(extra_context or {})
   850         context.update(extra_context or {})
   784         return self.render_change_form(request, context, form_url=form_url, add=True)
   851         return self.render_change_form(request, context, form_url=form_url, add=True)
   785     add_view = transaction.commit_on_success(add_view)
   852 
   786 
   853     @csrf_protect_m
       
   854     @transaction.commit_on_success
   787     def change_view(self, request, object_id, extra_context=None):
   855     def change_view(self, request, object_id, extra_context=None):
   788         "The 'change' admin view for this model."
   856         "The 'change' admin view for this model."
   789         model = self.model
   857         model = self.model
   790         opts = model._meta
   858         opts = model._meta
   791 
   859 
   792         try:
   860         obj = self.get_object(request, unquote(object_id))
   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 
   861 
   800         if not self.has_change_permission(request, obj):
   862         if not self.has_change_permission(request, obj):
   801             raise PermissionDenied
   863             raise PermissionDenied
   802 
   864 
   803         if obj is None:
   865         if obj is None:
   815                 new_object = self.save_form(request, form, change=True)
   877                 new_object = self.save_form(request, form, change=True)
   816             else:
   878             else:
   817                 form_validated = False
   879                 form_validated = False
   818                 new_object = obj
   880                 new_object = obj
   819             prefixes = {}
   881             prefixes = {}
   820             for FormSet in self.get_formsets(request, new_object):
   882             for FormSet, inline in zip(self.get_formsets(request, new_object),
       
   883                                        self.inline_instances):
   821                 prefix = FormSet.get_default_prefix()
   884                 prefix = FormSet.get_default_prefix()
   822                 prefixes[prefix] = prefixes.get(prefix, 0) + 1
   885                 prefixes[prefix] = prefixes.get(prefix, 0) + 1
   823                 if prefixes[prefix] != 1:
   886                 if prefixes[prefix] != 1:
   824                     prefix = "%s-%s" % (prefix, prefixes[prefix])
   887                     prefix = "%s-%s" % (prefix, prefixes[prefix])
   825                 formset = FormSet(request.POST, request.FILES,
   888                 formset = FormSet(request.POST, request.FILES,
   826                                   instance=new_object, prefix=prefix)
   889                                   instance=new_object, prefix=prefix,
       
   890                                   queryset=inline.queryset(request))
       
   891 
   827                 formsets.append(formset)
   892                 formsets.append(formset)
   828 
   893 
   829             if all_valid(formsets) and form_validated:
   894             if all_valid(formsets) and form_validated:
   830                 self.save_model(request, new_object, form, change=True)
   895                 self.save_model(request, new_object, form, change=True)
   831                 form.save_m2m()
   896                 form.save_m2m()
   837                 return self.response_change(request, new_object)
   902                 return self.response_change(request, new_object)
   838 
   903 
   839         else:
   904         else:
   840             form = ModelForm(instance=obj)
   905             form = ModelForm(instance=obj)
   841             prefixes = {}
   906             prefixes = {}
   842             for FormSet in self.get_formsets(request, obj):
   907             for FormSet, inline in zip(self.get_formsets(request, obj), self.inline_instances):
   843                 prefix = FormSet.get_default_prefix()
   908                 prefix = FormSet.get_default_prefix()
   844                 prefixes[prefix] = prefixes.get(prefix, 0) + 1
   909                 prefixes[prefix] = prefixes.get(prefix, 0) + 1
   845                 if prefixes[prefix] != 1:
   910                 if prefixes[prefix] != 1:
   846                     prefix = "%s-%s" % (prefix, prefixes[prefix])
   911                     prefix = "%s-%s" % (prefix, prefixes[prefix])
   847                 formset = FormSet(instance=obj, prefix=prefix)
   912                 formset = FormSet(instance=obj, prefix=prefix,
       
   913                                   queryset=inline.queryset(request))
   848                 formsets.append(formset)
   914                 formsets.append(formset)
   849 
   915 
   850         adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj), self.prepopulated_fields)
   916         adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj),
       
   917             self.prepopulated_fields, self.get_readonly_fields(request, obj),
       
   918             model_admin=self)
   851         media = self.media + adminForm.media
   919         media = self.media + adminForm.media
   852 
   920 
   853         inline_admin_formsets = []
   921         inline_admin_formsets = []
   854         for inline, formset in zip(self.inline_instances, formsets):
   922         for inline, formset in zip(self.inline_instances, formsets):
   855             fieldsets = list(inline.get_fieldsets(request, obj))
   923             fieldsets = list(inline.get_fieldsets(request, obj))
   856             inline_admin_formset = helpers.InlineAdminFormSet(inline, formset, fieldsets)
   924             readonly = list(inline.get_readonly_fields(request, obj))
       
   925             inline_admin_formset = helpers.InlineAdminFormSet(inline, formset,
       
   926                 fieldsets, readonly, model_admin=self)
   857             inline_admin_formsets.append(inline_admin_formset)
   927             inline_admin_formsets.append(inline_admin_formset)
   858             media = media + inline_admin_formset.media
   928             media = media + inline_admin_formset.media
   859 
   929 
   860         context = {
   930         context = {
   861             'title': _('Change %s') % force_unicode(opts.verbose_name),
   931             'title': _('Change %s') % force_unicode(opts.verbose_name),
   869             'root_path': self.admin_site.root_path,
   939             'root_path': self.admin_site.root_path,
   870             'app_label': opts.app_label,
   940             'app_label': opts.app_label,
   871         }
   941         }
   872         context.update(extra_context or {})
   942         context.update(extra_context or {})
   873         return self.render_change_form(request, context, change=True, obj=obj)
   943         return self.render_change_form(request, context, change=True, obj=obj)
   874     change_view = transaction.commit_on_success(change_view)
   944 
   875 
   945     @csrf_protect_m
   876     def changelist_view(self, request, extra_context=None):
   946     def changelist_view(self, request, extra_context=None):
   877         "The 'change list' admin view for this model."
   947         "The 'change list' admin view for this model."
   878         from django.contrib.admin.views.main import ChangeList, ERROR_FLAG
   948         from django.contrib.admin.views.main import ERROR_FLAG
   879         opts = self.model._meta
   949         opts = self.model._meta
   880         app_label = opts.app_label
   950         app_label = opts.app_label
   881         if not self.has_change_permission(request, None):
   951         if not self.has_change_permission(request, None):
   882             raise PermissionDenied
   952             raise PermissionDenied
   883 
   953 
   890             try:
   960             try:
   891                 list_display.remove('action_checkbox')
   961                 list_display.remove('action_checkbox')
   892             except ValueError:
   962             except ValueError:
   893                 pass
   963                 pass
   894 
   964 
       
   965         ChangeList = self.get_changelist(request)
   895         try:
   966         try:
   896             cl = ChangeList(request, self.model, list_display, self.list_display_links, self.list_filter,
   967             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)
   968                 self.date_hierarchy, self.search_fields, self.list_select_related, self.list_per_page, self.list_editable, self)
   898         except IncorrectLookupParameters:
   969         except IncorrectLookupParameters:
   899             # Wacky lookup parameters were given, so redirect to the main
   970             # Wacky lookup parameters were given, so redirect to the main
   900             # changelist page, without parameters, and pass an 'invalid=1'
   971             # changelist page, without parameters, and pass an 'invalid=1'
   901             # parameter via the query string. If wacky parameters were given and
   972             # parameter via the query string. If wacky parameters were given
   902             # the 'invalid=1' parameter was already in the query string, something
   973             # and the 'invalid=1' parameter was already in the query string,
   903             # is screwed up with the database, so display an error page.
   974             # something is screwed up with the database, so display an error
       
   975             # page.
   904             if ERROR_FLAG in request.GET.keys():
   976             if ERROR_FLAG in request.GET.keys():
   905                 return render_to_response('admin/invalid_setup.html', {'title': _('Database error')})
   977                 return render_to_response('admin/invalid_setup.html', {'title': _('Database error')})
   906             return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1')
   978             return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1')
   907 
   979 
   908         # If the request was POSTed, this might be a bulk action or a bulk edit.
   980         # If the request was POSTed, this might be a bulk action or a bulk
   909         # Try to look up an action first, but if this isn't an action the POST
   981         # edit. Try to look up an action or confirmation first, but if this
   910         # will fall through to the bulk edit check, below.
   982         # isn't an action the POST will fall through to the bulk edit check,
   911         if actions and request.method == 'POST':
   983         # below.
   912             response = self.response_action(request, queryset=cl.get_query_set())
   984         action_failed = False
   913             if response:
   985         selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME)
   914                 return response
   986 
       
   987         # Actions with no confirmation
       
   988         if (actions and request.method == 'POST' and
       
   989                 'index' in request.POST and '_save' not in request.POST):
       
   990             if selected:
       
   991                 response = self.response_action(request, queryset=cl.get_query_set())
       
   992                 if response:
       
   993                     return response
       
   994                 else:
       
   995                     action_failed = True
       
   996             else:
       
   997                 msg = _("Items must be selected in order to perform "
       
   998                         "actions on them. No items have been changed.")
       
   999                 self.message_user(request, msg)
       
  1000                 action_failed = True
       
  1001 
       
  1002         # Actions with confirmation
       
  1003         if (actions and request.method == 'POST' and
       
  1004                 helpers.ACTION_CHECKBOX_NAME in request.POST and
       
  1005                 'index' not in request.POST and '_save' not in request.POST):
       
  1006             if selected:
       
  1007                 response = self.response_action(request, queryset=cl.get_query_set())
       
  1008                 if response:
       
  1009                     return response
       
  1010                 else:
       
  1011                     action_failed = True
   915 
  1012 
   916         # If we're allowing changelist editing, we need to construct a formset
  1013         # 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
  1014         # for the changelist given all the fields to be edited. Then we'll
   918         # use the formset to validate/process POSTed data.
  1015         # use the formset to validate/process POSTed data.
   919         formset = cl.formset = None
  1016         formset = cl.formset = None
   920 
  1017 
   921         # Handle POSTed bulk-edit data.
  1018         # Handle POSTed bulk-edit data.
   922         if request.method == "POST" and self.list_editable:
  1019         if (request.method == "POST" and self.list_editable and
       
  1020                 '_save' in request.POST and not action_failed):
   923             FormSet = self.get_changelist_formset(request)
  1021             FormSet = self.get_changelist_formset(request)
   924             formset = cl.formset = FormSet(request.POST, request.FILES, queryset=cl.result_list)
  1022             formset = cl.formset = FormSet(request.POST, request.FILES, queryset=cl.result_list)
   925             if formset.is_valid():
  1023             if formset.is_valid():
   926                 changecount = 0
  1024                 changecount = 0
   927                 for form in formset.forms:
  1025                 for form in formset.forms:
   963             action_form = self.action_form(auto_id=None)
  1061             action_form = self.action_form(auto_id=None)
   964             action_form.fields['action'].choices = self.get_action_choices(request)
  1062             action_form.fields['action'].choices = self.get_action_choices(request)
   965         else:
  1063         else:
   966             action_form = None
  1064             action_form = None
   967 
  1065 
       
  1066         selection_note_all = ungettext('%(total_count)s selected',
       
  1067             'All %(total_count)s selected', cl.result_count)
       
  1068 
   968         context = {
  1069         context = {
       
  1070             'module_name': force_unicode(opts.verbose_name_plural),
       
  1071             'selection_note': _('0 of %(cnt)s selected') % {'cnt': len(cl.result_list)},
       
  1072             'selection_note_all': selection_note_all % {'total_count': cl.result_count},
   969             'title': cl.title,
  1073             'title': cl.title,
   970             'is_popup': cl.is_popup,
  1074             'is_popup': cl.is_popup,
   971             'cl': cl,
  1075             'cl': cl,
   972             'media': media,
  1076             'media': media,
   973             'has_add_permission': self.has_add_permission(request),
  1077             'has_add_permission': self.has_add_permission(request),
   974             'root_path': self.admin_site.root_path,
  1078             'root_path': self.admin_site.root_path,
   975             'app_label': app_label,
  1079             'app_label': app_label,
   976             'action_form': action_form,
  1080             'action_form': action_form,
   977             'actions_on_top': self.actions_on_top,
  1081             'actions_on_top': self.actions_on_top,
   978             'actions_on_bottom': self.actions_on_bottom,
  1082             'actions_on_bottom': self.actions_on_bottom,
       
  1083             'actions_selection_counter': self.actions_selection_counter,
   979         }
  1084         }
   980         context.update(extra_context or {})
  1085         context.update(extra_context or {})
   981         context_instance = template.RequestContext(request, current_app=self.admin_site.name)
  1086         context_instance = template.RequestContext(request, current_app=self.admin_site.name)
   982         return render_to_response(self.change_list_template or [
  1087         return render_to_response(self.change_list_template or [
   983             'admin/%s/%s/change_list.html' % (app_label, opts.object_name.lower()),
  1088             'admin/%s/%s/change_list.html' % (app_label, opts.object_name.lower()),
   984             'admin/%s/change_list.html' % app_label,
  1089             'admin/%s/change_list.html' % app_label,
   985             'admin/change_list.html'
  1090             'admin/change_list.html'
   986         ], context, context_instance=context_instance)
  1091         ], context, context_instance=context_instance)
   987 
  1092 
       
  1093     @csrf_protect_m
   988     def delete_view(self, request, object_id, extra_context=None):
  1094     def delete_view(self, request, object_id, extra_context=None):
   989         "The 'delete' admin view for this model."
  1095         "The 'delete' admin view for this model."
   990         opts = self.model._meta
  1096         opts = self.model._meta
   991         app_label = opts.app_label
  1097         app_label = opts.app_label
   992 
  1098 
   993         try:
  1099         obj = self.get_object(request, unquote(object_id))
   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 
  1100 
  1001         if not self.has_delete_permission(request, obj):
  1101         if not self.has_delete_permission(request, obj):
  1002             raise PermissionDenied
  1102             raise PermissionDenied
  1003 
  1103 
  1004         if obj is None:
  1104         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)})
  1105             raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_unicode(opts.verbose_name), 'key': escape(object_id)})
  1006 
  1106 
  1007         # Populate deleted_objects, a data structure of all related objects that
  1107         # Populate deleted_objects, a data structure of all related objects that
  1008         # will also be deleted.
  1108         # 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))), []]
  1109         (deleted_objects, perms_needed) = get_deleted_objects((obj,), opts, request.user, self.admin_site)
  1010         perms_needed = set()
       
  1011         get_deleted_objects(deleted_objects, perms_needed, request.user, obj, opts, 1, self.admin_site)
       
  1012 
  1110 
  1013         if request.POST: # The user has already confirmed the deletion.
  1111         if request.POST: # The user has already confirmed the deletion.
  1014             if perms_needed:
  1112             if perms_needed:
  1015                 raise PermissionDenied
  1113                 raise PermissionDenied
  1016             obj_display = force_unicode(obj)
  1114             obj_display = force_unicode(obj)
  1050         action_list = LogEntry.objects.filter(
  1148         action_list = LogEntry.objects.filter(
  1051             object_id = object_id,
  1149             object_id = object_id,
  1052             content_type__id__exact = ContentType.objects.get_for_model(model).id
  1150             content_type__id__exact = ContentType.objects.get_for_model(model).id
  1053         ).select_related().order_by('action_time')
  1151         ).select_related().order_by('action_time')
  1054         # If no history was found, see whether this object even exists.
  1152         # If no history was found, see whether this object even exists.
  1055         obj = get_object_or_404(model, pk=object_id)
  1153         obj = get_object_or_404(model, pk=unquote(object_id))
  1056         context = {
  1154         context = {
  1057             'title': _('Change history: %s') % force_unicode(obj),
  1155             'title': _('Change history: %s') % force_unicode(obj),
  1058             'action_list': action_list,
  1156             'action_list': action_list,
  1059             'module_name': capfirst(force_unicode(opts.verbose_name_plural)),
  1157             'module_name': capfirst(force_unicode(opts.verbose_name_plural)),
  1060             'object': obj,
  1158             'object': obj,
  1107     """
  1205     """
  1108     model = None
  1206     model = None
  1109     fk_name = None
  1207     fk_name = None
  1110     formset = BaseInlineFormSet
  1208     formset = BaseInlineFormSet
  1111     extra = 3
  1209     extra = 3
  1112     max_num = 0
  1210     max_num = None
  1113     template = None
  1211     template = None
  1114     verbose_name = None
  1212     verbose_name = None
  1115     verbose_name_plural = None
  1213     verbose_name_plural = None
       
  1214     can_delete = True
  1116 
  1215 
  1117     def __init__(self, parent_model, admin_site):
  1216     def __init__(self, parent_model, admin_site):
  1118         self.admin_site = admin_site
  1217         self.admin_site = admin_site
  1119         self.parent_model = parent_model
  1218         self.parent_model = parent_model
  1120         self.opts = self.model._meta
  1219         self.opts = self.model._meta
  1124         if self.verbose_name_plural is None:
  1223         if self.verbose_name_plural is None:
  1125             self.verbose_name_plural = self.model._meta.verbose_name_plural
  1224             self.verbose_name_plural = self.model._meta.verbose_name_plural
  1126 
  1225 
  1127     def _media(self):
  1226     def _media(self):
  1128         from django.conf import settings
  1227         from django.conf import settings
  1129         js = []
  1228         js = ['js/jquery.min.js', 'js/jquery.init.js', 'js/inlines.min.js']
  1130         if self.prepopulated_fields:
  1229         if self.prepopulated_fields:
  1131             js.append('js/urlify.js')
  1230             js.append('js/urlify.js')
       
  1231             js.append('js/prepopulate.min.js')
  1132         if self.filter_vertical or self.filter_horizontal:
  1232         if self.filter_vertical or self.filter_horizontal:
  1133             js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js'])
  1233             js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js'])
  1134         return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js])
  1234         return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js])
  1135     media = property(_media)
  1235     media = property(_media)
  1136 
  1236 
  1142             fields = None
  1242             fields = None
  1143         if self.exclude is None:
  1243         if self.exclude is None:
  1144             exclude = []
  1244             exclude = []
  1145         else:
  1245         else:
  1146             exclude = list(self.exclude)
  1246             exclude = list(self.exclude)
       
  1247         exclude.extend(kwargs.get("exclude", []))
       
  1248         exclude.extend(self.get_readonly_fields(request, obj))
  1147         # if exclude is an empty list we use None, since that's the actual
  1249         # if exclude is an empty list we use None, since that's the actual
  1148         # default
  1250         # default
       
  1251         exclude = exclude or None
  1149         defaults = {
  1252         defaults = {
  1150             "form": self.form,
  1253             "form": self.form,
  1151             "formset": self.formset,
  1254             "formset": self.formset,
  1152             "fk_name": self.fk_name,
  1255             "fk_name": self.fk_name,
  1153             "fields": fields,
  1256             "fields": fields,
  1154             "exclude": (exclude + kwargs.get("exclude", [])) or None,
  1257             "exclude": exclude,
  1155             "formfield_callback": curry(self.formfield_for_dbfield, request=request),
  1258             "formfield_callback": curry(self.formfield_for_dbfield, request=request),
  1156             "extra": self.extra,
  1259             "extra": self.extra,
  1157             "max_num": self.max_num,
  1260             "max_num": self.max_num,
       
  1261             "can_delete": self.can_delete,
  1158         }
  1262         }
  1159         defaults.update(kwargs)
  1263         defaults.update(kwargs)
  1160         return inlineformset_factory(self.parent_model, self.model, **defaults)
  1264         return inlineformset_factory(self.parent_model, self.model, **defaults)
  1161 
  1265 
  1162     def get_fieldsets(self, request, obj=None):
  1266     def get_fieldsets(self, request, obj=None):
  1163         if self.declared_fieldsets:
  1267         if self.declared_fieldsets:
  1164             return self.declared_fieldsets
  1268             return self.declared_fieldsets
  1165         form = self.get_formset(request).form
  1269         form = self.get_formset(request).form
  1166         return [(None, {'fields': form.base_fields.keys()})]
  1270         fields = form.base_fields.keys() + list(self.get_readonly_fields(request, obj))
       
  1271         return [(None, {'fields': fields})]
       
  1272 
       
  1273     def queryset(self, request):
       
  1274         return self.model._default_manager.all()
  1167 
  1275 
  1168 class StackedInline(InlineModelAdmin):
  1276 class StackedInline(InlineModelAdmin):
  1169     template = 'admin/edit_inline/stacked.html'
  1277     template = 'admin/edit_inline/stacked.html'
  1170 
  1278 
  1171 class TabularInline(InlineModelAdmin):
  1279 class TabularInline(InlineModelAdmin):