# 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 '%s' % (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\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)