# 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)