views:

310

answers:

2

I have been looking for a way to allow the user to easily change the order of entries in a formset. I found a StackOverflow question that addresses this subject, with the accepted answer referencing a Django Snippet that uses a JQuery tool to allow drag 'n drop of the entries. This is nifty and cool, but I have a problem with 'extra' entries. If an 'extra' entry is modified and then dragged I get an error on the submit:

(1048, "Column 'order' cannot be null")

I believe Django keeps the 'extra' entries separate, since they have to be inserted instead of updates. So the reordering probably confuses matters. Is there a way to make this work, or are there other suggestions for reordering and/or adding new entries?

Edit: Added some relevant code excerpts. I'm trying this out in the admin, as the snippet shows. I'd like to put it in my own page ultimately, however.

models.py:

class Section(models.Model):
    section_id = models.AutoField(primary_key=True)
    section_name = models.CharField(max_length=135)
    score_order = models.IntegerField()
    def __unicode__(self):
      return self.section_name
    class Meta:
        db_table = u'section'
        ordering = [u"score_order"]

class Chair(models.Model):
    chair_id = models.AutoField(primary_key=True)
    member = models.ForeignKey(Member, null=True, blank=True,
      limit_choices_to={'current_member': True})
    section = models.ForeignKey(Section)
    description = models.CharField(max_length=135)
    order = models.IntegerField(blank=True, null=True)
    def __unicode__(self):
      return "%s - %s" % (self.description, self.member)
    class Meta:
        db_table = u'chair'
        ordering = (u'section', u'order')

admin.py

class SectionForm(forms.ModelForm):
    model = Section
    class Media:
        js = (
            '/scripts/jquery.js',
            '/scripts/ui.core.js',
            '/scripts/ui.sortable.js',
            '/scripts/section-sort.js',
        )

class ChairInline(admin.StackedInline):
    model = Chair

admin.site.register(Section,
                    inlines = [ChairInline],
                    form = SectionForm,
)
+1  A: 

I do sortable formsets in one of my apps. I use this jQuery drag and drop plugin:

http://www.isocra.com/2008/02/table-drag-and-drop-jquery-plugin/

I bind the plugin's onDrop event to a function that resets the value of all "order" fields.

Additionally, I pass initial data to the formset so the formset's extra "order" fields always have a value in cases where there is no javascript available - the user won't be able to re-order rows, but they can edit and post changes without the null error you described.

Harold
Thanks for your response. I've found my own solution, which I'll post as an answer shortly. This plugin sounds similar to the jQuery UI plugin I'm using. I can't use initial values since I'm using an [inline formset](http://stackoverflow.com/questions/442040/pre-populate-an-inline-formset), and I don't think that would help. It would avoid the error, but the modified extra rows still wouldn't get the correct order. I dunno, if you have this working you must have solved this.
Fred Larson
A: 

I found my own solution. The snippet was setting the order for every row that had a non-empty primary key value. But the extra rows have an empty primary key, and I believe they have to stay empty for Django to know that they are to be inserted instead of updated. I modified the function to check for the other fields being empty (fortunately, I only have a couple) as well as the primary key:

jQuery(function($) {
    $('div.inline-group').sortable({
        items: 'div.inline-related',
        handle: 'h3:first',
        update: function() {
            $(this).find('div.inline-related').each(function(i) {
                if ($(this).find('input[id$=chair_id]').val() ||
                  $(this).find('select[id$=member]').val() ||
                      $(this).find('select[id$=description]').val()) {
                    $(this).find('input[id$=order]').val(i+1);
                }
            });
        }
    });
    $('div.inline-related h3').css('cursor', 'move');
    $('div.inline-related').find('input[id$=order]').parent('div').hide();
});

This did the trick for me. I could maybe improve it by adding a hidden field to the form that gets set whenever any field on a row gets modified. But at this point I'm still a jQuery n00b, so this will do. If anybody has better ideas, feel free to comment or add another answer.

Fred Larson