Ordering on inline edited items in Django's admin with jQuery
Feb 13, 2009

I just finished up with some relatively simple, but still fun modifications to the Django admin site for one of the projects I'm working on. For this project I needed to create a many-to-many relationship between two models with ordering information associated with it. This is fairly easy to accomplish by creating a many-to-many with an intermediary table. But providing a convenient mechanism for managing that information is a little trickier. By default you'll just end up with a text entry box in which to manually type the order for that item. This gets pretty old pretty fast, so finding a better method is important. In the past I've used jQuery to add drag and drop re-ordering to inline edited models, but this time I needed to do a little more as well. Specifically I wanted to make it easier to add more inline item. Ordinarily you just set an arbitrary number of empty boxes to have displayed (by default 3) and if you want more you have to fill those boxes, then hit 'Save and continue editing' to get three more. This is a pretty crappy way to do it (but the only way without introducing unwanted dependencies). Some googling revealed that Arne Brodowski had done pretty much exactly what I wanted to so, so I worked up some modified version of his scripts. The first step was setting it up to hide entries that were marked for deletion. Arne provided a prototype script to do this, but I made a few modifications that clean it up a bit and made it actually work (at least with jQuery 1.3.1). What I ended up with was this somewhat more elegant looking script: jQuery(function($) { $("div.inline-related input:checkbox[id$=DELETE]").change(function() { if ($(this).attr('checked')) { $(this).parents('div.inline-related').children('fieldset.module').addClass('collapsed'); } else { $(this).parents('div.inline-related').children('fieldset.module').removeClass('collapsed'); } }); }); The changes I made are mainly in the selector for grabbing the checkboxes ($("div.inline-related input:checkbox[id$=DELETE]"), and in the method for checking whether or not the box is actually checked ($(this).attr('checked')). With those changes it works exactly as advertised, which is a pretty handy bit of functionality. The next step was slightly more complex. To be able to dynamically add more relationships without having to 'Save and continue editing', I made basically the same template changes as Arne did, and fortunately this time didn't need to make many changes to the script. I basically just removed the 'return false' from the end and wound up with this: function increment_form_ids(el, to, name) { var from = to-1 ; $(':input', $(el)).each(function(i,e){ var old_name = $(e).attr('name'); var old_id = $(e).attr('id'); $(e).attr('name', old_name.replace(from, to)); $(e).attr('id', old_id.replace(from, to)); $(e).val(''); }); } function add_inline_form(name) { var first = $('#id_'+name+'-0-id').parents('.inline-related'); var last = $(first).parent().children('.last-related'); var copy = $(last).clone(true); var count = $(first).parent().children('.inline-related').length; $(last).removeClass('last-related'); $(last).after(copy); $('input#id_'+name+'-TOTAL_FORMS').val(count+1); increment_form_ids($(first).parents('.inline-group').children('.last-related'), count, name); $(first).parents('.inline-group').children('.last-related').find('input[id$=order]').val(0); $('div.inline-group').find('div.inline-related').each(function(i) { $(this).find('input[id$=order]').val(i+1); }); } You'll probably notice that I did actually make one other change. After I had gotten all this working, I realized there was one problem: If when starting from scratch you just happened to enter your choices in the order you wanted it wouldn't actually save that ordering information. By default (with my model definitions anyway) everything was assigned an order of 0 until you actually dragged things around to reorder them. This might be ok for some applications, but just won't work for this particular project. So I added those three lines of code to the function for adding new entries and also made sure that it's also done when you first load the page. This way you can be sure that everything will always be numbered appropriately and you won't end up with any unwanted 0s in your ordering information.
blog comments powered by Disqus