Commits

Anonymous committed 173c411

[soc2009/admin-ui] A far better way to implement adding inlines using javascript. Instead of handling all the prefix incrementing in Javascript and losing any default values, added a way to generate a template form that can be cloned every time a new inline is added.

This is for stacked inlines. Support for tabular and selector inlines coming next.

  • Participants
  • Parent commits 04a2ef6
  • Branches soc2009/admin-ui

Comments (0)

Files changed (5)

File django/contrib/admin/helpers.py

             yield InlineAdminForm(self.formset, form, self.fieldsets, self.opts.prepopulated_fields, original)
         for form in self.formset.extra_forms:
             yield InlineAdminForm(self.formset, form, self.fieldsets, self.opts.prepopulated_fields, None)
+        
+        yield InlineAdminForm(self.formset, self.formset.empty_form, self.fieldsets, self.opts.prepopulated_fields, None)
 
     def fields(self):
         fk = getattr(self.formset, "fk", None)

File django/contrib/admin/media/css/base.css

     background-color: #F6F6F6;
 }
 
+.empty_form {
+    display: none;
+}
+
 /* FORM DEFAULTS */
 
 input, textarea, select {

File django/contrib/admin/templates/admin/edit_inline/stacked.html

 {{ inline_admin_formset.formset.non_form_errors }}
 
 {% for inline_admin_form in inline_admin_formset %}
-<div class="inline-related{% if forloop.last %} last-related{% endif %}" id="{{ inline_admin_formset.opts.verbose_name}}{{ forloop.counter }}">
+<div class="inline-related{% if forloop.last %} empty_form{% endif %}" id="{{ inline_admin_formset.opts.verbose_name}}{% if not forloop.last %}{{ forloop.counter }}{% else %}-empty{% endif %}">
   <h3><b>{{ inline_admin_formset.opts.verbose_name|title }}:</b>&nbsp;<span class="inline_label">{% if inline_admin_form.original %}{{ inline_admin_form.original }}{% else %} #{{ forloop.counter }}{% endif %}</span>
     {% if inline_admin_formset.formset.can_delete and inline_admin_form.original %}<span class="delete">{{ inline_admin_form.deletion_field.field }} {{ inline_admin_form.deletion_field.label_tag }}</span>{% endif %}
   </h3>
 </div>
 {% endfor %}
 
-<ul class="tools add_inline">
+<ul class="tools add_inline" id="{{ inline_admin_formset.opts.verbose_name}}-addinline">
   <li><a id="{{ inline_admin_formset.opts.verbose_name }}-add" class="add" href="#">Add a {{ inline_admin_formset.opts.verbose_name }}</a></li>
 </ul>
 
 </div>
 
 <script type="text/javascript">
-function increment_fields(el) {
-    el.attr('id', el.attr('id').replace(/-(\d+)-/, function (num) {
-        var newnum = parseInt(num.replace(/-/g,''))+1;
-        return '-' + newnum + '-';
-    }));
+$(function() {
+    var prefix = "{{ inline_admin_formset.opts.verbose_name }}";
+    var id_prefix = "#" + prefix;
+    var total_forms = $(id_prefix + '-group input[id$="TOTAL_FORMS"]');
+    var initial_forms = $(id_prefix + '-group').find('input[id$="INITIAL_FORMS"]');
     
-    el.attr('name', el.attr('name').replace(/-(\d+)-/, function (num) {
-        var newnum = parseInt(num.replace(/-/g,''))+1;
-        return '-' + newnum + '-';
-    }));
-}
-
-$(function() {
-    var id_prefix = "{{ inline_admin_formset.opts.verbose_name }}";
-    var total_forms = $('#' + id_prefix + '-group input[id$="TOTAL_FORMS"]');
-    var initial_forms = $('#' + id_prefix + '-group').find('input[id$="INITIAL_FORMS"]');
-    
-    // since javascript is turned on, unhide the "add new <inline>" link and hide the extras
+    // since javascript is turned on, unhide the "add new <inline>" link
     $('.add_inline').show();
     
-    if (parseInt(initial_forms.val()) > 0) {
-        $('#' + id_prefix + '-group .inline-related:gt(' + (initial_forms.val() - 1) + ')').remove();
-    }
-    else {
-        $('#' + id_prefix + '-group .inline-related:gt(0)').remove();
-        $('#' + id_prefix + '-group .inline-related:first').hide();
-    }
-
-    total_forms.val(parseInt(initial_forms.val()));
-    
-    // clicking on the "add" link will add a blank form to add a new inline object
-    $('#' + id_prefix + "-add").click(function() {
-        if (parseInt(total_forms.val()) == 0) {
-            $('#' + id_prefix + '-group .inline-related:first').fadeIn('normal');
-            total_forms.val(parseInt(total_forms.val()) + 1);
-            return false;
+    // hide the extras, but only if there were no form errors
+    if (!$('.errornote').html()) {
+        if (parseInt(initial_forms.val()) > 0) {
+            $(id_prefix + '-group .inline-related:gt(' + (initial_forms.val() - 1) + ')')
+                .not('.empty_form').remove();
+        }
+        else {
+            $(id_prefix + '-group .inline-related').not('.empty_form').remove();
         }
         
-        var last_inline = $('#' + id_prefix + '-group .inline-related:last');
-        var new_inline = last_inline.clone(true).hide().insertAfter(last_inline).fadeIn('normal');
+        total_forms.val(parseInt(initial_forms.val()));
+    }
+    
+    $(id_prefix + "-add").click(function() {
+        var new_inline = $(id_prefix + '-empty').clone(true)
+                            .insertBefore(id_prefix + '-addinline').fadeIn('normal');
         
-        new_inline.find('input, select').each(function(i) { 
-            increment_fields($(this));
-            $(this).val("");
-        });
+        var inline_template = $(new_inline).html();
+        var new_inline_html = inline_template.replace(/__prefix__/g, total_forms.val().toString());
         
         total_forms.val(parseInt(total_forms.val()) + 1);
         
-        new_inline.find(".inline_label").text('#' + total_forms.val());
+        $(new_inline).html(new_inline_html);
+        $(new_inline).attr('id', prefix + total_forms.val().toString());
+        $(new_inline).find('.inline_label').html('#' + total_forms.val().toString());
+        $(new_inline).removeClass('empty_form');
         
         return false;
     });

File django/forms/formsets.py

         return self.forms[self.initial_form_count():]
     extra_forms = property(_get_extra_forms)
 
+    def _get_empty_form(self, **kwargs):
+        defaults = {'auto_id': self.auto_id, 'prefix': self.add_prefix("__prefix__")}
+        if self.data or self.files:
+            defaults['data'] = self.data
+            defaults['files'] = self.files
+        
+        defaults['empty_permitted'] = True
+        defaults.update(kwargs)
+
+        form = self.form(**defaults)
+        self.add_fields(form, None)
+        
+        return form
+    empty_form = property(_get_empty_form)
+
     # Maybe this should just go away?
     def _get_cleaned_data(self):
         """
         Returns a list of forms that have been marked for deletion. Raises an
         AttributeError if deletion is not allowed.
         """
-        if not self.can_delete:
+        if not self.is_valid or not self.can_delete:
             raise AttributeError("'%s' object has no attribute 'deleted_forms'" % self.__class__.__name__)
         # construct _deleted_form_indexes which is just a list of form indexes
         # that have had their deletion widget set to True
         Returns a list of form in the order specified by the incoming data.
         Raises an AttributeError if ordering is not allowed.
         """
-        if not self.can_order:
+        if not self.is_valid or not self.can_order:
             raise AttributeError("'%s' object has no attribute 'ordered_forms'" % self.__class__.__name__)
         # Construct _ordering, which is a list of (form_index, order_field_value)
         # tuples. After constructing this list, we'll sort it by order_field_value
         """A hook for adding extra fields on to each form instance."""
         if self.can_order:
             # Only pre-fill the ordering field for initial forms.
-            if index < self.initial_form_count():
+            if index != None and index < self.initial_form_count():
                 form.fields[ORDERING_FIELD_NAME] = IntegerField(label=_(u'Order'), initial=index+1, required=False)
             else:
                 form.fields[ORDERING_FIELD_NAME] = IntegerField(label=_(u'Order'), required=False)

File django/forms/models.py

                 or (pk.rel and pk.rel.parent_link and pk_is_not_editable(pk.rel.to._meta.pk)))
         if pk_is_not_editable(pk) or pk.name not in form.fields:
             try:
-                pk_value = self.get_queryset()[index].pk
+                if index:
+                    pk_value = self.get_queryset()[index].pk
+                else:
+                    pk_value = None
             except IndexError:
                 pk_value = None
             if isinstance(pk, OneToOneField) or isinstance(pk, ForeignKey):