Ajax ordering models on the change list page of the admin using drag and drop with jQuery UI

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# models.py
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.core.urlresolvers import reverse


class MenuItemManager(models.Manager):
    def get_query_set(self):
        return super(MenuItemManager, self).get_query_set().order_by("order", "id")
    

class MenuItem(models.Model):
    name = models.CharField(max_length=100, blank=True, verbose_name="Name")
    order = models.IntegerField(blank = True, null = True)
    
    objects = MenuItemManager()
    order_field = 'order' # You can specify your own field for sorting, but it's 'order' by default

    class Meta:
        db_table = u"menu"
    
    def __unicode__(self):
        return u"%s" % self.name
        
    def order_link(self):
        model_type_id = ContentType.objects.get_for_model(self.__class__).id
        obj_id = self.id
        kwargs = {"model_type_id": model_type_id}
        url = reverse("admin_order", kwargs=kwargs)
        return '<a href="%s" class="order_link">%s</a>' % (url, str(self.pk) or '')
    order_link.allow_tags = True
    order_link.short_description = 'Order' # If you change this you should change admin_sorting.js too

# urls.py

(r'^order/(?P<model_type_id>\d+)/$', 'path.to.view', {}, 'admin_order'),

# views.py

from django.http import HttpResponseRedirect, HttpResponse
from django.contrib.contenttypes.models import ContentType

def order(request, model_type_id=None):
  if not request.is_ajax() or not request.method == "POST":
      return HttpResponse("BAD")
  
  try:
      indexes = request.POST.get('indexes', []).split(",")
      
      klass = ContentType.objects.get(id=model_type_id).model_class()
      order_field = getattr(klass, 'order_field', 'order')
      
      objects_dict = dict([(obj.pk, obj) for obj in klass.objects.filter(pk__in=indexes)])
      min_index = min(objects_dict.values(), key=lambda x: getattr(x, order_field))
      min_index = getattr(min_index, order_field) or 0
      for index in indexes:
        obj = objects_dict[int(index)]
        setattr(obj, order_field, min_index)
        obj.save()
        min_index += 1
      
  except IndexError:
      pass
  except klass.DoesNotExist:
      pass
  except AttributeError:
      pass
  
  return HttpResponse()

# admin_sorting.js

$(function() {
    $("#content-main").sortable({
        axis: 'y',
        items: '.row1, .row2',
        stop: function(event, ui) {
            var indexes = Array();
            var url = "";
            $("#content-main").find(".row1, .row2").each(function(i){
                var id = $(this).find('.order_link').html();
                // the bad way i know.
                url = $(this).find('.order_link').attr('href');
                indexes.push(id);
            });
            $.ajax({
               url: url,
               type: 'POST',
               data: {
                 indexes: indexes.join(","),
               }
            });
        },
    });
    $('#content-main table tbody').find('.order_link').parent('td').hide();
    /* 
       :contains(value)  value here must be the same as in model's 
       order_link.short_description 
    */
    $('#content-main table thead').find('th:contains("Order")').hide();
    $("#content-main table tbody").disableSelection();
});

# admin.py

class MenuItemAdmin(admin.ModelAdmin):
    list_display = ('id', 'name', 'order_link')
    list_display_links = ('id', 'name')
    ordering = ('order','id')
    
    class Media:
      js = ("js/jquery-1.4.2.min.js",
            "js/jqueryui-custom-1.7.2.min.js",
            "js/admin_sorting.js",)

admin.site.register(MenuItem, MenuItemAdmin)

More like this

  1. Drag and drop ordering of admin list elements using jQuery UI by johj 2 years, 11 months ago
  2. Orderable inlines using drag and drop with jQuery UI by simon 4 years, 8 months ago
  3. Drag and drop ordering of admin list elements for Grappelli by sjaensch 2 years, 4 months ago
  4. Drag and drop admin list items by chrsgrrtt 2 years, 9 months ago
  5. Dynamic tabular inlines with optional drag-n-drop sorting by Aneon 4 years ago

Comments

elequ (on January 2, 2011):

Thanks!

bug on line 9: s/MenuManager/MenuItemManager/

It'd also be helpful to put a comma at the end of the urls.py line (when copying it's easy to neglect to put in ones own comma). Thanks again.

#

spoof (on January 24, 2011):

Thank you for reply. i've fixed typos :)

#

abolotnov (on October 30, 2012):

how do I get rid of "CSRF verification failed. Request aborted." error? I tried decorating the order view with @csrf_exempt - no cure :(

#

abolotnov (on October 30, 2012):

Never mind my comment :) Fixed that.

#

(Forgotten your password?)